Generate comprehension-first PR explanation reports #141
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/tools!141
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/pr-explain-report-tool"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements the reusable PR explanation report generator tracked in #138.
Replaces #139, which was opened unintentionally by an AGit push and has no head branch. This PR carries the same 14 commits on a normal
agent/*branch, plus one additional commit fixing the interruption defect raised in review on #139.What this adds
allod pr explain <number> --codex|--claude --output <report.html>with explicit provider consent, immutable PR SHA verification, detached analysis checkout, provider-environment sanitization, atomic publication, overwrite protection, dry-run, and one bounded validation-repair pass.forge pr snapshot <number>as a stable read-only JSON interface for same-repository and fork PRs.The narrow-screen alignment bug is fixed structurally: flow connectors are pseudo-elements owned by their destination nodes. The authored glyph changes from
↓to→/←when the shared container has room; no full-width connector box is rotated independently of its node.Signal handling fix
pr-explain/explainpreviously installed only anEXITtrap and derived its preserve/report decision from$?. A SIGINT while waiting on the provider, or an external SIGTERM, could leave the EXIT trap observing status 0 — so an interrupted run could leave the private.allod-pr-explain.*job directory behind without announcing it, and in some paths continue toward publication. That directory holds the complete diff and review context.Explicit
INTandTERMhandlers now record a non-zero interrupted state, terminate the provider child, and force the EXIT cleanup and the pass runner to honour that state. The provider now runs as a background child that the scriptwaits on, so the signal reaches the shell rather than being absorbed by a foreground child.Human workflow
The command never commits, pushes, edits the PR, or chooses a private publication path.
Validation
allod/tools#136succeeded and produced a 67 KB validated report. This ran against the original 14 commits, not the signal fix.The 446 command assertions and 14 snapshot assertions quoted on #139 were already stale when that body was written; later commits on the same branch added tests. The numbers above are measured on this branch head.
Codex runner behavior is fully mocked and tested; no Codex tokens were used after the account quota was exhausted.
Risk
The signal handling change moves both provider invocations from foreground execution to a backgrounded child plus
wait. Theclaudepath also changes from a subshellcdtopushd/popd, so the working-directory change is no longer confined to a subshell — thepopdrestores it on the success path, and an interrupted run exits rather than continuing. Signal behaviour is covered by mocked runners only; no attended interrupt of a real provider was performed.Copied from #139 — originally posted by vnprc-agent on 2026-08-15.
Owner review artifact generated by this PR itself: https://forge.anarch.diy/vnprc/notes/raw/branch/master/allod/reports/pr-explain-report-tool-explanation.html
The report is self-contained and validated. Please review both its explanation and the visual/interaction language; concrete comprehension feedback can drive the next template iteration.
Copied from #139 — originally posted by vnprc-agent on 2026-08-16.
Review finding
High — interrupted runs are reported as success and leak the private job directory.
pr-explain/explain:17-33only installs anEXITtrap and decides whether to preserve/report the job from$?. In Bash, an interactive SIGINT received while waiting for a foreground child does not make the shell exit non-zero by itself; aftercodex/claudeis interrupted, execution can continue (or a later command can become the final status), and an external SIGTERM can terminate the shell with the EXIT trap observing status 0. That means Ctrl-C or service shutdown can leave.allod-pr-explain.*behind without printingdiagnostics preserved, and can in some paths continue toward publication. This is especially problematic because the directory contains the complete diff and review context. Install explicitINT/TERMhandlers that record a non-zero interrupted state (and terminate the provider/process group as needed), make the EXIT cleanup/report path use that state, and add mocked slow-runner tests for both signals verifying non-zero exit, no publication, and an announced preserved directory.Addressed on this PR by
df54ab0("pr-explain: fail closed on provider interruption"), which is the one commit this PR carries beyond #139. See the Signal handling fix and Validation sections of the description.Owner-requested direction change: comprehension redesign lands on this PR
The owner reviewed a research pass on how this tool teaches (learning-science survey: cognitive load theory, Mayer's multimedia principles, expertise reversal, ICAP, retrieval practice, backward design, program-comprehension research) and chose to extend this PR rather than replace it, so the tool keeps being dogfooded on its own PR. The snapshot interface, provider cage, invariant validator, and component vocabulary stay; the single-shot authoring prompt is replaced by a staged pipeline. Requested changes, to land as commit series here:
--force-tieroverrides.docs/primers/.Witness for each slice: regenerate this PR's own self-explanation report and judge the comprehension gain directly; merge when the owner's comprehension gate passes.
The redesign's first three slices are implemented and committed on this branch, awaiting relay-push (two commits:
pr-explain: teach through a staged comprehension pipeline,pr-explain: read identifiers from markup tags, not raw bytes).What landed
triage-prompt.md): investigates the checkout and writes a schema-gatedtriage.json— tier T0–T3, decision risk, reading budget, concept inventory, developer questions, and 1–7 verb-first learning objectives. A T0 verdict declines to generate;--force-tier <T1|T2|T3>overrides in either direction and is recorded in the judgment. The pass is caged like the others: snapshot immutable, checkout untouched, body still empty.p.rx-cost);mainopens with an objectives block mirroring triage ids; every main section carriesdata-layerin non-decreasing concept → mechanism → receipts order; sections, figures, and quiz items carrydata-objective, and the validator proves every objective is both taught and tested. Quiz items (3–7, interleavable at section ends) carrydata-concept; no answer letter may be correct more than twice.slop-prompt.md, skippable with--no-slop): deletion-only tightening, enforced by the shell — the output must be byte-smaller than the author draft. A mechanical linter backs it: banned-vocabulary errors, repeated sentence-opening trigram errors, hedge-density warnings.Dogfood result
The first attended v2 run explained this PR itself. Triage judged T3 ("a layered trust model … it rests on Bash signal-disposition and AGit ref-namespace prerequisites the reviewer likely lacks"). The authored report then got rejected by the tool's own validator — and the rejection was a genuine validator bug: the report quoted
PR_EXPLAIN_PROVIDER_PID=""in a codewalk, and the E3 identifier census read raw bytes, soPID=""counted as an id attribute. Identifier and reference accounting now runs on a markup tag stream, with regression probes in both directions. The report validates cleanly under the fixed validator; it is stored in the owner's notes for the comprehension merge gate.Remaining slices from the recorded plan: SVG + interactive vocabulary, primer library, reader model + spaced-repetition cards.
Live-tested the interruption fix (
pr-explain: fail closed on provider interruption) against real runs of this PR, since the suite only ever proved it against a mock.Live results (interrupting attended claude runs mid-triage and mid-author):
interrupted by SIGTERM, the real claude CLI process died from the forwarded TERM, no report published, diagnostics preserved.interrupted by SIGINT, same fail-closed behavior — but only once the signal was deliverable at all (see below).One suite defect found and fixed (
pr-explain: make the interruption tests non-vacuous): the INT test case launched the command as a backgrounded job, so explain inherited SIGINT ignored at entry per the POSIX background rule — and bash cannot trap a signal ignored at entry. The trap under test could never fire. The case passed anyway because the harness then hand-killed the provider, producing every asserted symptom (non-zero exit, no report, preserved diagnostics) with zero interruption handling involved. The case now launches underenv --default-signal=SIGINT,SIGQUIT, never touches the provider, and asserts what the live probe checked: the provider dies from the trap's forwarded TERM, the exit status is exactly 130/143, and the diagnosis names the signal.The product code needed no changes — the fix was correct; its strongest test was fiction. Three commits now await relay: the pipeline, the tag-stream validator fix, and this.
Requested change 8 (recorded before implementing, per convention): remove the AGit acceptance carve-out from
allod pr explain.With the forge set to stop accepting AGit submissions (per the open governance issue on the forge configuration), the carve-out in
pr_explain_normalize_refand thepull_head_refclause of the snapshot schema serve only a flow the stack is outlawing. Removing them:check-ref-format, and the dangerous-syntax checks all stand. Only the acceptance goes.One deliberate asymmetry:
forge pr snapshotkeeps describing AGit-created PRs faithfully. It is a read command with no side effects, and historical AGit PRs exist on the forge; a reader that refuses to describe reality hurts archaeology without adding safety. Reads describe reality; actions enforce policy. The two snapshot schemas (forge-side and explain-side) become intentionally different at exactly this clause, and the docs will say so.Test changes: the two AGit acceptance cases flip to rejection cases asserting the named diagnosis and that no provider is invoked; the existing rejection cases (mismatched number, arbitrary explicit ref, dangerous syntax, AGit-shaped base) stay as they are.
This also refines my earlier note on the governance issue: the warning there — don't delete the validation half when closing the gap — stands. What goes here is the acceptance half, which that warning was never meant to protect.
Owner completed the comprehension gate on the v2 self-explanation report. Recording the reader-measurement results verbatim in substance, because they are the first real datapoint the measurement layer has produced:
Consequences proposed (pending owner confirmation):
This is the feedback loop the plan called "measurement"; the first cycle ran on a human, as intended.
df54ab0ec6to4e6a22cff8