Repository navigation
feat(kix): render planning decisions into PRs from recorded answers - #172
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The service now writes a planning pull request's decision section from the job's answered decision questions, with a check mark on each choice and the maintainer's own words. Permission requests and account or publication questions stay out. Every prompt and answer appears only in a code span, so GitHub links no mention, reference, URL, or closing keyword in them. Each delivery re-renders the section and drops any the agent wrote, and planning assignments tell the agent not to write the trail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Outcome: this is complete and ready for review. It was started by a background
No decisions are open. The PR body lists the routine choices I made (code spans over zero-width breaks, labels without descriptions, the section appended at the end, planning PRs only, (Written by Claude Opus 5.5) |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
# Conflicts: # quest/m0/kix/README.md
…es its PR A retried plan job continues the failed job's pull request (#171), but the decision section rendered only the new job's answers, so the first delivery after a retry erased the earlier rounds. Render the answers of every job on the target that delivered the same pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0add401c30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| */ | ||
| export function withDecisions(body: string, questions: { prompt: unknown; answer: unknown }[]): string { | ||
| const own = body | ||
| .replace(/<!-- kix:decisions -->[\s\S]*?(<!-- \/kix:decisions -->|$)/g, "") |
There was a problem hiding this comment.
Prevent embedded end markers from escaping sanitization
When a later publication passes the rendered PR body back into withDecisions, an untrusted prompt or answer containing <!-- /kix:decisions --> makes this non-greedy regex stop at the marker inside its code span. The remainder then becomes own outside the span and is prepended to the fresh section, so content following the marker—such as mentions or Closes #1—can regain GitHub semantics and leaked fragments accumulate on subsequent deliveries. Remove the generated section without interpreting marker text inside its contents as a delimiter, or neutralize marker strings before rendering.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1f61fb8: a section now ends only at an end marker on its own line (or the end of the body). Untrusted text is always inside a code span after a list prefix, with line breaks flattened, so it can never start a line. The hostile-input test now includes a marker followed by Closes #6 @octocat and checks that copying the published body back yields the same section with nothing outside code spans.
(Written by Claude Opus 5.5)
| * backtick run inside, so the text cannot close it. | ||
| */ | ||
| export function code(value: string): string { | ||
| const flat = value.replace(/[\u202a-\u202e\u2066-\u2069]/g, "").replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, " "); |
There was a problem hiding this comment.
Remove all Unicode bidirectional controls
When untrusted text contains U+061C, U+200E, or U+200F, this sanitization leaves those Unicode bidi-control characters intact while removing only embedding, override, and isolate controls. Such input can therefore render with a misleading visual order in the public decision record despite the function's guarantee that bidi controls are dropped; include the remaining Bidi_Control code points in the removal set.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1f61fb8: code() now also drops U+061C, U+200E, and U+200F, covering every Bidi_Control code point. Tests cover each one.
(Written by Claude Opus 5.5)
# Conflicts: # quest/m0/kix/README.md
An untrusted prompt containing the end marker stopped the section match inside its code span when an agent copied the published body back, so the rest of the section leaked outside code spans. Code spans never start a line, so the section now ends only at an end marker on its own line, or at the end of the body. Also drop U+061C, U+200E, and U+200F with the other bidirectional controls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7415b7d691
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .replaceAll(END, "") | ||
| .trimEnd(); | ||
| const section = render(questions, BODY_LIMIT - own.length - 2); | ||
| return section ? (own ? `${own}\n\n${section}` : section) : own; |
There was a problem hiding this comment.
Keep the required attribution at the end
For every planning PR with at least one recorded decision, this append places the generated section after the agent-supplied body, so an otherwise compliant (written by <model>) footer is no longer the final content of the GitHub post. Preserve that footer after the generated section, or insert the section before it, so publication continues to satisfy the repository policy.
AGENTS.md reference: AGENTS.md:L31-L31
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not changing this. The (written by <model>) line is this repository's convention for agents developing quest (AGENTS.md), not part of the shared workflow in assets/AGENTS.md, and the core stays independent of any one repository's conventions. Detecting a trailing attribution line would couple kix to that convention. The appended section is the service's own content, labeled "Recorded by kix from the maintainer's answers", so the agent's attribution still ends the agent's text. Appending at the end of the body is one of this PR's recorded decisions, which the maintainer accepted. If an agent should control where the section goes, a follow-up could honor an empty <!-- kix:decisions --> placeholder the agent writes.
(Written by Claude Opus 5.5)
|
Merge summary for head 4947d5f. Changes since the PR opened:
Decisions:
Review: Codex reviewed 1f61fb8 with no findings and 7415b7d with the one declined finding. Since then the only change is a clean merge from main. CI passes, and Follow-ups:
(Written by Claude Opus 5.5) |
Completes quest/m0/kix/paper-trail.md.
Problem
A planning PR's paper trail was whatever the agent wrote, so it could paraphrase or drift from what the maintainer actually chose, and it carried untrusted prompt text straight into a public PR body.
Behavior
src/jobs/trail.tsrenders a## Decisionssection from the job's answeredagentquestions whose prompt is adecision, one### Round Nper answered prompt. Each option gets ✅ (chosen) or ⬜, and free text shows after ✍️. Permission requests and the service'saccountandpublicationquestions are left out, so runners and accounts never reach the PR.@user/@org/teammention,#N/GH-N/owner/repo#Nreference, commit SHA, bare URL, or closing keyword. I checked this against GitHub's own renderer (POST /markdown,gfm, contextkixelated/quest): code spans linked nothing, and the same text outside them linked mentions, issues, the commit, and the URL.publishadds the section to everyplandelivery, between<!-- kix:decisions -->markers. Any section the agent wrote, including a forged or unterminated one, is dropped first. Re-delivery renders the section again from the record, so it updates in place and is never duplicated. If the body would exceed GitHub's 65,536-character limit, the rounds that don't fit are counted in a note instead of being cut mid-round.assignnow carriesinstructions(nullexcept forplan). A planning job's instructions tell the agent not to write the trail but still to record settled decisions in the quest Plans. The shared skills are unchanged, so local agents keep writing their own trail.runner.mdtells the future runner to forward these instructions.cloudflare/GITHUB.md#publication,cloudflare/README.md, andquest/m0/kix/planning.md. The finished quest is deleted.Validation
just checkandjust testpass in the Nix shell (cloudflare: 232 tests; all: 269).test/trail.test.tscovers the rendered format; skipping permission prompts, account prompts, and invalid answers; 15 hostile inputs, parsed as GFM withmarked, which must yield no link, image, emphasis, blockquote, or extra HTML, and no untrusted text outside code spans (Markdown,@user,@org/team,Closes/fixes/RESOLVES,#N,owner/repo#N,GH-N, SHAs andowner/repo@sha, bare URLs andwww., GitHub issue URLs, emails, HTML and comment markers, backtick runs, blank-line breakouts, bidi and line separators); replacing a forged section; and the body limit. A mutation that drops the code spans fails the hostile test.test/publish.test.tsruns a multi-round interview with a permission round, free text, and an account limit whose account question must not appear, then checks a re-delivery after another round updates the one section. Astartjob's body is left untouched.test/jobs.test.tschecks theinstructionson planning and other assignments.Decisions made without asking (recommended options)
assign, since the runner that will use them doesn't exist yet.Suggested follow-ups
start) PRs whose jobs asked decisions, if wanted.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)