dev-loops 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +7 -7
  3. package/.claude/agents/fixer.md +1 -0
  4. package/.claude/agents/refiner.md +2 -2
  5. package/.claude/agents/review.md +2 -2
  6. package/.claude/commands/loop-enqueue.md +1 -1
  7. package/.claude/commands/loop-info.md +1 -1
  8. package/.claude/commands/loop-start-spike.md +1 -1
  9. package/.claude/skills/copilot-pr-followup/SKILL.md +37 -23
  10. package/.claude/skills/dev-loop/SKILL.md +6 -6
  11. package/.claude/skills/docs/acceptance-criteria-verification.md +3 -2
  12. package/.claude/skills/docs/anti-patterns.md +3 -2
  13. package/.claude/skills/docs/artifact-authority-contract.md +12 -11
  14. package/.claude/skills/docs/confirmation-rules.md +1 -0
  15. package/.claude/skills/docs/contract-style-guide.md +37 -0
  16. package/.claude/skills/docs/copilot-loop-operations.md +18 -14
  17. package/.claude/skills/docs/cross-harness-regression-contract.md +2 -2
  18. package/.claude/skills/docs/debt-remediation-contract.md +1 -1
  19. package/.claude/skills/docs/epic-tree-refinement-procedure.md +25 -22
  20. package/.claude/skills/docs/issue-intake-procedure.md +25 -19
  21. package/.claude/skills/docs/merge-preconditions.md +13 -11
  22. package/.claude/skills/docs/pr-lifecycle-contract.md +44 -35
  23. package/.claude/skills/docs/public-dev-loop-contract.md +59 -43
  24. package/.claude/skills/docs/retrospective-checkpoint-contract.md +18 -5
  25. package/.claude/skills/docs/spike-mode-contract.md +14 -7
  26. package/.claude/skills/docs/stop-conditions.md +30 -15
  27. package/.claude/skills/docs/tracker-first-loop-state.md +13 -8
  28. package/.claude/skills/docs/validation-policy.md +4 -5
  29. package/.claude/skills/local-implementation/SKILL.md +48 -142
  30. package/CHANGELOG.md +6 -0
  31. package/agents/dev-loop.agent.md +8 -8
  32. package/agents/fixer.agent.md +1 -0
  33. package/agents/refiner.agent.md +2 -2
  34. package/agents/review.agent.md +2 -2
  35. package/package.json +5 -4
  36. package/scripts/_core-helpers.mjs +2 -0
  37. package/scripts/claude/headless-dev-loop.mjs +1 -1
  38. package/scripts/docs/validate-rule-ownership.mjs +442 -0
  39. package/scripts/docs/validate-state-machine-conformance.mjs +1092 -0
  40. package/scripts/github/_review-thread-mutations.mjs +5 -2
  41. package/scripts/github/create-pr.mjs +119 -10
  42. package/scripts/github/detect-checkpoint-evidence.mjs +102 -17
  43. package/scripts/github/list-review-threads.mjs +277 -0
  44. package/scripts/github/post-gate-findings.mjs +6 -3
  45. package/scripts/github/request-copilot-review.mjs +92 -26
  46. package/scripts/github/upsert-checkpoint-verdict.mjs +74 -5
  47. package/scripts/github/verify-briefing-prefixes.mjs +191 -0
  48. package/scripts/github/verify-fresh-review-context.mjs +78 -3
  49. package/scripts/github/wait-pr-checks.mjs +171 -0
  50. package/scripts/github/write-gate-context.mjs +213 -7
  51. package/scripts/github/write-gate-findings-log.mjs +54 -2
  52. package/scripts/loop/copilot-pr-handoff.mjs +29 -2
  53. package/scripts/loop/detect-change-scope.mjs +2 -2
  54. package/scripts/loop/detect-copilot-loop-state.mjs +23 -3
  55. package/scripts/loop/detect-pr-gate-coordination-state.mjs +35 -3
  56. package/scripts/loop/resolve-dev-loop-startup.mjs +145 -8
  57. package/scripts/loop/sanctioned-commands.mjs +2 -0
  58. package/scripts/loop/validate-pr-body-spec.mjs +21 -5
  59. package/scripts/pages/build-state-atlas.mjs +50 -48
  60. package/scripts/projects/_resolve-project.mjs +1 -148
  61. package/scripts/projects/list-queue-items.mjs +3 -377
  62. package/scripts/projects/move-queue-item.mjs +3 -410
  63. package/scripts/projects/reorder-queue-item.mjs +3 -22
  64. package/skills/copilot-pr-followup/SKILL.md +37 -23
  65. package/skills/dev-loop/SKILL.md +1 -1
  66. package/skills/docs/acceptance-criteria-verification.md +3 -2
  67. package/skills/docs/anti-patterns.md +3 -2
  68. package/skills/docs/artifact-authority-contract.md +12 -11
  69. package/skills/docs/confirmation-rules.md +1 -0
  70. package/skills/docs/contract-style-guide.md +37 -0
  71. package/skills/docs/copilot-loop-operations.md +18 -14
  72. package/skills/docs/cross-harness-regression-contract.md +2 -2
  73. package/skills/docs/debt-remediation-contract.md +1 -1
  74. package/skills/docs/epic-tree-refinement-procedure.md +25 -22
  75. package/skills/docs/issue-intake-procedure.md +25 -19
  76. package/skills/docs/merge-preconditions.md +13 -11
  77. package/skills/docs/pr-lifecycle-contract.md +44 -35
  78. package/skills/docs/public-dev-loop-contract.md +59 -43
  79. package/skills/docs/required-rules.json +149 -0
  80. package/skills/docs/retrospective-checkpoint-contract.md +18 -5
  81. package/skills/docs/spike-mode-contract.md +14 -7
  82. package/skills/docs/stop-conditions.md +30 -15
  83. package/skills/docs/tracker-first-loop-state.md +13 -8
  84. package/skills/docs/validation-policy.md +4 -5
  85. package/skills/local-implementation/SKILL.md +48 -142
  86. package/scripts/docs/validate-no-duplicate-rules.mjs +0 -250
@@ -71,6 +71,8 @@ Max watch timeout: **30 minutes** (from `policy-constants.mjs` COPILOT_REVIEW_WA
71
71
  ```sh
72
72
  node <resolved-skill-scripts>/github/request-copilot-review.mjs --help
73
73
  node <resolved-skill-scripts>/github/probe-copilot-review.mjs --help
74
+ node <resolved-skill-scripts>/github/list-review-threads.mjs --help
75
+ node <resolved-skill-scripts>/github/wait-pr-checks.mjs --help
74
76
  node <resolved-skill-scripts>/loop/detect-copilot-loop-state.mjs --help
75
77
  ```
76
78
 
@@ -94,7 +96,7 @@ Verify all material claims against source, tests, configuration, and CI.
94
96
  When this skill refers to helper paths such as `scripts/...` or `docs/...`, resolve them from the actual skill installation layout you are running, not from the active target repository checkout.
95
97
 
96
98
  Use this rule:
97
- - if the skill is installed as a normalized standalone copy, the required bundled contract docs live under the shared `../docs/` directory next to the installed skill directories; do not assume helper scripts are bundled unless that installed layout actually contains them
99
+ - if the skill is installed as a normalized standalone copy, the required bundled contract docs live under the shared `../docs/` directory next to the installed skill directories. <!-- rule: ASSET-PATH-INSTALLED-NO-ASSUME --> `ASSET-PATH-INSTALLED-NO-ASSUME`: Agents MUST NOT assume helper scripts are bundled unless that installed layout actually contains them.
98
100
  - if you are working in the `dev-loops` source repository, this skill file lives under `skills/copilot-pr-followup/`, so source-repo helper scripts live two levels up at `../../scripts/`, while required bundled contract docs live one level up at `../docs/`
99
101
  - when in doubt, resolve helper paths relative to this [skill file](./SKILL.md) first, then verify the target file exists before running it
100
102
 
@@ -105,7 +107,7 @@ Required bundled runtime contract docs for installed copies of this skill:
105
107
  - [Copilot Loop Operations](../docs/copilot-loop-operations.md)
106
108
 
107
109
  Read those bundled `../docs/` files from the installed skill layout instead of assuming the source repository checkout is present. If any required bundled contract doc is missing from the installed skill layout, treat that as a packaging/installer bug.
108
- Do not assume `scripts/...` is repo-local to the target codebase you are operating on.
110
+ <!-- rule: ASSET-PATH-SOURCE-NO-REPO-LOCAL --> `ASSET-PATH-SOURCE-NO-REPO-LOCAL`: Agents MUST NOT assume `scripts/...` is repo-local to the target codebase they are operating on.
109
111
 
110
112
  ## Authority and safety rules
111
113
 
@@ -123,16 +125,19 @@ Inspect: PR body/title (must satisfy [PR description contract](../docs/copilot-l
123
125
 
124
126
  At the issue-assignment seam, use `detect-initial-copilot-pr-state.mjs` and keep waiting when `waiting_for_initial_copilot_implementation`.
125
127
 
126
- When confirming whether Copilot is requested as a reviewer, do not rely solely on `gh pr view --json reviewRequests`. Use `request-copilot-review.mjs` (see [Operational cookbook](#operational-cookbook)). Do **not** request Copilot by posting literal `/copilot` or `/copilot re-review` PR comments. After draft→ready or fix push, explicitly decide whether another pass is desired; if yes, ensure green/credibly green posture first.
128
+ <!-- rule: COPILOT-FOLLOWUP-REQUEST-HELPER-ONLY -->
129
+ `COPILOT-FOLLOWUP-REQUEST-HELPER-ONLY`: Copilot review requests MUST go through `request-copilot-review.mjs` (see [Operational cookbook](#operational-cookbook)); the agent MUST NOT request Copilot by posting literal `/copilot` or `/copilot re-review` PR comments, and MUST NOT rely solely on `gh pr view --json reviewRequests` to confirm a request. After draft→ready or fix push, explicitly decide whether another pass is desired; if yes, ensure green/credibly green posture first.
127
130
 
128
- Branch on the `request-copilot-review.mjs` machine-readable result:
131
+ <!-- rule: COPILOT-FOLLOWUP-REQUEST-BRANCHING -->
132
+ `COPILOT-FOLLOWUP-REQUEST-BRANCHING`: The agent MUST branch on the `request-copilot-review.mjs` machine-readable result exactly as follows, and MUST NOT treat an attempted request as equivalent to a confirmed request:
129
133
  - `requested`: if another Copilot pass is actually desired, immediately re-baseline with `detect-copilot-loop-state.mjs` and follow its `nextAction` (enter persistent wait only through `dev-loops loop watch-cycle` or `gh run watch`)
130
134
  - `already-requested`: apply the same detector-first rebasing and wait branching as `requested`
131
135
  - `suppressed_same_head_clean`: report clean-converged state and stop unless `--force-rerequest-review` bypass is intentionally authorized
132
136
  - `unavailable`: report the limitation and stop
137
+ - `blocked_by_copilot_comment`: no request was placed. Delete the violating `violationCommentIds` (or confirm they only quote the rule inside a code span/fenced block, which does not arm the guard) and re-run; do NOT treat this as a placed request and do NOT enter a wait seam
133
138
  - non-zero / unexpected failure: stop and report error
134
139
 
135
- Do not treat an attempted request as equivalent to a confirmed request.
140
+ Branch on `status`, never on `ok`/exit-code truthiness alone: `ok: true` means the helper ran without error, not that a review was placed. Under `--silent`, the exit code is 0 only for `requested`; every other status (including `blocked_by_copilot_comment`) exits non-zero.
136
141
 
137
142
  ### Re-attachment guard (check for existing loop state first)
138
143
 
@@ -166,7 +171,8 @@ The outer-loop checkpoint is the canonical re-attachment artifact.
166
171
 
167
172
  Start every wait seam with a detector refresh: `detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>`.
168
173
 
169
- Allowed wait tools: `detect-copilot-loop-state.mjs` (one-shot), `dev-loops loop watch-cycle` (persistent), `copilot-pr-handoff.mjs --watch-status` (refresh after timeout/idle), `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic CI: CircleCI / Actions / external commit-status), `gh run watch <run-id> --repo <owner/name>` (Actions-only fallback when the run id is known). Otherwise exit and resume later from fresh detector call.
174
+ <!-- rule: COPILOT-FOLLOWUP-WAIT-TOOLS -->
175
+ `COPILOT-FOLLOWUP-WAIT-TOOLS`: The agent MUST wait only through allowed deterministic tools: `detect-copilot-loop-state.mjs` (one-shot), `dev-loops loop watch-cycle` (persistent), `copilot-pr-handoff.mjs --watch-status` (refresh after timeout/idle), `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic CI: CircleCI / Actions / external commit-status), `scripts/github/wait-pr-checks.mjs --repo <owner/name> --pr <number>` (same provider-agnostic CI wait, with a direct 0/1/2 process-exit-code contract for shell/scripted callers instead of a JSON `status` field), `gh run watch <run-id> --repo <owner/name>` (Actions-only fallback when the run id is known); otherwise exit and resume later from a fresh detector call.
170
176
 
171
177
  Practical rules: do not poll manually. `waiting_for_copilot_review` → `run-watch-cycle.mjs` or report-and-resume. `waiting_for_ci` with pending/none CI → `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic; covers CircleCI / Actions / external commit-status) or report-and-resume; `gh run watch <run-id>` is an Actions-only fallback. `dev-loops loop watch-cycle` also auto-routes a `waiting_for_ci` boundary to this CI watcher. Bounded CI exception: zero current-head suites + previous-head green + local `npm run verify` passed → rerun detector with `--local-validation-head-sha` for `crediblyGreen` promotion. `ciStatus=failure` → stop/fix, never wait.
172
178
 
@@ -181,6 +187,7 @@ Preferred approach:
181
187
  <!-- /pi-only -->
182
188
  - dispatch fix findings to the `fixer` agent; do not run inline fix passes in-watcher
183
189
  - do not report completion while unresolved Copilot feedback remains
190
+ - once a watch/probe settles, do not parse its raw output: re-read via `detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>` and `list-review-threads.mjs --repo <owner/name> --pr <number> --unresolved-only` — inline interpreters are barred by `OPS-NO-INLINE-INTERPRETER` in [Copilot loop operations](../docs/copilot-loop-operations.md)
184
191
 
185
192
  ### Canonical async dispatch wording
186
193
 
@@ -209,6 +216,7 @@ This step covers four responsibilities: the draft gate right before `gh pr ready
209
216
  When unresolved feedback exists, use a narrow follow-up loop:
210
217
 
211
218
  1. inspect unresolved comments/threads and failing checks
219
+ - enumerate unresolved threads (with the thread/comment ids the reply-resolve helpers below need) via `scripts/github/list-review-threads.mjs --repo <owner/name> --pr <number> --unresolved-only` rather than a hand-written `gh api graphql` query
212
220
  2. before the first local file write in each fixer pass on a Copilot-assigned PR, run `node <resolved-skill-scripts>/loop/pre-write-remote-freshness-guard.mjs --branch <headRefName>` as a required fail-closed guard
213
221
  - source `<headRefName>` from authoritative PR state (`headRefName`), not from a local branch guess
214
222
  - if the guard exits non-zero (`remote_ahead`), stop writing locally, reconcile to the refreshed remote head, then restart the fixer pass
@@ -222,7 +230,7 @@ When unresolved feedback exists, use a narrow follow-up loop:
222
230
  - source `<headRefName>` from authoritative PR state (`headRefName`), not from a local branch guess
223
231
  - if the guard exits non-zero (`branch_mismatch`), stop and realign to the expected branch before staging or committing
224
232
  7. if files changed, push the resolving commit before any thread reply claims the fix is present
225
- 8. when a comment or thread is actually addressed, reply on GitHub with a short resolution note that references the resolving commit SHA or commit URL when applicable
233
+ 8. <!-- rule: COPILOT-FOLLOWUP-REPLY-RESOLVE-HELPER --> `COPILOT-FOLLOWUP-REPLY-RESOLVE-HELPER`: When a comment or thread is actually addressed, the agent MUST reply on GitHub with a short resolution note that references the resolving commit SHA or commit URL when applicable, using the deterministic helpers below rather than ad hoc thread mutations:
226
234
  - for one thread, must use the deterministic helper `reply-resolve-review-thread.mjs` from the resolved skill scripts directory
227
235
  - when the same bounded resolution note applies to multiple matching unresolved threads, use `reply-resolve-review-threads.mjs` instead of ad hoc inline `gh api` / `gh api graphql` mutations
228
236
  - when using the single-thread helper, pair `--comment-id` and `--thread-id` from the same fresh PR thread snapshot rather than mixing ids across review rounds
@@ -239,8 +247,9 @@ When unresolved feedback exists, use a narrow follow-up loop:
239
247
  - do not stop at a local fix if GitHub-side reply/resolve is authorized
240
248
  11. after completing reply/resolve for a pass, verify zero unresolved threads remain via `dev-loops gate capture-threads` before proceeding
241
249
  - if the refreshed snapshot reports unresolved threads, re-enter the reply/resolve loop for the missed threads
242
- 12. only after GitHub-side reply/resolve work is done for the addressed threads and the refreshed thread snapshot proves zero unresolved threads remain, decide whether another Copilot pass is desired
243
- - resolve the review-round cap from config via `resolveRefinementConfig(config, "maxCopilotRounds")` from `@dev-loops/core/config`; default config ships `maxCopilotRounds: 5`
250
+ 12. <!-- rule: COPILOT-FOLLOWUP-ROUND-CAP --> `COPILOT-FOLLOWUP-ROUND-CAP`: The agent MUST decide whether another Copilot pass is desired, applying the round-cap/signal-gating rules below, only after GitHub-side reply/resolve work is done for the addressed threads and the refreshed thread snapshot proves zero unresolved threads remain
251
+ - resolve the review-round cap from config via `resolveRefinementConfig(config, "maxCopilotRounds")` from `@dev-loops/core/config`; default config ships `maxCopilotRounds: 5`. For a light-dispatched PR, resolve `resolveEffectiveCopilotRoundCap(config, { lightweight: true })` instead — `min(localImplementation.lightMode.maxCopilotRounds ?? 1, maxCopilotRounds)` (default lightweight cap: 1) — see the [Artifact Authority Contract](../docs/artifact-authority-contract.md) lightweight section (issue #1210)
252
+ - for a light-dispatched PR, pass `--lightweight` on every round-cap-consuming helper invocation — `detect-copilot-loop-state.mjs`, `copilot-pr-handoff.mjs`, `detect-pr-gate-coordination-state.mjs`, `request-copilot-review.mjs`, and `upsert-checkpoint-verdict.mjs` — otherwise those tools resolve the full-PR cap and the composed lightweight cap is never enforced
244
253
  - **Opt out entirely:** `maxCopilotRounds: 0` disables the external Copilot review gate for the repo — the loop runs `draft_gate → pre_approval_gate` with the local harness only, never requesting or waiting on Copilot. Use this when the repo has no Copilot reviewer configured or prefers local-harness-only review.
245
254
  - use the completed Copilot review-round count from `detect-copilot-loop-state.mjs` / `copilot-pr-handoff.mjs` as the current PR's review-round count
246
255
  - if completed review rounds have reached the maximum (default: 5), do **not** re-request Copilot review within that concluded cycle
@@ -263,7 +272,10 @@ Do not treat `fix applied locally` as the end of the loop when the workflow also
263
272
 
264
273
  ### Mandatory gate-comment command contract
265
274
 
266
- For every `draft_gate` or `pre_approval_gate` comment, you MUST run:
275
+ <!-- rule: COPILOT-FOLLOWUP-GATE-COMMENT-CANONICAL -->
276
+ `COPILOT-FOLLOWUP-GATE-COMMENT-CANONICAL`: For every `draft_gate` or `pre_approval_gate` comment, agents MUST run `upsert-checkpoint-verdict.mjs` and MUST NOT use `gh pr comment`, `gh api`, or `gh pr review` for gate comments.
277
+
278
+ Per `COPILOT-FOLLOWUP-GATE-COMMENT-CANONICAL` above, run:
267
279
 
268
280
  For a gate that ran via the fan-out/fan-in sub-loop, pass the structured per-angle review results via `--findings-json` (NOT the wall-of-text `--findings-summary`). The operator/loop writes that JSON file from the collected per-angle results — the same per-angle `{angle, verdict, findings}` objects the fan-out reviewers wrote to `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json` that feed `consolidateFanin`. The helper renders a readable per-angle breakdown and derives the single-line `**Findings summary:**` digest itself:
269
281
 
@@ -297,19 +309,19 @@ node <resolved-skill-scripts>/github/upsert-checkpoint-verdict.mjs \
297
309
 
298
310
  `--execution-mode <fanout_fanin|inline_single_agent>` records how the gate review ran (default `inline_single_agent`). When the gate did not run via the fan-out/fan-in sub-loop ([Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md)), you MUST pass `--execution-mode inline_single_agent --inline-reason "<why>"` — silent inline runs are no longer allowed: inline mode requires a non-empty `--inline-reason` and emits a stderr warning. Because inline is the default mode, a bare call with neither flag now fails with an argument error, so always pass `--execution-mode` explicitly (and `--inline-reason` for inline). The recorded `executionMode` is surfaced by `detect-checkpoint-evidence.mjs` and gated by `gates.requireFanoutEvidence`.
299
311
 
300
- Do NOT use `gh pr comment`, `gh api`, or `gh pr review` for gate comments.
301
-
302
312
  `--force --force-reason` on `upsert-checkpoint-verdict.mjs` is a narrow operator-authorized CI override for the helper itself, not the default gate path. Use it only when the helper refuses gate entry solely because the current head is `blocked_needs_user_decision` with `ciStatus="failure"`, and only after the user explicitly authorizes ignoring that current-head CI failure for this one gate-comment upsert. It does **not** bypass stale-head checks, unresolved-thread / unsettled-review refusal, non-draft `draft_gate` refusal, merge conflicts, or other legality checks.
303
313
 
304
314
  ### Gate fan-out/fan-in procedure (agent-orchestrated)
305
315
 
306
- Both gates run this same checkpoint review chain. It is an **agent-orchestrated skill procedure** — a node script cannot spawn the per-angle reviewers, so the conductor agent drives the fan-out and uses the pure `@dev-loops/core/loop/gate-fanin` helpers only for consolidation, batching, and ledger mapping. The cost model is **build once, seed many**: the context-builder script builds ONE deterministic, neutral context bundle (diff + adjacent code) and each independent reviewer is seeded with that identical bundle verbatim instead of re-deriving it (work-dedup; no fork primitive, no Workflow tool). See [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md).
316
+ Both gates run this same checkpoint review chain, owned end-to-end by [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md) (`GATE-EXEC-BUILD-ONCE-SEED`, `GATE-EXEC-BRIEFING-PREFIX`, `GATE-EXEC-SEPARATE-CHAINS`, `GATE-EXEC-POST-BEFORE-FIX`, `GATE-EXEC-REGATE-MANDATORY`, `GATE-EXEC-LIGHT-ESCALATION`); this section owns only this skill's dispatch of that chain. It is an **agent-orchestrated skill procedure** — a node script cannot spawn the per-angle reviewers, so the conductor agent drives the fan-out and uses the pure `@dev-loops/core/loop/gate-fanin` helpers only for consolidation, batching, and ledger mapping.
307
317
 
308
- 1. **Context (Phase 1) — build the neutral bundle ONCE:** build/read the gate-context artifact via `scripts/github/write-gate-context.mjs` (`buildGateContext` resolves the angle set through `resolveGateAnglesDynamic`; mandatory angles always survive). The artifact at `tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.json` carries the resolved angles + change scope + acceptance-criteria pointer. When the resolver was handed a diff with `diffOutput`, `buildGateContext` also persists the FULL diff to `tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.diff` and records `scope.diffPath` (relative) plus `scope.changedFiles` (full repo-relative paths). It ADDITIONALLY builds a deterministic, neutral `adjacentCode` bundle — each changed file plus its 1-hop import in/out-edges (callers/callees/imports), with size guards (skip lockfiles/generated/binary/minified; cap per-file bytes; truncate the long tail) recorded in a `stripped`/`truncated`/`missing` manifest. This single bundle is the build-once seed; reviewers do not re-derive the diff + adjacent code.
309
- 2. **Fan-out (Phase 2) — seed each reviewer with the bundle verbatim:** plan batches with `planFanoutBatches(angles, cap)` using the `gates.maxFanoutReviewers` cap (default 8). Spawn one scoped `review` agent per resolved angle (plain Agent tool), parallel up to the cap, batching any overflow sequentially. When `degraded` is true, record the sequential degradation in the gate evidence. Reviewers are independent: each is seeded with the IDENTICAL neutral bundle (the gate-context artifact's `scope.diffPath` + `adjacentCode`) verbatim plus its single angle, and never inherits the conductor agent's conversation or opinions. Each reviewer follows the [review agent scoped angle-review mode](../../agents/review.agent.md): fresh context, single angle, read-only, writing its per-angle findings artifact to `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json`. Brief each reviewer to use the provided bundle as its base and to review like an external code reviewer hunting real bugs: read the FULL diff (from `scope.diffPath`, or `git diff` when null) and the bundled adjacent code (callers, callees, imports) rather than re-deriving them, then review adversarially for concrete defects (edge cases, input validation, numeric coercion incl. NaN/Infinity/floats/negatives, null/undefined, boundary conditions, mismatched caller/callee contracts, dedup/identity bugs) with `file:line` + the failing scenario — not process nits like "no test exists". Reviewers are read-only and MAY widen scope (open adjacent repo files beyond the bundle) only when their angle genuinely needs more; that widening is recorded in the optional `contextWidened` field on each per-angle findings artifact.
310
- 3. **Fan-in (Phase 3):** consolidate the per-angle artifacts via `consolidateFanin({ angleResults, blockCleanOnFindingSeverities })` (blocking severities from `resolveGateConfig(config, <configKey>).blockCleanOnFindingSeverities`, where `<configKey>` is the config key for the gate — `draft` for `draft_gate`, `preApproval` for `pre_approval_gate` — not the `draft_gate|pre_approval_gate` artifact name). It yields the gate `verdict` (`clean` when no blocking-severity finding remains, `findings_present` when one does, `blocked` when any per-angle artifact is malformed/missing). Map consolidated findings with `toFindingsLogShape(...)` and write the disposition ledger via `write-gate-findings-log.mjs` **before** the visible comment. The ledger is written regardless of any comment opt-out. Then, when `resolveGatePostFindingsComments(config)` is true (default; opt-out via `gates.postFindingsComments: false`), post the consolidated findings as a visible, marker-tagged PR comment via `node <resolved-skill-scripts>/github/post-gate-findings.mjs --repo <owner/name> --pr <number> --gate <gate> --head-sha <current_head_sha> --findings '<toFindingsLogShape JSON>'` so the findings are auditable and Copilot/humans are aware of them. The helper is idempotent per gate (exactly one comment per gate, updated in place on each run; the reviewed head is shown in the body) and posts a brief "no findings" note when the consolidated set is empty. When `postFindingsComments` is false the helper no-ops with a `skipped` result and you skip this post step; the ledger still records the findings.
311
- 4. **Verdict (Phase 4):** post the verdict with the [Gate comment command](#mandatory-gate-comment-command-contract) using `--execution-mode fanout_fanin` and `--findings-json <path>`. Write that JSON from the collected per-angle results (the same per-angle `{angle, verdict, findings}` artifacts at `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json` that fed `consolidateFanin` in Phase 3; the flat `toFindingsLogShape` output is also accepted and grouped by `.angle`) so the verdict comment renders the structured per-angle breakdown instead of a wall-of-text summary. Do NOT use `--findings-summary` for a fan-out gate that is the inline_single_agent fallback.
312
- 5. **Retry (Phase 5):** on blocking findings, drive each internal fan-out finding through the SAME fix reply-with-resolving-commit resolve loop used for external Copilot review comments (Step 7 above): fix on the branch, push the resolving commit, then re-run only the `findings_present` angles from the previous pass (the context-builder and fan-in always re-run); repeat until the consolidated verdict is `clean` for the current head SHA. Internal fan-out findings and external review comments are handled identically by that loop.
318
+ 1. **Context (Phase 1):** build/read the gate-context artifact via `scripts/github/write-gate-context.mjs` (`buildGateContext` resolves the angle set through `resolveGateAnglesDynamic`; mandatory angles always survive). Consume the resulting `tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.json` artifact: the FULL diff at `scope.diffPath` plus the top-level adjacent-code bundle (`adjacentCode`) are the build-once seed every reviewer uses verbatim see `GATE-EXEC-BUILD-ONCE-SEED` for the bundle contract; do not re-derive them per reviewer.
319
+ 2. **Fan-out (Phase 2):** plan batches with `planFanoutBatches(angles, cap)` using the `gates.maxFanoutReviewers` cap (default 8); spawn one scoped `review` agent per resolved angle (plain Agent tool), parallel up to the cap, sequential for overflow (record any degradation in the gate evidence). Each reviewer follows [review agent scoped angle-review mode](../../agents/review.agent.md): fresh context, single angle, read-only, writing its per-angle findings artifact to `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json`.
320
+ <!-- rule: COPILOT-FOLLOWUP-ADVERSARIAL-BRIEFING -->
321
+ `COPILOT-FOLLOWUP-ADVERSARIAL-BRIEFING`: Each reviewer MUST be briefed to review like an external code reviewer hunting real bugs: read the FULL diff (from `scope.diffPath`, or `git diff` when null) and the bundled adjacent code (callers, callees, imports) rather than re-deriving them, then review adversarially for concrete defects (edge cases, input validation, numeric coercion incl. NaN/Infinity/floats/negatives, null/undefined, boundary conditions, mismatched caller/callee contracts, dedup/identity bugs) with `file:line` + the failing scenario not process nits like "no test exists". Reviewers MAY widen scope (open adjacent repo files beyond the bundle) only when their angle genuinely needs more, recording that in the optional `contextWidened` field on their findings artifact. The LAYOUT of this briefing (invariant block first, this adversarial angle prompt last) and the `--prefix-hash`/`--prefix-file` sentinel recording are owned by `GATE-EXEC-BRIEFING-PREFIX` not restated here.
322
+ 3. **Fan-in (Phase 3):** before consolidating, run `scripts/github/verify-briefing-prefixes.mjs --head-sha <current_head_sha>` and stop the pass on a fail-closed result (`GATE-EXEC-BRIEFING-PREFIX` owns this check). Then consolidate via `consolidateFanin({ angleResults, blockCleanOnFindingSeverities })` (blocking severities from `resolveGateConfig(config, <configKey>).blockCleanOnFindingSeverities` `draft` or `preApproval`, not the `<gate>` artifact name). Map with `toFindingsLogShape(...)` and write the disposition ledger via `write-gate-findings-log.mjs` before the visible comment (`GATE-EXEC-DISPOSITION-LEDGER`, `GATE-EXEC-POST-BEFORE-FIX` own that ordering and the ledger's opt-out-proof durability). When `resolveGatePostFindingsComments(config)` is true (default), post via `node <resolved-skill-scripts>/github/post-gate-findings.mjs --repo <owner/name> --pr <number> --gate <gate> --head-sha <current_head_sha> --findings '<toFindingsLogShape JSON>'`.
323
+ 4. **Verdict (Phase 4):** post via the [Gate comment command](#mandatory-gate-comment-command-contract) using `--execution-mode fanout_fanin` and `--findings-json <path>` built from the same per-angle artifacts consolidated in Phase 3 — that flag's accepted shapes are owned by the Gate comment command section above; do not restate them here.
324
+ 5. **Retry (Phase 5):** on blocking findings, drive each internal fan-out finding through the SAME fix → reply-with-resolving-commit → resolve loop used for external Copilot review comments (Step 7 above); re-run only the `findings_present` angles from the previous pass (context-builder and fan-in always re-run); repeat until the consolidated verdict is `clean` for the current head SHA (`GATE-EXEC-REGATE-MANDATORY`).
313
325
 
314
326
  ### Draft gate contract (before marking PR ready for review)
315
327
 
@@ -324,8 +336,8 @@ The canonical checkpoint verdict comment contract is [Gate Review Comment Contra
324
336
  - **Pass criteria:** all configured draft gate angles pass; all findings at severities in `blockCleanOnFindingSeverities` are addressed; validation passes; no unrelated files are included.
325
337
  - **Next step after passing:** mark the PR ready for review.
326
338
  - **Board status sync (built-in, after ready-for-review):** the In-Progress board move is now performed automatically as a deterministic tail of `ready-for-review.mjs` — marking the PR ready couples the board move to the ready transition (#1069), so no separate `sync-item-status` step is needed. It stays best-effort and NON-FATAL: it uses local `gh` auth (no CI/PAT), exits 0 when the board is not configured / the item is not on the board / the API fails, and never blocks marking the PR ready.
327
- - **Non-substitution rule:** a clean `draft_gate` comment only authorizes the draft → ready-for-review transition for that head SHA. It does **not** satisfy `pre_approval_gate`, final-approval readiness, or merge-ready requirements.
328
- - **Required PR comment:** post a visible checkpoint verdict comment using the mandatory [Gate comment command](#mandatory-gate-comment-command-contract). Keep validation reporting concise: include command names with pass/fail status. Do **not** paste raw passing test output into the visible gate comment. If you include a failing validation excerpt, keep it focused and truncate it to a deterministic retained-prefix length before posting the comment. See [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). Do not run `gh pr ready` unless a visible `clean` `draft_gate` checkpoint verdict comment exists for the current head SHA. A checkpoint verdict comment for an older head SHA does not satisfy this requirement for the current head. If findings exist, the PR stays draft and needs fixes before retry. If fixes advance the head SHA while still draft, post a new checkpoint verdict comment for the new head. If the checkpoint verdict comment cannot be posted, fail closed and do not run `gh pr ready`.
339
+ - **Non-substitution rule:** a clean `draft_gate` comment only authorizes the draft → ready-for-review transition for that head SHA; cross-gate non-substitution is owned by `GATE-COMMENT-NON-SUBSTITUTION` in [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). This skill does not restate that rule.
340
+ - **Required PR comment:** post a visible checkpoint verdict comment using the mandatory [Gate comment command](#mandatory-gate-comment-command-contract). Comment field content and validation-reporting format are owned by `GATE-COMMENT-VALIDATION-REPORTING`; the draft-boundary comment requirement is owned by `GATE-COMMENT-DRAFT-REQUIREMENTS`; posting-failure fail-closed behavior is owned by `GATE-COMMENT-FAIL-CLOSED` all in [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). This skill does not restate those field/format/fail-closed rules.
329
341
 
330
342
  ### Pre-approval gate contract
331
343
 
@@ -336,11 +348,12 @@ This is the default pre-approval gate for this workflow boundary. The canonical
336
348
  - **Execution directive:** run the [Gate fan-out/fan-in procedure](#gate-fan-outfan-in-procedure-agent-orchestrated) (the agent-orchestrated chain defined in [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md)) with the pre-approval gate inspection angles resolved from config. The `acceptance-criteria` angle is mandatory for this gate (see `.devloops` `gates.preApproval.mandatoryAngles`) and always survives dynamic resolution. Retry rule: in subsequent cycles, only re-run reviewers that produced `findings_present` in the previous pass.
337
349
  - **Review angles:** resolved at runtime from config via `resolveGateAngles(config, "preApproval")` from `@dev-loops/core/config`. Default config enables all configured pre-approval gate angle families; consumer repos may opt out individual angles via `excludeAngles`.
338
350
  - **Persona mapping:** each angle resolves to a reviewer persona via `resolveReviewerRole(config, angle)` from `@dev-loops/core/config`. Include this prompt in each reviewer's briefing so the reviewer knows exactly what to look for.
339
- - **Pass criteria:** the sub-loop completes with verdict `clean`; all configured angles pass; if parallel execution is impractical, still run all configured lenses and explicitly record the limitation.
351
+ - **Pass criteria:** the sub-loop completes with verdict `clean`; all configured angles pass, following the sequential-fallback rule owned by `GATE-EXEC-FANOUT-SEQUENTIAL-FALLBACK` in [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md).
340
352
  - **Acceptance criteria verification:** follow the canonical procedure in [Acceptance Criteria Verification](../docs/acceptance-criteria-verification.md) before posting the `pre_approval_gate` comment. After a clean verification this also ticks the verified PR-body checkboxes via `scripts/github/tick-verified-checkboxes.mjs`, so the merged PR shows checked AC.
341
353
  - **Next step after passing:** continue the Step 7 flow and then proceed to the human approval checkpoint below.
342
- - **Non-substitution rule:** a clean `pre_approval_gate` comment is separate from `draft_gate` evidence. It governs final-approval readiness for that head SHA; it does **not** replace the required `draft_gate` evidence for leaving draft.
343
- - **Required PR comment:** post a visible checkpoint verdict comment using the mandatory [Gate comment command](#mandatory-gate-comment-command-contract). Keep validation reporting concise: include command names with pass/fail status. Do **not** paste raw passing test output into the visible gate comment. If you include a failing validation excerpt, keep it focused and truncate it to a deterministic retained-prefix length before posting the comment. Do not declare final-approval readiness unless a visible `clean` `pre_approval_gate` checkpoint verdict comment exists for the current head SHA. Final-approval readiness must not rely only on local or hidden artifacts; the visible PR comment is the required auditable evidence. If the checkpoint verdict comment cannot be posted, fail closed and do not declare final-approval readiness. A checkpoint verdict comment for an older head SHA does not satisfy this requirement for the current head. If findings exist, follow-up fixes are required before final approval. The `pre_approval_gate` procedure must be entered and completed (visible comment posted) before any merge-ready or approval-ready declaration. Skipping the gate is not recoverable by asserting convergence. If fixes advance the head SHA, post a new checkpoint verdict comment for the new head.
354
+ - **Non-substitution rule:** a clean `pre_approval_gate` comment governs final-approval readiness for that head SHA and is separate from `draft_gate` evidence; cross-gate non-substitution is owned by `GATE-COMMENT-NON-SUBSTITUTION` in [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). This skill does not restate that rule.
355
+ - **Required PR comment:** post a visible checkpoint verdict comment using the mandatory [Gate comment command](#mandatory-gate-comment-command-contract). Comment field content and validation-reporting format are owned by `GATE-COMMENT-VALIDATION-REPORTING`; the pre-approval-boundary comment requirement is owned by `GATE-COMMENT-PREAPPROVAL-REQUIREMENTS`; posting-failure fail-closed behavior is owned by `GATE-COMMENT-FAIL-CLOSED` all in [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). This skill does not restate those field/format/fail-closed rules.
356
+ - <!-- rule: GATE-SKIP-NOT-RECOVERABLE-BY-CONVERGENCE --> `GATE-SKIP-NOT-RECOVERABLE-BY-CONVERGENCE`: Skipping the gate MUST NOT be treated as recoverable by asserting convergence.
344
357
 
345
358
  ### Conflict-resolution gate
346
359
 
@@ -446,6 +459,7 @@ Follow [Stop Conditions](../docs/stop-conditions.md). Genuine stops: `blocked` s
446
459
 
447
460
  See [Anti-patterns](../docs/anti-patterns.md). Key repo-specific additions:
448
461
  - Use `reply-resolve-review-thread.mjs` / `reply-resolve-review-threads.mjs` helpers instead of ad hoc `gh api`/`gh api graphql` thread-mutation commands. Do NOT use `gh pr comment`, `gh api`, or `gh pr review` for gate comments (use `upsert-checkpoint-verdict.mjs`).
462
+ - Use `list-review-threads.mjs` and `wait-pr-checks.mjs` instead of ad hoc `gh api graphql` review-thread queries or `gh pr checks` shell-pipe polling loops.
449
463
  - Do not declare merge-ready without visible `pre_approval_gate` comment on current head SHA. Do not declare merge-ready based solely on `mergeable_state: clean` + CI green without gate evidence. CI green + resolved threads alone is insufficient.
450
464
  - Do not blind-run `gh pr merge`/`gh pr update-branch`/unapproved rebase when conflicted. Do not dispatch async dev-loop tasks that omit the pre-approval gate requirement.
451
465
  - Do not assume generated wiki is authoritative over code or CI.
@@ -130,7 +130,7 @@ Info/handoff requests can be served directly via `node <dev-loops-package-root>/
130
130
  When you need a fact from a dev-loops JSON-emitting script, climb this ladder and stop at the first rung that answers the question — never read more output than you need:
131
131
 
132
132
  1. **Prefer the dev-loops subcommand / concise mode.** Use `loop info` or a script's `--concise`/`--summary` mode (e.g. `run-watch-cycle.mjs --concise`, `probe-copilot-review.mjs --concise`) for a human-readable digest. The concise modes surface loop state, Copilot round count, unresolved/actionable thread counts, round-cap-clean eligibility, CI status, next action, and the current round's new Copilot comment bodies.
133
- 2. **`--silent` / `-s` for a yes/no check.** Reads ZERO output: `… --jq '<predicate>' --silent; echo $?` exits `0` for true / `1` for false. Without `--jq`, `--silent` maps the script's success (`ok:true`) to exit `0`, failure to `1`. Example: `probe-copilot-review.mjs --repo o/r --pr N --jq '.status=="idle"' -s`.
133
+ 2. **`--silent` / `-s` for a yes/no check.** Reads ZERO output: `… --jq '<predicate>' --silent; echo $?` exits `0` for true / `1` for false. Without `--jq`, `--silent` maps the script's success (`ok:true`) to exit `0`, failure to `1` — unless the tool documents a stricter `--silent` contract in its own usage text (e.g. `request-copilot-review.mjs` exits `0` only for `status: "requested"`). Example: `probe-copilot-review.mjs --repo o/r --pr N --jq '.status=="idle"' -s`.
134
134
  3. **`--jq <filter>` to extract a single field.** `--jq`/`--silent` are a BASE-CLI GUARANTEE across every operator-facing JSON-result dev-loops command — each accepts a gh-style `--jq` filter (jq subset: field access, `.[]`/`.[N]`, `|`, `select(...)`, `==`/`!=`/`<`/`<=`/`>`/`>=`, `length`, `keys`), not just a named subset. (A few scripts are out of scope — e.g. build/smoke tooling, dashboard servers, dormant/unwired adapters, and scripts that write JSON to a file rather than stdout — each carried with its reason in the contract test's exclusion list.) It prints only the filtered value. An invalid filter fails closed (stderr + exit `2`), distinct from a clean predicate-false (silent exit `1`). This is enforced by a contract test (`test/contracts/jq-output-base-guarantee-contract.test.mjs`) that fails the build if a new JSON-emitting command ships without wiring the shared `scripts/lib/jq-output.mjs` emit path.
135
135
  4. **Use a dev-loops wrapper for `gh` reads — never an agent-level raw `gh`.** A raw `gh` call is a recorded advisory retro violation (issue #1077: reported, never blocking). If no script covers the read you need, treat it as a tooling gap: file/build a thin wrapper (reuse `scripts/lib/jq-output.mjs`, like the ones below), don't shell out. The reads/edits that already have wrappers:
136
136
  - CI run-log tail (a failing PR's job log) → `scripts/github/fetch-ci-logs.mjs --repo <o/r> --pr <n> [--failed-only] [--tail <n>]`, **never raw `gh run view --log`/`--log-failed`**. (`probe-ci-status.mjs` names the failed checks; this returns the LOG.)
@@ -1,10 +1,11 @@
1
1
  # Acceptance Criteria Verification
2
2
 
3
- Extracted procedure for verifying issue acceptance criteria during the `pre_approval_gate`. Referenced from [Copilot PR Follow-up Skill](../copilot-pr-followup/SKILL.md#pre-approval-gate-contract).
3
+ Canonical owner for the acceptance-criteria verification procedure run during the `pre_approval_gate`. Referenced from [Copilot PR Follow-up Skill](../copilot-pr-followup/SKILL.md#pre-approval-gate-contract).
4
4
 
5
5
  ## Procedure
6
6
 
7
- Before posting the `pre_approval_gate` comment, verify every acceptance criteria checklist item in the issue linked to this PR, and reflect the verified items back into both the linked issue body and the PR body (exception: under the lightweight path — `specSource: pr_body` — the step 5 issue-body mirroring is skipped, since the PR body, not the issue body, is the canonical spec surface; see the lightweight fork below):
7
+ <!-- rule: ACCEPT-CRITERIA-VERIFY-AND-REFLECT -->
8
+ `ACCEPT-CRITERIA-VERIFY-AND-REFLECT`: Before posting the `pre_approval_gate` comment, the agent MUST verify every acceptance criteria checklist item in the issue linked to this PR, and MUST reflect the verified items back into both the linked issue body and the PR body (exception: under the lightweight path — `specSource: pr_body` — the step 5 issue-body mirroring is skipped, since the PR body, not the issue body, is the canonical spec surface; see the lightweight fork below):
8
9
 
9
10
  > **Lightweight (PR-body-as-spec) fork:** when the session is lightweight — the resolver output / handoff envelope carries `specSource: pr_body` (`canonicalSpecSource: pr_body`) — a linked issue still exists (`--lightweight` runs on the `--issue` path, so keep the `Closes #N` linkage and all issue-level tracking), but the **PR body**, not the issue body, is the canonical spec-of-record read for AC/DoD. In that case, still resolve the linked issue for tracking, but read the AC/DoD/invariants from the PR body rather than the issue body (skip step 2's issue-body read for spec purposes). Run `node scripts/loop/validate-pr-body-spec.mjs --repo <owner/name> --pr <pr-number> --expected-issue <issue-number>` (reuses `@dev-loops/core/loop/issue-refinement-artifact`) to confirm the body carries the required invariants — it fails closed with a per-section `missing_*` reason if any is absent, in which case post the gate comment with verdict `blocked`. The PR body MUST carry the `Closes #N` linkage (or GitHub's other closing-keyword forms): `validate-pr-body-spec` fails closed with `missing_closing_issue_reference` without it, and with `closes_wrong_issue` when `--expected-issue` doesn't match. Then continue at step 3, extracting the **Acceptance criteria** checklist items from the PR body itself; step 5's issue-body AC-tick update is unnecessary for lightweight since the issue body is not the spec surface (the PR body is ticked in step 6 as usual). The default issue-backed procedure below is unchanged.
10
11
 
@@ -8,11 +8,12 @@ Canonical owner for anti-pattern guidance across all workflow families.
8
8
  2. **Routing review-only work through local_implementation**: Review-only comparison, synthesis, or consolidation must use `refiner` agent, not `dev-loop` + `local_implementation`.
9
9
  3. **Thin placeholder PR descriptions**: Always include change summary, scope/context, acceptance criteria, definition of done, non-goals, and `Closes #N`.
10
10
  4. **Merging directly to main without PR**: Use PR-based remote loop when practical.
11
- 5. **Duplicate worktree paths**: Check `git worktree list` before creating new worktrees; reuse existing matching worktrees.
12
- 6. **Main-checkout mutation**: Reserve main checkout for inspection/control; use `tmp/worktrees/` paths for mutation work.
11
+ 5. **Duplicate worktree paths**: See `WORKTREE-DEDUPE` in [Worktree usage guidance](../../docs/worktree-guidance.md#coordination-and-collision-checks).
12
+ 6. **Main-checkout mutation**: See `WORKTREE-DEFAULT-USE` in [Worktree usage guidance](../../docs/worktree-guidance.md#default-rule-use-a-worktree-for-mutating-local-work).
13
13
  7. **Spelunking tooling internals instead of using the public surface**: Do not read installed package internals, scan tooling source, or run ad-hoc scripts to understand a tool's behavior. Use the CLI, its `--help` subcommands, and `skills/docs/`. Read tool source only when the task is to inspect or change that tool, or when a concrete failure path is inside it and no public CLI/docs path exists. Once a failure is concrete, search changed files for the exact pattern — don't run duplicate broad searches.
14
14
  8. **Hand-editing the queue file when a board is configured**: When a GitHub Projects board is configured (`queue.projectNumber`/`queue.boardTitle` in `.devloops`), the board is the authoritative queue MEMBERSHIP and ordering source — not just status. Add work via the board (`dev-loops queue add ... --next-up` to land directly in the normative `Next Up` pickup queue; default lands in Backlog, which is unprioritized intake and never auto-picked), not by hand-editing `.pi/dev-loop-queue.json`; the queue runner reconciles board `Next Up` items into queue entries before each run. See the operator guides under `docs/` (queue board usage/setup) for the workflow.
15
15
  9. **Hand-rolling a fan-in wait by polling a subagent transcript or parsing tool JSON with `node -e`/`python3`**: When awaiting fan-out reviewers (gate `draft_gate`/`pre_approval_gate` sub-loops, or any Agent-tool fan-out), NEVER tail/parse another agent's JSONL transcript, NEVER use `node -e`/`python3` to parse tool/subagent JSON, and NEVER `sleep`-poll in a shell loop for completion. These improvisations breach the [internal-tooling-only rule (issue #982)](retrospective-checkpoint-contract.md#internal-tooling-only-rule-issue-982--now-advisory) (no `node -e`/`python3` parsing of tool JSON) and the no-shell-polling rule, and a single improvised wait adds a non-empty entry to the advisory `retrospectiveFindings.rawCallViolations` array (issue #1077: reported to the conductor, **never blocking**). **Sanctioned wait:** rely on the harness completion notification, or read each reviewer's findings artifact at its deterministic output path (see [Checkpoint Review Chain Contract](../../docs/gate-review-sub-loop-contract.md) Phase 2), then join via `consolidateFanin` from `@dev-loops/core/loop/gate-fanin` (Phase 3). See also the dev-loop SKILL "Bounded async task contract".
16
+ 10. **Ad hoc `gh api graphql` review-thread queries or `gh pr checks`/shell-pipe CI polling**: Do not hand-write a GraphQL query to enumerate review threads, and do not wrap `gh pr checks` in an `awk`/`grep`/`until`-based shell loop to wait for CI. Enumerate threads (with the ids `reply-resolve-review-thread.mjs` needs) via `scripts/github/list-review-threads.mjs`; block on current-head CI settling via `scripts/github/wait-pr-checks.mjs` (or `dev-loops loop watch-ci` for the JSON-status variant); read the aggregate loop/CI/thread state via `detect-copilot-loop-state.mjs` instead of re-deriving it from raw `gh` output.
16
17
 
17
18
  ## Light mode exception
18
19
 
@@ -8,7 +8,8 @@ Other repo docs may summarize or link this contract, but they should not redefin
8
8
 
9
9
  ## Two-tier model
10
10
 
11
- dev-loops supports two mutually exclusive artifact authority modes. Every work item originates from exactly one authoritative artifact: a GitHub issue or a persisted markdown plan file. Work originates from a PR or a direct local change only when explicitly requested.
11
+ <!-- rule: ARTIFACT-TWO-TIER-EXCLUSIVE -->
12
+ dev-loops supports two mutually exclusive artifact authority modes. Every work item MUST originate from exactly one authoritative artifact: a GitHub issue or a persisted markdown plan file. Work MUST NOT originate from a PR or a direct local change unless explicitly requested.
12
13
 
13
14
  The shipped extension default selects local-planning; see [Shipped default posture](#shipped-default-posture) below. The mode names that follow describe the two tiers; "default" in their headings refers to the local-first code-level default in `BUILT_IN_DEFAULTS`.
14
15
 
@@ -19,7 +20,7 @@ The shipped extension default selects local-planning; see [Shipped default postu
19
20
  Artifacts:
20
21
  - **Planning artifact:** GitHub issue (title, body, labels, assignees, acceptance criteria)
21
22
  - **Execution artifact:** GitHub PR (linked to issue; created during implementation)
22
- - **No local duplicate:** Do not create `docs/phases/phase-<n>.md` for the same session when a GitHub issue is the canonical spec
23
+ - <!-- rule: ARTIFACT-TRACKER-FIRST-NO-DUP --> **No local duplicate:** A tracker-first session MUST NOT create `docs/phases/phase-<n>.md` for the same session when a GitHub issue is the canonical spec
23
24
 
24
25
  Key contract:
25
26
  - GitHub issue state is authoritative — not local notes or chat context
@@ -40,7 +41,7 @@ Artifacts:
40
41
  Key contract:
41
42
  - The markdown plan file is the canonical spec — not a duplicate of a tracker issue
42
43
  - GitHub issues may still be used for tracking or linking, but the plan file is authoritative for scope and acceptance criteria
43
- - A tracker-backed local implementation session (GitHub issue as canonical spec) must not also maintain a duplicate `docs/phases/phase-<n>.md` — see [Public Dev Loop Contract](public-dev-loop-contract.md) "Tracker-backed local implementation input-source contract"
44
+ - A tracker-backed local implementation session (GitHub issue as canonical spec) is bound by [ARTIFACT-TRACKER-FIRST-NO-DUP](#tracker-first) above — see [Public Dev Loop Contract](public-dev-loop-contract.md) "Tracker-backed local implementation input-source contract"
44
45
 
45
46
  ### Lightweight (PR-body-as-spec)
46
47
 
@@ -51,11 +52,10 @@ Artifacts:
51
52
  - **No committed plan doc:** no `docs/phases/*.md` is created for the session
52
53
 
53
54
  Key contract:
54
- - The PR body must carry the same invariants a durable spec would: **Objective/why, in-scope + explicit non-goals, testable acceptance criteria, definition of done, open questions/risks**. `scripts/loop/validate-pr-body-spec.mjs` (reusing the generic markdown logic of `@dev-loops/core/loop/issue-refinement-artifact`, `validatePrBodySpec`) validates these and **fails closed** with a distinct `missing_*` reason per absent invariant.
55
- - The PR body MUST also carry the `Closes #N` linkage (GitHub's other closing keywords count too); `validate-pr-body-spec` fails closed with `missing_closing_issue_reference` without it, and with `closes_wrong_issue` when an `--expected-issue` is given and doesn't match — the lightweight path's issue-tracking state must never silently diverge from PR state (issue #1181).
55
+ - <!-- rule: ARTIFACT-LIGHTWEIGHT-BODY-INVARIANTS --> The PR body MUST carry the same invariants a durable spec would: **Objective/why, in-scope + explicit non-goals, testable acceptance criteria, definition of done, open questions/risks** — unconditionally, whether or not the work is tracker-backed. The `Closes #N` linkage (GitHub's other closing keywords count too) is conditional on artifact backing (operator ruling, issue #1210): REQUIRED when the work originates from a GitHub issue (`--issue --lightweight`), ABSENT BY DESIGN when the PR is the sole artifact with no backing issue (`--lightweight` alone, issue-less PR-first) — an issue-less PR body MUST NOT carry a closing reference to an issue that doesn't back it. `scripts/loop/validate-pr-body-spec.mjs` (reusing the generic markdown logic of `@dev-loops/core/loop/issue-refinement-artifact`, `validatePrBodySpec`) validates these and fails closed with a distinct reason per violated invariant — `missing_closing_issue_reference` without the linkage in tracker-backed mode, `closes_wrong_issue` when an `--expected-issue` is given and doesn't match, `unexpected_closing_issue_reference` when a closing reference is present under explicit issue-less mode (`--no-issue`) — so the lightweight path's issue-tracking state never silently diverges from PR state (issue #1181).
56
56
  - This flips the promotion invariant below (P4, "the PR body carries the committed plan-doc **path**"): under lightweight there is no committed plan doc — the PR body **is** the spec, not a pointer to one.
57
57
  - The explicit `--lightweight` flag is the primary, deterministic trigger. The secondary heuristic (chore/fix commit type + no `--plan-file` + small change) is a documented manual signal for when to reach for the flag; it is not an automatic selector.
58
- - `--lightweight` is rejected when combined with `--plan-file` (they are opposites: `--plan-file` commits a durable plan doc as the spec, `--lightweight` makes the PR body the spec) and only composes with `--issue`.
58
+ - <!-- rule: ARTIFACT-LIGHTWEIGHT-PLAN-FILE-EXCLUSIVE --> `--lightweight` MUST be rejected when combined with `--plan-file` (they are opposites: `--plan-file` commits a durable plan doc as the spec, `--lightweight` makes the PR body the spec). It composes with `--issue` (tracker-backed) or stands alone (issue-less PR-first, #1210 — gated on `localImplementation.lightMode` being enabled and the change scope staying within its threshold); it MUST be rejected when combined with any other mode flag (`--pr`, `--input`, `--spike`).
59
59
  - Pre-approval acceptance-criteria verification reads the AC/DoD/invariants directly from the PR body rather than a linked issue body; see [Acceptance Criteria Verification](acceptance-criteria-verification.md).
60
60
 
61
61
  ### Mode selection table
@@ -64,7 +64,7 @@ Key contract:
64
64
  |---|---|---|---|
65
65
  | Tracker-first | GitHub issue | Yes | `strategy.default: github-first` |
66
66
  | Local-planning (shipped default) | Markdown plan file | No | `strategy.default: local-first` |
67
- | Lightweight (PR-body-as-spec) | GitHub PR description | Yes (`--issue`) | modifier: `--lightweight` (`canonicalSpecSource: pr_body`) |
67
+ | Lightweight (PR-body-as-spec) | GitHub PR description | Conditional `--issue` when tracker-backed; absent for issue-less PR-first (#1210), gated on `localImplementation.lightMode` + change-scope threshold | modifier: `--lightweight` (`canonicalSpecSource: pr_body`) |
68
68
 
69
69
  `inputSource.default` further disambiguates local-first startup:
70
70
  | inputSource | Meaning |
@@ -106,13 +106,14 @@ The effective default for a consumer comes from the config-merge layering in `pa
106
106
 
107
107
  With nothing but the shipped package in place, the extension layer resolves `strategy.default` to `local-first`, so the shipped default posture is local-planning (epic #947, decision #7). A repo opts back into tracker-first by setting `strategy.default: github-first` in its own `.devloops`.
108
108
 
109
- Two legacy repo-local layers also exist under `.pi/dev-loop/` (the package no longer ships a `.pi/dev-loop/defaults.yaml`). They differ in how they load: `.pi/dev-loop/defaults.*` is always applied when present, between the extension defaults and `.devloops`; `.pi/dev-loop/settings.*` (and the older `overrides.*`) load only when no `.devloops` is present — when `.devloops` exists it is authoritative and those files are ignored (with a deprecation warning). The full precedence, low to high, is: `BUILT_IN_DEFAULTS` < extension defaults < `.pi/dev-loop/defaults.*` < repo `.devloops` (or, when `.devloops` is absent, `.pi/dev-loop/settings.*` / `overrides.*`).
109
+ Two legacy repo-local layers also exist under `.pi/dev-loop/` (the package no longer ships a `.pi/dev-loop/defaults.yaml`). They differ in how they load, per the precedence list above: `.pi/dev-loop/defaults.*` is always applied when present, between the extension defaults and `.devloops`; `.pi/dev-loop/settings.*` (and the older `overrides.*`) load only when no `.devloops` is present — when `.devloops` exists it is authoritative and those files are ignored (with a deprecation warning).
110
110
 
111
111
  ### Explicit non-knobs
112
112
 
113
- These are not valid artifact authority mode selectors:
114
- - `strategy.default: copilot` not a valid mode; the enum accepts only `github-first` or `local-first` (`packages/core/src/config/config.mjs`)
115
- - Free-form string values fail closed
113
+ <!-- rule: ARTIFACT-STRATEGY-ENUM-FAIL-CLOSED -->
114
+ `ARTIFACT-STRATEGY-ENUM-FAIL-CLOSED`: The strategy enum MUST accept only `github-first` or `local-first` and MUST fail closed on any other value (`packages/core/src/config/config.mjs`). These are not valid artifact authority mode selectors:
115
+ - `strategy.default: copilot`not a valid mode
116
+ - Free-form string values — MUST fail closed
116
117
  - Omitting `strategy.default` from every layer — resolves to `local-first` from `BUILT_IN_DEFAULTS`
117
118
 
118
119
  ## Local-first plan-file flow end to end
@@ -4,6 +4,7 @@ Canonical owner for agent confirmation / authorization rules across all workflow
4
4
 
5
5
  ## Core rule
6
6
 
7
+ <!-- rule: CONFIRM-CORE-EXPLICIT -->
7
8
  Before any state-changing action, get explicit confirmation unless the latest user message already clearly authorizes that exact action.
8
9
 
9
10
  ## What counts as confirmation
@@ -0,0 +1,37 @@
1
+ # Contract style guide
2
+
3
+ Canonical owner for contract style, rule IDs, and definitional discipline.
4
+
5
+ A short orientation paragraph introduces the document; normative rules live only in the rule table.
6
+
7
+ ## Terms
8
+
9
+ | Term | Definition |
10
+ |---|---|
11
+ | <!-- term: gate:pre_approval_gate --> `pre_approval_gate` | Review gate that checks changed contract rules against the registry before approval. |
12
+
13
+ ## Rule ownership
14
+
15
+ | Rule ID | Rule |
16
+ |---|---|
17
+ | <!-- rule: STYLE-RFC2119-KEYWORDS --> `STYLE-RFC2119-KEYWORDS` | Normative contract text MUST use RFC-2119 keywords (`MUST`, `MUST NOT`, `SHALL`, `SHALL NOT`, `SHOULD`, `MAY`) when declaring requirements. |
18
+ | <!-- rule: STYLE-SINGLE-OWNER --> `STYLE-SINGLE-OWNER` | Each normative rule MUST have one single owner: exactly one source document defines the rule and every other document references that rule by ID instead of restating it. |
19
+ | <!-- rule: STYLE-RULE-MARKER --> `STYLE-RULE-MARKER` | A rule definition MUST place an HTML marker immediately before the rule body using `&lt;!-- rule: AREA-TOPIC-NNN --&gt;`; markers are invisible in rendered Markdown and MUST pass through mirror generation verbatim. |
20
+ | <!-- rule: STYLE-ID-SCHEME --> `STYLE-ID-SCHEME` | Rule IDs MUST be stable uppercase identifiers using the descriptive `AREA-TOPIC-NNN` shape or a short descriptive suffix when clearer. |
21
+ | <!-- rule: STYLE-ID-STABILITY --> `STYLE-ID-STABILITY` | A rule ID is stable forever: semantic changes MUST retire the old ID and mint a new ID through a dedicated semantic-change issue, never silent mutation. |
22
+ | <!-- rule: STYLE-REFERENCE-BY-ID --> `STYLE-REFERENCE-BY-ID` | A non-owner document MUST reference an owned rule by ID, preferably with a link to the owner, and MUST NOT duplicate the owned rule text. |
23
+ | <!-- rule: STYLE-DEFINED-TERMS --> `STYLE-DEFINED-TERMS` | State tokens, reason codes, and gate names MUST have exactly one canonical definition before annotated rule text uses them. |
24
+ | <!-- rule: STYLE-TERM-MARKER --> `STYLE-TERM-MARKER` | A canonical term definition MUST use `&lt;!-- term: kind:value --&gt;`, where `kind` is `state`, `reason`, or `gate`. |
25
+ | <!-- rule: STYLE-PRECEDENCE-CONFLICT --> `STYLE-PRECEDENCE-CONFLICT` | When two contract statements conflict, the document that owns the referenced rule ID wins until a semantic-change issue retires or replaces the rule. |
26
+ | <!-- rule: STYLE-CANONICAL-OPENER --> `STYLE-CANONICAL-OPENER` | A contract document SHOULD open with `Canonical owner for X` and SHOULD end with a Cross-references section. |
27
+ | <!-- rule: STYLE-CONTRADICTION-LENS --> `STYLE-CONTRADICTION-LENS` | A contract-touching PR MUST include an RFC-2119 contradiction check in `pre_approval_gate`: every added or changed rule is checked against the rule registry for opposing modality or a weaker restatement. |
28
+ | <!-- rule: STYLE-LEXICAL-SCAN-LIMIT --> `STYLE-LEXICAL-SCAN-LIMIT` | The ownership validator's contradiction scan is lexical only; behavioral contradictions belong to the L2/L3 harness and semantic contradictions belong to the gate contradiction lens. |
29
+
30
+ ## Reference syntax
31
+
32
+ Use `&lt;!-- rule-ref: RULE-ID --&gt;` next to a cross-reference when a machine-checkable reference is useful. Markdown links may also use the rule ID as link text.
33
+
34
+ ## Cross-references
35
+
36
+ - [Stop conditions](stop-conditions.md)
37
+ - State-machine conformance + invariant harness (`scripts/docs/validate-state-machine-conformance.mjs`) — L2 (doc ↔ code) conformance and L3 (completeness/safety/liveness) graph invariants; its header comment documents the registration path for adding a machine
@@ -25,18 +25,17 @@ Use the machines to answer:
25
25
  - what the next required action is
26
26
  - when to stop instead of guessing
27
27
 
28
- Each machine captures an observable snapshot from GitHub facts (plus explicit bounded local loop
29
- metadata when required) and interprets it into exactly one current state plus allowed next
30
- transitions. In the `dev-loops` source repository, the supporting source-authority references are [Copilot Loop State Graph](../../docs/copilot-loop-state-graph.md) and [Reviewer Loop State Graph](../../docs/reviewer-loop-state-graph.md) under `../../docs/` relative to `skills/copilot-pr-followup/SKILL.md`. Treat those links as source-repo references, not bundled installed-skill docs.
28
+ Each machine's snapshot-to-state mapping is defined in its owner doc (see the Overview of
29
+ [Copilot Loop State Graph](../../docs/copilot-loop-state-graph.md) and
30
+ [Reviewer Loop State Graph](../../docs/reviewer-loop-state-graph.md)), under `../../docs/`
31
+ relative to `skills/copilot-pr-followup/SKILL.md` in the `dev-loops` source repository. Treat
32
+ those links as source-repo references, not bundled installed-skill docs.
31
33
 
32
34
  For tracker-first MVP `story -> PR -> tracker sync` work, the source-repo reference is [Tracker-First Story-to-PR Contract](../../docs/tracker-story-pr-contract.md). That source doc inherits source-of-truth ownership, the required work item <-> PR link, and reverse-sync semantics from `#21`; it only adds the mutually exclusive workflow-family states and post-merge sync-verification states for this narrower MVP slice.
33
35
 
34
36
  ## Key guarantees from the state machine
35
37
 
36
- - `unresolvedThreadCount > 0` always routes to fix/reply-resolve never to a wait/watch state
37
- - `snapshot.copilotReviewRequestStatus === "unavailable"` or `snapshot.copilotReviewRequestStatus === "failed"` routes to a terminal stop state — never to sleep or watch
38
- - `agentFixStatus === "applied"` with unresolved threads routes to `already_fixed_needs_reply_resolve` — reply/resolve on GitHub is required before re-requesting review
39
- - Copilot being in `requested_reviewers` (`"requested"` or `"already-requested"`) routes to `waiting_for_copilot_review`
38
+ These routing guarantees are owned by [Copilot Loop State Graph](../../docs/copilot-loop-state-graph.md); see `COPILOT-STATE-UNRESOLVED-PRIORITY`, `COPILOT-STATE-TERMINAL-STOP`, `COPILOT-STATE-REPLY-BEFORE-REREQUEST`, and `COPILOT-STATE-ACTIVE-REQUEST-WAIT` there for the routing rules driving `unresolvedThreadCount`, `copilotReviewRequestStatus`, and `agentFixStatus`.
40
39
 
41
40
  ## How to use the state machine in practice
42
41
 
@@ -51,7 +50,7 @@ For tracker-first MVP `story -> PR -> tracker sync` work, the source-repo refere
51
50
 
52
51
  4. Branch on the detector output instead of inventing a polling loop:
53
52
  - `state=waiting_for_copilot_review` with `snapshot.copilotReviewOnCurrentHead=false`: do **not** poll manually; either run `node <resolved-skill-scripts>/loop/run-watch-cycle.mjs --repo <owner/name> --pr <number>` for persistent async waiting or report the wait state and resume later after the single detector call
54
- - `state=waiting_for_ci` with `snapshot.ciStatus` in `{ "pending", "none" }`: do **not** poll manually by default; use `dev-loops loop watch-ci --repo <owner/name> --pr <number>` to block-wait on the combined check/status state (provider-agnostic — CircleCI, GitHub Actions, and any external commit-status / check-run). `gh run watch <run-id>` is an Actions-only fallback when the current-head run id is already known and you know the gating checks are GitHub Actions. Either way, on `timeout`/`changed` report pending CI and resume later after the single detector refresh. Bounded exception: if GitHub created zero current-head check suites/statuses, the previous head rollup was green, and local `npm run verify` already passed for the same current head, rerun `detect-copilot-loop-state.mjs` with `--local-validation-head-sha <current-head-sha>` so the detector can promote that exact zero-suite case to `snapshot.ciStatus="crediblyGreen"` instead of waiting forever on raw `none`.
53
+ - `state=waiting_for_ci` with `snapshot.ciStatus` in `{ "pending", "none" }`: do **not** poll manually by default; use `dev-loops loop watch-ci --repo <owner/name> --pr <number>` or `node <resolved-skill-scripts>/github/wait-pr-checks.mjs --repo <owner/name> --pr <number>` (settle-blocking with a 0/1/2 exit contract) to block-wait on the combined check/status state (provider-agnostic — CircleCI, GitHub Actions, and any external commit-status / check-run). `gh run watch <run-id>` is an Actions-only fallback when the current-head run id is already known and you know the gating checks are GitHub Actions. Either way, on `timeout`/`changed` report pending CI and resume later after the single detector refresh. Bounded exception: if GitHub created zero current-head check suites/statuses, the previous head rollup was green, and local `npm run verify` already passed for the same current head, rerun `detect-copilot-loop-state.mjs` with `--local-validation-head-sha <current-head-sha>` so the detector can promote that exact zero-suite case to `snapshot.ciStatus="crediblyGreen"` instead of waiting forever on raw `none`.
55
54
  - `snapshot.ciStatus="failure"` remains a stop/fix state, never a wait loop
56
55
 
57
56
  5. For reviewer-side draft-review work, run `node <resolved-skill-scripts>/loop/detect-reviewer-loop-state.mjs --repo <owner/name> --pr <number> [--reviewer-login <login>] [--local-state <path>]`.
@@ -64,10 +63,7 @@ For tracker-first MVP `story -> PR -> tracker sync` work, the source-repo refere
64
63
 
65
64
  ## Judgment calls that remain in the agent layer
66
65
 
67
- - Whether a comment should be accepted, deferred, or disagreed with
68
- - Whether the code change is already sufficient (→ sets `agentFixStatus: "applied"`)
69
- - What the narrowest valid fix is
70
- - Whether another Copilot pass is desired (→ triggers re-request or selects `done`)
66
+ See [Copilot Loop State Graph](../../docs/copilot-loop-state-graph.md)'s "Agent judgment boundary" for the bounded list of decisions the state machine leaves to the agent.
71
67
 
72
68
  ## Workflow overview
73
69
 
@@ -102,7 +98,7 @@ Useful checks:
102
98
 
103
99
  If the user asks for status/progress/readiness/merge-state/next-step (including “what is next”):
104
100
  - resolve authoritative active artifact identity first (issue/PR, plus branch/head SHA when useful)
105
- - for issue targets, do not assert "no open PR" until authoritative issue↔PR linkage is resolved via the startup resolver (`dev-loops loop startup --issue <number>`, run inside the `dev-loop` async subagent) do not run `detect-linked-issue-pr.mjs` manually
101
+ - for issue targets, resolve authoritative issue↔PR linkage via the startup resolver (`dev-loops loop startup --issue <number>`, run inside the `dev-loop` async subagent) rather than running `detect-linked-issue-pr.mjs` manually; the fail-closed-until-resolved requirement is owned by `FACADE-STATUS-AUTHORITATIVE-FAIL-CLOSED` in [Public Dev Loop Contract](./public-dev-loop-contract.md)
106
102
  - resolve artifact state (`open`/`closed`/`merged`/`not_applicable`)
107
103
  - resolve current loop state and next action from deterministic helper/state output
108
104
  - include explicit resolved artifact identity in the answer
@@ -170,7 +166,8 @@ Follow the PR description contract (see [Agent Instructions](../../AGENTS.md) if
170
166
 
171
167
  Checkbox rule: acceptance criteria, definition-of-done items, and any task list must be rendered as real GitHub markdown checkboxes inside list items (`- [ ]` / `- [x]`, also `* [ ]` / `* [x]`). Do not wrap checkbox markers (e.g. `[x]`) in backticks. Do not place checkbox markers inside table cells — task lists are not interactive there even with a leading `- `.
172
168
 
173
- New PRs in this workflow must be opened as **draft** PRs first when the repository enables `.devloops` at repo root `workflow.requireDraftFirst`. The built-in shipped default remains permissive; this repo opts in. Do not create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy for the current PR scope. The draft gate inspection is a real workflow boundary, so a new PR must exist in draft before `gh pr ready` is even eligible.
169
+ <!-- rule: OPS-DRAFT-FIRST-PR -->
170
+ `OPS-DRAFT-FIRST-PR`: New PRs in this workflow MUST be opened as **draft** PRs first when the repository enables `.devloops` at repo root `workflow.requireDraftFirst` (the built-in shipped default remains permissive; this repo opts in) — mechanically enforced by the `create-pr.mjs` wrapper below. Agents MUST NOT create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy for the current PR scope. The draft gate inspection is a real workflow boundary, so a new PR must exist in draft before `gh pr ready` is even eligible.
174
171
 
175
172
  Only use `node <resolved-skill-scripts>/github/create-pr.mjs` when authoritative issue↔PR resolution says there is no already-open linked PR. If a PR already exists, reuse/update that canonical PR instead of opening another one. This wrapper preserves the underlying `gh pr create` output contract while enforcing draft-first mechanically and self-assigning the PR by default (`--assignee @me`).
176
173
 
@@ -230,4 +227,11 @@ Helper-owned sleep inside `run-watch-cycle.mjs`, `probe-copilot-review.mjs`, or
230
227
 
231
228
  A watcher sleeping between polls is expected behavior, not a blocker.
232
229
 
230
+ ## Hard rule: no inline interpreters
231
+
232
+ <!-- rule: OPS-NO-INLINE-INTERPRETER -->
233
+ `OPS-NO-INLINE-INTERPRETER`: Coordinator and agent flows MUST NOT invoke an inline interpreter (`node -e`/`node --eval`, `python3 -c`, `python3 - <<EOF` heredocs, or equivalent) to (a) parse any dev-loops tool output, or (b) mutate repository files. Sanctioned paths: a tool's `--jq`/`--silent` output flags (or `gh ... --jq`) for (a); the editor/patch tools, or `gh ... --body-file` composed from a `--jq` read, for (b). This rule applies to ALL repos using the dev-loop workflow, not just the source repo.
234
+
235
+ Post-watch read pattern: after a probe/watch settles, do not parse its output — re-read state via `detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>` and, for thread bodies/ids, `list-review-threads.mjs --repo <owner/name> --pr <number> --unresolved-only` (see [Copilot PR Follow-up Skill](../copilot-pr-followup/SKILL.md) Step 6).
236
+
233
237
  If the polling interval is 1 minute, do not treat silence shorter than one full poll interval as suspicious, and do not configure needs-attention thresholds close to a few seconds for this loop.
@@ -32,13 +32,13 @@ Definitions:
32
32
 
33
33
  ## Claude-Code-path test inventory
34
34
 
35
- When a change targets a Claude-Code-specific seam, the following suites are the baseline that MUST stay green, and are the first place to add coverage for a Pi-originated change that must not regress Claude Code:
35
+ When a change targets a Claude-Code-specific seam, the following suites are the baseline that MUST stay green, and are the first place to add coverage for a Pi-originated change that MUST NOT regress Claude Code:
36
36
 
37
37
  - `npm run test:assets` — runs `test/contracts/*.test.mjs`, including `test/contracts/claude-assets-reproducible.test.mjs`, `test/contracts/claude-headless-smoke.test.mjs`, `test/contracts/claude-plugin-manifest.test.mjs`, `test/contracts/claude-plugin-marketplace.test.mjs`, `test/contracts/claude-hooks-settings.test.mjs`, and `test/contracts/cli-harness-agnostic.test.mjs`
38
38
  - `npm run smoke:headless` — runs `scripts/claude/headless-info-smoke.mjs`; this is a manual/local-only check, not part of `npm test` or `npm run verify` and not run by CI — run it directly when a change touches Claude Code headless-info behavior
39
39
  - `npm run test:core` — runs `packages/core/test/*.test.mjs`, including `packages/core/test/claude-headless-entry.test.mjs`, `packages/core/test/claude-hook-decisions.test.mjs`, `packages/core/test/run-context.test.mjs`, `packages/core/test/async-start-contract.test.mjs`
40
40
 
41
- Symmetrically, for a change targeting the Claude-Code-specific seam that must not regress Pi, `npm run test:extension` (which runs `test/extension-*.test.mjs`, including `test/extension-pi-adapter.test.mjs`, the Pi-side adapter inventory) is the baseline suite to check and extend.
41
+ Symmetrically, for a change targeting the Claude-Code-specific seam that MUST NOT regress Pi, `npm run test:extension` (which runs `test/extension-*.test.mjs`, including `test/extension-pi-adapter.test.mjs`, the Pi-side adapter inventory) is the baseline suite to check and extend.
42
42
 
43
43
  ## How to add coverage
44
44
 
@@ -1,6 +1,6 @@
1
1
  # Debt remediation contract
2
2
 
3
- Canonical authority for the debt remediation pipeline and its integration with the `dev-loop` execution path.
3
+ Canonical owner for the debt remediation pipeline and its integration with the `dev-loop` execution path.
4
4
 
5
5
  ## Pipeline
6
6