Tasks: derive the work-item ref, and stop swallowing outbound-hook failures
Derived from the approved
bugfix.md,design.mdandtesting-plan.md. A DAG, not a list: tasks with no edge between them are independent. Each_Test:_names a row of the testing plan.
flowchart TD
T1["T1 — refs.py: derive_ref()"] --> T2["T2 — Runtime.work_item() uses it"]
T1 --> T7["T7 — unit tests"]
T3["T3 — _split_ref names its remedies"] --> T7
T4["T4 — _degradations() + wire into<br/>advance / start / cleanup"] --> T5["T5 — force & skip audit warnings"]
T4 --> T8["T8 — integration scenarios"]
T2 --> T8
T5 --> T6["T6 — graph_cmd prints skip warnings"]
T6 --> T8
T7 --> T9["T9 — docs: CLI page + capability docs"]
T8 --> T9
T9 --> T10["T10 — verification: run the plan,<br/>commit evidence"]Tasks
[x] T1 —
cli/the_loop/graph/refs.py: the pure translation. New module withderive_ref(work_item_id, origin_repo) -> str. Validates the id against^issue-(\d+)$, splitsorigin_repointo exactly one owner and one repo, checks both againstsessions/registry.py's existing GitHub name regex (imported, not re-declared), and builds the ref throughWorkItemRef(...).ref. Returns""on any failure; raises nothing; performs no I/O. Requirements: R1.1, R1.3, R1.4 · Test: T1[x] T2 —
Runtime.work_item()derives when--refis absent. An explicit ref still wins; an underivable one still falls back to the bare id. One debug log line when derivation happens, so-vshows which ref was used. An inner loop derives the pull request's ref instead (config["prRef"], built bybuild_runtimefrom--pr/--pr-repo) and never falls through to the work item's — found in self-review, and the one way this fix could have been worse than the bug. Requirements: R1.1, R1.2, R1.3, R1.5 · Test: T1, T2[x] T3 —
_split_refnames both remedies. Message only: the expected shape,--ref, andticketing.githubin the harness config. No behaviour change. Requirements: R3.1 · Test: T1[x] T4 —
_degradations()and its three readers. New helper ingraph/runtime.pyreturning(hook, error)for every result that passed while carrying a non-emptydata["error"]. Wired intoadvance(both the exit chain and the target's entry chain),startandcleanup: each appendswarning: <hook> did not complete: <error>to itsNodeReport.messagesand emitsgraph.hook_degradedatwarninglevel. Status, outcome, edge and pointer unchanged. Requirements: R2.1, R2.3, R2.4 · Test: T1, T2[x] T5 — the force and skip audit comments report their own failure.
_announce_forceand_announce_skipsreturn the error string instead of only logging it;force()appends it toForceResult.warnings,declare_skips()to a newSkipResult.warnings.core/graphs.skip()carrieswarningsin its dict. Requirements: R2.5, R2.6 · Test: T2[x] T6 —
graph_cmdprints the skip warnings.WARNING: <text>per entry, matching the force verb's existing output.advanceandrunneed no change — they already printresult["messages"], which is where T4's lines land. Requirements: R2.2, R2.6 · Test: T2, T6[x] T7 — unit tests (
cli/tests/test_graph_refs.py). Every row of the T1 trace: the happy path, the four id refusals, the three origin-repo refusals, the three name-shape refusals,_degradations' keying onerrorand its silence on a legitimate no-op, and_split_ref's message. Includes the three abuse cases fromdesign.md§ Security design. Requirements: R1.1, R1.3, R1.4, R2.1, R3.1 · Test: T1, T9[x] T8 — integration scenarios (
cli/tests/test_graph_refs_integration.py). Gherkin-docstringed, against a realRuntimeand a fake integration: the checklist reaching the derived ref; a failing hook printing without moving the edge; a repository with no ticketing config; the force and skip audit failures. Two of them driveGraphCommandand assert oncapsys, which is the T6 row. Requirements: R1.1, R1.2, R1.3, R2.1–R2.6, R3.1 · Test: T2, T6[x] T9 — documentation.
docs/cli/commands/graph.md: what--refnow defaults to, and the new warning line.docs/capabilities/process-graph.md: the ref-resolution ladder and the degradation reporting.docs/capabilities/cli.mdif the skip verb's output is described there. Execution log's## Documentationsection records what changed and why. Requirements: all · Test: T13[x] T10 — verification. Execute
testing-plan.md: run every activity, tick only what ran, record command, outcome and evidence, and commit the evidence underevidence/. Requirements: all · Test: T1, T2, T4, T6, T9, T13
Unplanned work, recorded
Two changes the plan did not anticipate. Both are in the diff and both are here rather than folded silently into a task above.
hooks/sideeffects.pyresolves its integration at call time. Found while writing T8: the module boundresolveat import, so the seam every other hook and every test patches did not apply to it, and the test reachedapi.github.comfor real. Fixed the seam rather than the test — a hook whose failure path cannot be exercised is the same class of defect this work item is about. One-line change, no behaviour difference in production (the sameresolve, one frame later).graph.hook_degradedregistered ineventlog.EVENT_TYPES.test_eventlog.pygates every emitted event against the catalogue, and rightly failed until the new event was documented there.