dev-loops 0.6.0 → 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 (203) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +8 -7
  3. package/.claude/agents/fixer.md +1 -0
  4. package/.claude/agents/refiner.md +2 -2
  5. package/.claude/agents/review.md +4 -3
  6. package/.claude/commands/{auto.md → loop-auto.md} +1 -1
  7. package/.claude/commands/loop-continue.md +15 -0
  8. package/.claude/commands/loop-enqueue.md +22 -0
  9. package/.claude/commands/loop-grill.md +17 -0
  10. package/.claude/commands/{info.md → loop-info.md} +2 -2
  11. package/.claude/commands/loop-queue-status.md +24 -0
  12. package/.claude/commands/{start-spike.md → loop-start-spike.md} +2 -2
  13. package/.claude/commands/{start.md → loop-start.md} +1 -1
  14. package/.claude/commands/{status.md → loop-status.md} +1 -1
  15. package/.claude/hooks/_bash-command-classify.mjs +333 -29
  16. package/.claude/hooks/_hook-decisions.mjs +138 -15
  17. package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
  18. package/.claude/skills/copilot-pr-followup/SKILL.md +60 -34
  19. package/.claude/skills/dev-loop/SKILL.md +16 -12
  20. package/.claude/skills/docs/acceptance-criteria-verification.md +8 -3
  21. package/.claude/skills/docs/anti-patterns.md +5 -3
  22. package/.claude/skills/docs/artifact-authority-contract.md +30 -11
  23. package/.claude/skills/docs/confirmation-rules.md +1 -0
  24. package/.claude/skills/docs/contract-style-guide.md +37 -0
  25. package/.claude/skills/docs/copilot-loop-operations.md +19 -15
  26. package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
  27. package/.claude/skills/docs/debt-remediation-contract.md +1 -1
  28. package/.claude/skills/docs/epic-tree-refinement-procedure.md +25 -22
  29. package/.claude/skills/docs/issue-intake-procedure.md +26 -19
  30. package/.claude/skills/docs/local-planning-flow.md +1 -1
  31. package/.claude/skills/docs/local-planning-worked-example.md +1 -1
  32. package/.claude/skills/docs/merge-preconditions.md +30 -12
  33. package/.claude/skills/docs/plan-file-contract.md +1 -1
  34. package/.claude/skills/docs/pr-lifecycle-contract.md +44 -35
  35. package/.claude/skills/docs/public-dev-loop-contract.md +60 -44
  36. package/.claude/skills/docs/retrospective-checkpoint-contract.md +106 -79
  37. package/.claude/skills/docs/spike-mode-contract.md +14 -7
  38. package/.claude/skills/docs/stop-conditions.md +30 -15
  39. package/.claude/skills/docs/tracker-first-loop-state.md +13 -8
  40. package/.claude/skills/docs/ui-e2e-scoping-step.md +32 -0
  41. package/.claude/skills/docs/validation-policy.md +4 -5
  42. package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
  43. package/.claude/skills/local-implementation/SKILL.md +62 -145
  44. package/.claude/skills/loop-grill/SKILL.md +163 -0
  45. package/AGENTS.md +1 -0
  46. package/CHANGELOG.md +101 -0
  47. package/README.md +9 -9
  48. package/agents/dev-loop.agent.md +11 -10
  49. package/agents/developer.agent.md +1 -1
  50. package/agents/docs.agent.md +1 -1
  51. package/agents/fixer.agent.md +2 -1
  52. package/agents/quality.agent.md +1 -1
  53. package/agents/refiner.agent.md +3 -3
  54. package/agents/review.agent.md +5 -4
  55. package/cli/index.mjs +35 -42
  56. package/extension/README.md +4 -4
  57. package/extension/checks.ts +1 -5
  58. package/extension/harness-types.ts +1 -0
  59. package/extension/index.ts +6 -0
  60. package/extension/pi-extension-adapter.ts +2 -0
  61. package/extension/presentation.ts +7 -19
  62. package/package.json +12 -12
  63. package/scripts/_core-helpers.mjs +2 -0
  64. package/scripts/claude/headless-dev-loop.mjs +1 -1
  65. package/scripts/docs/validate-rule-ownership.mjs +442 -0
  66. package/scripts/docs/validate-state-machine-conformance.mjs +1092 -0
  67. package/scripts/github/_review-thread-mutations.mjs +5 -2
  68. package/scripts/github/capture-review-threads.mjs +2 -9
  69. package/scripts/github/comment-issue.mjs +2 -9
  70. package/scripts/github/create-label.mjs +133 -0
  71. package/scripts/github/create-pr.mjs +119 -10
  72. package/scripts/github/detect-checkpoint-evidence.mjs +259 -18
  73. package/scripts/github/detect-linked-issue-pr.mjs +22 -6
  74. package/scripts/github/edit-pr.mjs +259 -0
  75. package/scripts/github/fetch-ci-logs.mjs +2 -9
  76. package/scripts/github/list-issues.mjs +2 -9
  77. package/scripts/github/list-review-threads.mjs +277 -0
  78. package/scripts/github/manage-sub-issues.mjs +46 -10
  79. package/scripts/github/offer-human-handoff.mjs +8 -5
  80. package/scripts/github/post-gate-findings.mjs +20 -5
  81. package/scripts/github/probe-ci-status.mjs +8 -2
  82. package/scripts/github/probe-copilot-review.mjs +21 -14
  83. package/scripts/github/ready-for-review.mjs +26 -8
  84. package/scripts/github/reconcile-draft-gate.mjs +7 -3
  85. package/scripts/github/reply-resolve-review-thread.mjs +16 -5
  86. package/scripts/github/reply-resolve-review-threads.mjs +69 -7
  87. package/scripts/github/request-copilot-review.mjs +98 -26
  88. package/scripts/github/resolve-handoff-candidates.mjs +7 -3
  89. package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
  90. package/scripts/github/stage-reviewer-draft.mjs +10 -2
  91. package/scripts/github/tick-verified-checkboxes.mjs +202 -0
  92. package/scripts/github/upsert-checkpoint-verdict.mjs +105 -15
  93. package/scripts/github/verify-briefing-prefixes.mjs +191 -0
  94. package/scripts/github/verify-fresh-review-context.mjs +226 -32
  95. package/scripts/github/view-pr.mjs +150 -0
  96. package/scripts/github/wait-pr-checks.mjs +171 -0
  97. package/scripts/github/write-gate-context.mjs +458 -65
  98. package/scripts/github/write-gate-findings-log.mjs +128 -2
  99. package/scripts/lib/jq-output.mjs +18 -0
  100. package/scripts/loop/_post-convergence-change.mjs +211 -0
  101. package/scripts/loop/_pr-runner-coordination.mjs +70 -0
  102. package/scripts/loop/_repo-root-resolver.mjs +47 -0
  103. package/scripts/loop/build-handoff-envelope.mjs +14 -4
  104. package/scripts/loop/check-retro-tooling.mjs +14 -3
  105. package/scripts/loop/checkpoint-contract.mjs +7 -4
  106. package/scripts/loop/cleanup-worktree.mjs +12 -3
  107. package/scripts/loop/conductor-monitor.mjs +12 -18
  108. package/scripts/loop/copilot-pr-handoff.mjs +162 -14
  109. package/scripts/loop/debt-remediate.mjs +24 -12
  110. package/scripts/loop/detect-change-scope.mjs +38 -9
  111. package/scripts/loop/detect-copilot-loop-state.mjs +34 -8
  112. package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
  113. package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
  114. package/scripts/loop/detect-internal-only-pr.mjs +8 -2
  115. package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
  116. package/scripts/loop/detect-pr-gate-coordination-state.mjs +184 -69
  117. package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
  118. package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
  119. package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
  120. package/scripts/loop/ensure-worktree.mjs +8 -3
  121. package/scripts/loop/info.mjs +12 -4
  122. package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
  123. package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
  124. package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
  125. package/scripts/loop/inspect-run-viewer.mjs +67 -4
  126. package/scripts/loop/inspect-run.mjs +8 -2
  127. package/scripts/loop/outer-loop.mjs +8 -4
  128. package/scripts/loop/pr-runner-coordination.mjs +2 -9
  129. package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
  130. package/scripts/loop/pre-flight-gate.mjs +9 -9
  131. package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
  132. package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
  133. package/scripts/loop/provision-worktree.mjs +74 -3
  134. package/scripts/loop/resolve-dev-loop-startup.mjs +216 -10
  135. package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
  136. package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
  137. package/scripts/loop/run-conductor-cycle.mjs +8 -2
  138. package/scripts/loop/run-queue.mjs +18 -9
  139. package/scripts/loop/run-refinement-audit.mjs +8 -9
  140. package/scripts/loop/run-watch-cycle.mjs +2 -9
  141. package/scripts/loop/sanctioned-commands.mjs +106 -0
  142. package/scripts/loop/steer-loop.mjs +29 -16
  143. package/scripts/loop/validate-pr-body-spec.mjs +223 -0
  144. package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
  145. package/scripts/pages/build-site.mjs +59 -8
  146. package/scripts/pages/build-state-atlas.mjs +443 -0
  147. package/scripts/projects/_resolve-project.mjs +1 -0
  148. package/scripts/projects/add-queue-item.mjs +60 -54
  149. package/scripts/projects/archive-done-items.mjs +49 -84
  150. package/scripts/projects/ensure-queue-board.mjs +10 -36
  151. package/scripts/projects/list-queue-items.mjs +59 -382
  152. package/scripts/projects/move-queue-item.mjs +21 -449
  153. package/scripts/projects/reconcile-queue.mjs +253 -0
  154. package/scripts/projects/reorder-queue-item.mjs +43 -68
  155. package/scripts/projects/resolve-active-board-item.mjs +108 -46
  156. package/scripts/projects/sync-item-status.mjs +15 -10
  157. package/scripts/refine/_refine-helpers.mjs +21 -5
  158. package/scripts/refine/exit-spike.mjs +18 -8
  159. package/scripts/refine/promote-plan.mjs +22 -16
  160. package/scripts/refine/prose-linkage-detector.mjs +1 -1
  161. package/scripts/refine/refine-plan-file.mjs +13 -4
  162. package/scripts/refine/refinement-completeness-checker.mjs +1 -1
  163. package/scripts/refine/scaffold-spike-file.mjs +4 -8
  164. package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
  165. package/scripts/refine/tree-integrity-validator.mjs +1 -1
  166. package/scripts/refine/validate-plan-file.mjs +1 -1
  167. package/scripts/refine/validate-spike-file.mjs +1 -1
  168. package/scripts/refine/verify.mjs +11 -6
  169. package/scripts/release/assert-core-dependency-version.mjs +123 -0
  170. package/scripts/release/extract-changelog-section.mjs +16 -2
  171. package/skills/copilot-pr-followup/SKILL.md +60 -34
  172. package/skills/dev-loop/SKILL.md +11 -7
  173. package/skills/docs/acceptance-criteria-verification.md +8 -3
  174. package/skills/docs/anti-patterns.md +5 -3
  175. package/skills/docs/artifact-authority-contract.md +30 -11
  176. package/skills/docs/confirmation-rules.md +1 -0
  177. package/skills/docs/contract-style-guide.md +37 -0
  178. package/skills/docs/copilot-loop-operations.md +19 -15
  179. package/skills/docs/cross-harness-regression-contract.md +60 -0
  180. package/skills/docs/debt-remediation-contract.md +1 -1
  181. package/skills/docs/epic-tree-refinement-procedure.md +25 -22
  182. package/skills/docs/issue-intake-procedure.md +26 -19
  183. package/skills/docs/local-planning-flow.md +1 -1
  184. package/skills/docs/local-planning-worked-example.md +1 -1
  185. package/skills/docs/merge-preconditions.md +30 -12
  186. package/skills/docs/plan-file-contract.md +1 -1
  187. package/skills/docs/pr-lifecycle-contract.md +44 -35
  188. package/skills/docs/public-dev-loop-contract.md +60 -44
  189. package/skills/docs/required-rules.json +149 -0
  190. package/skills/docs/retrospective-checkpoint-contract.md +106 -79
  191. package/skills/docs/spike-mode-contract.md +14 -7
  192. package/skills/docs/stop-conditions.md +30 -15
  193. package/skills/docs/tracker-first-loop-state.md +13 -8
  194. package/skills/docs/ui-e2e-scoping-step.md +32 -0
  195. package/skills/docs/validation-policy.md +4 -5
  196. package/skills/docs/workflow-handoff-contract.md +39 -0
  197. package/skills/local-implementation/SKILL.md +62 -145
  198. package/skills/loop-grill/SKILL.md +165 -0
  199. package/.claude/commands/continue.md +0 -15
  200. package/scripts/docs/validate-no-duplicate-rules.mjs +0 -250
  201. package/scripts/loop/conductor.mjs +0 -233
  202. package/scripts/loop/detect-stale-runner.mjs +0 -265
  203. package/scripts/loop/pre-push-main-guard.mjs +0 -117
@@ -1,6 +1,9 @@
1
1
  # Retrospective checkpoint contract
2
2
 
3
- This document defines the enforcement seam for the post-run behavioral retrospective checkpoint after qualifying async `dev-loop` completions in this repository. Whether a missing checkpoint blocks the next qualifying start/resume is controlled by `.devloops` at repo root `workflow.requireRetrospective`; shipped defaults remain permissive and this repo opts in.
3
+ Canonical owner for the enforcement seam of the post-run behavioral retrospective checkpoint after qualifying async `dev-loop` completions in this repository.
4
+
5
+ <!-- rule: RETRO-ENFORCEMENT-CONFIG-GATED -->
6
+ Whether a missing checkpoint blocks the next qualifying start/resume MUST be controlled by `.devloops` at repo root `workflow.requireRetrospective`; shipped defaults remain permissive and this repo opts in.
4
7
 
5
8
  ## Relationship to formal dev mode
6
9
 
@@ -85,67 +88,99 @@ Callers have two supported integration options:
85
88
  4. Call `evaluateRetrospectiveGate({ checkpointState, proposedRouting })`.
86
89
  5. Use the gate result (not the raw routing result) as the effective routing decision when enforcement is enabled; otherwise keep the raw routing result and treat the checkpoint artifact as advisory context only.
87
90
 
88
- If the gate result is `needs_reconcile`, the caller must not proceed with the proposed routing. The `nextAction` field instructs the operator to complete or explicitly skip the retrospective.
89
-
90
- ## Merge gate (`requireRetrospectiveGate`)
91
-
92
- When `workflow.requireRetrospectiveGate` is enabled in `.devloops` at repo root, merge-ready progression is blocked after `pre_approval_gate` unless the retrospective checkpoint:
93
- - has `state: "complete"`
94
- - includes a `behavioralReview` with `mergeApproved: true`, `followedWorkingAgreement` (boolean), `gateQualityAcceptable` (boolean), and `drifts` (array)
95
- - includes `mergeRecommendation` (non-empty string)
96
- - **(developer mode only)** includes `behavioralReview.internalToolingOnly: true` (boolean) and `behavioralReview.rawCallViolations: []` (empty array) see **Internal-tooling-only rule** below
97
-
98
- The enforcement function is `evaluateRetrospectiveMergeApproval(checkpoint, { developerMode })` in `packages/core/src/loop/pr-gate-coordination.mjs`, called from `evaluatePrGateCoordination` at each merge-ready boundary.
99
-
100
- ### Internal-tooling-only rule (issue #982) DEVELOPER MODE ONLY, opt-in
101
-
102
- This is a **developer-mode** retro step: it enforces the dev-loops maintainers' own dogfooding discipline and is **opt-in** via `workflow.requireRetrospectiveInternalTooling` (**default OFF**). **Consumers of the dev-loops extension are never blocked by it** — a consumer may legitimately use raw `gh`/`python`/`node -e` in their own workflow, so when the flag is OFF (the consumer default) the `internalToolingOnly`/`rawCallViolations` fields are neither required nor enforced, and a complete, merge-approved checkpoint passes exactly as it would without these fields. The check only requires and enforces them when `workflow.requireRetrospectiveInternalTooling: true` (the dev-loops repo opts in via its own repo-root `.devloops`).
103
-
104
- When developer mode is ON, the loop's own execution must use internal dev-loops tooling only. **Agent-level top-level raw calls are all the same breach and all block the merge gate:**
105
-
106
- - `gh ...` (including `gh api`, `gh ... --jq`) use `gate capture-threads`, `gate reply-resolve`, `dev-loops loop info`, `queue …`, or a `node scripts/...` wrapper instead
107
- - `python` / `python3` — parse JSON with the tool's `--jq`/built-in output, not an inline Python one-liner
108
- - `node -e` / `node --eval` inline eval to read/parse tool output is a violation; `node scripts/foo.mjs` is **not**
109
-
110
- **Allowed (NOT violations):** dev-loops subcommands, and `node scripts/*.mjs` invocations those scripts legitimately call `gh`/GraphQL internally; that is the tooling. The rule targets the agent's own top-level shell calls, not a script's internals.
111
-
112
- The retrospective author records the result in the checkpoint:
113
- - `behavioralReview.internalToolingOnly` (boolean) `true` only when the run used internal tooling throughout
114
- - `behavioralReview.rawCallViolations` (array of strings) one entry per agent-level raw call; empty when clean
115
-
116
- **Fail-closed (developer mode only):** when `requireRetrospectiveInternalTooling` is ON, the merge gate blocks (`retrospective_gate_pending`) if `internalToolingOnly` is not `true` **OR** `rawCallViolations` is missing/non-empty. `state: "complete"` alone is **not** sufficient — a clean tooling record is also required. When the flag is OFF (consumer default) this check is inert: it never blocks, even for a checkpoint that omits the fields or records a violation.
117
-
118
- **Back-compat:** an OLD checkpoint that omits `internalToolingOnly`/`rawCallViolations` only fails closed in developer mode; with the flag OFF (consumer default) it passes unchanged. Re-record the retrospective with the new fields to clear a developer-mode block.
119
-
120
- **Write-op allowlist (verifier only):** `gh pr merge`, `gh pr ready`, `gh issue create`, `gh issue edit` have no internal wrapper today. The deterministic verifier (below) records these as `allowedWriteOps` rather than violations so the gate is not blocked forever on an unavoidable gap. Close the gap with a wrapper to remove an entry. The gate itself still fails on any non-empty `rawCallViolations` the author records.
121
-
122
- ### Deterministic verifier
123
-
124
- `node scripts/loop/check-retro-tooling.mjs [--transcript <path>] [--json]` reads a newline-delimited transcript of the shell commands the agent ran (one top-level command per line, via `--transcript` or stdin) and reports agent-level raw `gh`/`python`/`python3`/`node -e`/`node --eval` calls. Use it to derive the `rawCallViolations` array the retrospective author records. Exit code `1` when violations are found, `0` when clean. The pure `analyzeTranscript(transcript)` export returns `{ violations, allowedWriteOps, internalToolingOnly }`.
125
-
126
- Matching rules: a tool name at the start of a command segment (start of line, or after `&&`/`||`/`|`/`;`); `node` is a violation only with `-e`/`--eval`. Before classifying, the verifier normalizes the segment head — it strips leading `NAME=value` env-assignment prefixes (`GH_TOKEN=x gh api`), strips a leading wrapper binary from `{sudo, env, xargs, time, nice, command}` (`sudo gh api`, `xargs gh api`), and reduces a path-prefixed binary to its basename (`./node_modules/.bin/gh`, `/usr/bin/python3`) — so the common prefixed/wrapped raw-call forms are caught. Known limitation: it does NOT fully parse shell quoting/substitution. A separator inside a quoted argument can over-report; deeply obfuscated calls (command substitution `$(...)`, aliases, `eval`) may evade it — prefer single-line, single-purpose commands in transcripts.
127
-
128
- ### Merge gate states
129
-
130
- | Retrospective state | Merge gate result |
131
- |---|---|
132
- | No checkpoint file | Blocked: `retrospective_gate_pending` |
133
- | `state: "complete"` with `mergeApproved: true` and valid base fields (consumer default; internal-tooling check OFF) | Allowed: proceeds to `FINAL_APPROVAL_READY` |
134
- | `state: "complete"` without `mergeApproved: true` | Blocked |
135
- | **Developer mode ON** + `internalToolingOnly` not `true`, or `rawCallViolations` missing/non-empty | Blocked |
136
- | **Developer mode OFF** (consumer default) + missing `internalToolingOnly`/`rawCallViolations` or a recorded violation | Allowed (check is inert) |
137
- | Missing base required fields (`followedWorkingAgreement`, `gateQualityAcceptable`, `drifts`, `mergeRecommendation`) | Blocked |
138
- | `state: "skipped"` or `state: "required"` | Blocked |
139
-
140
- ### Configuration
141
-
142
- ```yaml
143
- # .devloops at repo root
144
- workflow:
145
- requireRetrospective: true # startup/resume gate
146
- requireRetrospectiveGate: true # merge gate after pre_approval_gate
147
- requireRetrospectiveInternalTooling: true # developer-mode internal-tooling-only check (default OFF; consumers leave unset)
148
- ```
91
+ <!-- rule: RETRO-GATE-FAIL-CLOSED -->
92
+ If the gate result is `needs_reconcile`, the caller MUST NOT proceed with the proposed routing. The `nextAction` field instructs the operator to complete or explicitly skip the retrospective.
93
+
94
+ ## Advisory findings — never a merge gate (issue #1077, Reading B)
95
+
96
+ <!-- rule: RETRO-ADVISORY-NEVER-GATE -->
97
+ The retrospective is **advisory**: it runs, records flagged raw-call / discipline
98
+ observations honestly, and passes them back to the conductor (main agent) to
99
+ **decide** what to do with them but it MUST NOT block a merge or any lifecycle
100
+ transition. The pre-merge retrospective gate (`evaluateRetrospectiveMergeApproval`
101
+ and the `requireRetrospectiveGate` / `requireRetrospectiveInternalTooling` config
102
+ keys) has been **removed**. There is no `retrospective_gate_pending` / `blocked`
103
+ disposition on account of the internal-tooling raw-call record.
104
+
105
+ ### How findings travel (Reading B)
106
+
107
+ <!-- rule: RETRO-FINDINGS-ENVELOPE-CARRY -->
108
+ 1. **Deterministic return contract.** The loop subagent's handoff envelope MUST carry
109
+ the retrospective findings as a structured `retrospectiveFindings` fieldthe
110
+ `check-retro-tooling.mjs` JSON output (`{ internalToolingOnly, rawCallViolations,
111
+ allowedWriteOps }`), not prose. The conductor reads that field. This is a hard
112
+ contract; see [Workflow Handoff Contract](./workflow-handoff-contract.md).
113
+ 2. **Durabilityadvisory PR comment.** The conductor posts a single advisory PR
114
+ comment carrying the findings (`rawCallViolations`, `internalToolingOnly`,
115
+ `allowedWriteOps`). Durable and on-GitHub, but **not a gate**. No disk artifact
116
+ is written for retrospective *findings*.
117
+ 3. **No config.** There is nothing to configure: the retrospective always runs and
118
+ always returns findings. `requireRetrospectiveGate` and
119
+ `requireRetrospectiveInternalTooling` no longer exist.
120
+
121
+ A PR that is otherwise green becomes merge-ready with the violations **recorded**,
122
+ not blocked. The conductor may note them, open a follow-up, coach — or ignore.
123
+
124
+ ### Internal-tooling-only rule (issue #982) — now advisory
125
+
126
+ This rule records the dev-loops maintainers' own dogfooding discipline: the loop's
127
+ own execution should use internal dev-loops tooling, not agent-level raw
128
+ `gh`/`python`/`node -e` escape hatches. **It no longer blocks.** The flagged calls
129
+ are reported as advisory findings via the envelope + PR comment.
130
+
131
+ **Flagged as raw-call violations:** `gh ...` (including `gh api`, `gh ... --jq`),
132
+ `python` / `python3`, `node -e` / `node --eval` (inline eval). **Allowed (NOT
133
+ violations):** dev-loops subcommands and `node scripts/*.mjs` invocations — those
134
+ scripts legitimately call `gh`/GraphQL internally; that is the tooling. The rule
135
+ targets the agent's own top-level shell calls, not a script's internals.
136
+
137
+ **Write-op allowlist (verifier only):** `gh pr merge`, `gh pr ready`,
138
+ `gh issue create`, `gh issue edit` have no internal wrapper today. The deterministic
139
+ verifier records these as `allowedWriteOps` rather than violations so they are
140
+ surfaced distinctly, not as breaches. Close the gap with a wrapper to remove an
141
+ entry. None of these block anything.
142
+
143
+ **Inline-interpreter check item:** the raw-call scan below mechanically catches
144
+ `node -e`/`python3 -c`/heredoc calls as a `rawCallViolations` entry — the same
145
+ class barred by `OPS-NO-INLINE-INTERPRETER` in
146
+ [Copilot loop operations](copilot-loop-operations.md). This is an addition to
147
+ what the retrospective records, not a new gate: `RETRO-ADVISORY-NEVER-GATE`
148
+ semantics are unchanged.
149
+
150
+ ### Deterministic verifier (findings-producer)
151
+
152
+ `node scripts/loop/check-retro-tooling.mjs [--transcript <path>] [--json]` reads a
153
+ newline-delimited transcript of the shell commands the agent ran (one top-level
154
+ command per line, via `--transcript` or stdin) and reports agent-level raw
155
+ `gh`/`python`/`python3`/`node -e`/`node --eval` calls. It is a **findings-producer**:
156
+ its JSON output (`{ ok, internalToolingOnly, rawCallViolations, allowedWriteOps }`)
157
+ is returned to the conductor via the envelope's `retrospectiveFindings` field (the
158
+ envelope carries the normalized shape `{ internalToolingOnly, rawCallViolations,
159
+ allowedWriteOps }` — the redundant `ok` flag is dropped by normalization) — it
160
+ is **not** written to a checkpoint and **not** a gate. Exit code `1` when violations
161
+ are found, `0` when clean. The pure `analyzeTranscript(transcript)` export returns
162
+ `{ violations, allowedWriteOps, internalToolingOnly }`.
163
+
164
+ Matching rules: a tool name at the start of a command segment (start of line, or
165
+ after `&&`/`||`/`|`/`;`); `node` is a violation only with `-e`/`--eval`. Before
166
+ classifying, the verifier normalizes the segment head — it strips leading
167
+ `NAME=value` env-assignment prefixes (`GH_TOKEN=x gh api`), strips a leading
168
+ wrapper binary from `{sudo, env, xargs, time, nice, command}` (`sudo gh api`,
169
+ `xargs gh api`), and reduces a path-prefixed binary to its basename
170
+ (`./node_modules/.bin/gh`, `/usr/bin/python3`) — so the common prefixed/wrapped
171
+ raw-call forms are caught. Known limitation: it does NOT fully parse shell
172
+ quoting/substitution. A separator inside a quoted argument can over-report; deeply
173
+ obfuscated calls (command substitution `$(...)`, aliases, `eval`) may evade it —
174
+ prefer single-line, single-purpose commands in transcripts.
175
+
176
+ ### Lifecycle reconciliation
177
+
178
+ The retrospective is described consistently as a **post-merge / advisory
179
+ reflection**, never a pre-merge blocker. The former contradiction —
180
+ `lifecycle-state.mjs` documenting the retro as a post-merge write while
181
+ `pr-gate-coordination.mjs` enforced it pre-merge — is resolved by removing the
182
+ pre-merge gate: the merge lifecycle step proceeds, and the retrospective is an
183
+ advisory reflection whose findings reach the conductor via the envelope.
149
184
 
150
185
  ## Durable artifact format
151
186
 
@@ -162,25 +197,17 @@ The checkpoint file is written by `.pi/extensions/dev-loop-behavioral-review.ts`
162
197
 
163
198
  ### After retrospective is done (written by operator or skill)
164
199
 
165
- A minimal completion clears the startup gate. To also clear the **merge gate**
166
- (`requireRetrospectiveGate`), include the `behavioralReview`, `gateQuality`, and
167
- `mergeRecommendation` fields:
200
+ A minimal completion clears the startup/resume completion gate. The checkpoint
201
+ file carries **only completion state** — retrospective *findings*
202
+ (`behavioralReview`, `rawCallViolations`, `internalToolingOnly`) no longer live on
203
+ disk; they travel in the handoff envelope's `retrospectiveFindings` field and an
204
+ advisory PR comment (issue #1077, Reading B):
168
205
 
169
206
  ```json
170
207
  {
171
208
  "state": "complete",
172
209
  "completedAt": "2026-05-29T16:30:00.000Z",
173
- "notes": "Loop followed working agreement; minor drift on thread resolution.",
174
- "gateQuality": "Real gates with concrete findings and follow-through.",
175
- "mergeRecommendation": "Merge approved — all gates passed clean.",
176
- "behavioralReview": {
177
- "mergeApproved": true,
178
- "followedWorkingAgreement": true,
179
- "gateQualityAcceptable": true,
180
- "drifts": [],
181
- "internalToolingOnly": true,
182
- "rawCallViolations": []
183
- }
210
+ "notes": "Loop followed working agreement; minor drift on thread resolution."
184
211
  }
185
212
  ```
186
213
 
@@ -199,9 +226,9 @@ A minimal completion clears the startup gate. To also clear the **merge gate**
199
226
  | Artifact | Location |
200
227
  |---|---|
201
228
  | Checkpoint state machine | `packages/core/src/loop/retrospective-checkpoint.mjs` (internal core module; not part of the public package exports surface) |
202
- | Merge gate evaluator | `packages/core/src/loop/pr-gate-coordination.mjs` `evaluateRetrospectiveMergeApproval` |
203
- | Internal-tooling verifier | `scripts/loop/check-retro-tooling.mjs` (+ `test/loop/check-retro-tooling.test.mjs`) |
204
- | Tests | `packages/core/test/retrospective-checkpoint.test.mjs`, `packages/core/test/pr-gate-coordination.test.mjs` |
229
+ | Internal-tooling verifier (findings-producer) | `scripts/loop/check-retro-tooling.mjs` (+ `test/loop/check-retro-tooling.test.mjs`) |
230
+ | Advisory findings envelope field | `packages/core/src/loop/handoff-envelope.mjs` `retrospectiveFindings` |
231
+ | Tests | `packages/core/test/retrospective-checkpoint.test.mjs`, `packages/core/test/pr-gate-coordination.test.mjs`, `packages/core/test/handoff-envelope.test.mjs` |
205
232
  | Extension (writes required marker, fires review prompt) | `.pi/extensions/dev-loop-behavioral-review.ts` |
206
233
  | Checkpoint file | `.pi/dev-loop-retrospective-checkpoint.json` |
207
234
  | AGENTS.md repo contract | [Agent Instructions](../../AGENTS.md) — concise repo contract and working rules |
@@ -1,6 +1,6 @@
1
1
  # Spike-mode contract
2
2
 
3
- A spike is a time-boxed, exploratory dev-loop run started from a local question with no GitHub issue. Its deliverable is a findings document. When the exploration reaches a recommendation, the operator concludes it with one of two exits: **discard** (drop it with zero tracker artifacts) or **graduate** (emit a #947-style local-first plan file that enters the existing plan→PR promotion path).
3
+ Canonical owner for spike mode: the time-boxed, exploratory dev-loop run started from a local question with no GitHub issue. Its deliverable is a findings document. When the exploration reaches a recommendation, the operator concludes it with one of two exits: **discard** (drop it with zero tracker artifacts) or **graduate** (emit a #947-style local-first plan file that enters the existing plan→PR promotion path).
4
4
 
5
5
  This doc is the canonical operator sequence for spike mode and carries a worked example showing both exits. The spike artifact lives outside the tracker; it is exempt from the production-gate ceremony at entry and runs under a relaxed gate profile (`gates.spike`). The [Artifact Authority Contract](artifact-authority-contract.md) owns the local-planning model a graduated spike feeds into.
6
6
 
@@ -53,7 +53,10 @@ Hand the artifact to startup with `--spike` (mutually exclusive with `--issue`,
53
53
  node scripts/loop/resolve-dev-loop-startup.mjs --spike <spike.md>
54
54
  ```
55
55
 
56
- `buildSpikeInput` (`scripts/loop/resolve-dev-loop-startup.mjs`) reads the file read-only — no tracker mutation, no GitHub call, no issue or PR number. It requires the exploration scaffold; a missing/unreadable file, or one failing `validateSpikeExplorationSections`, throws and startup fails closed (exit 1, no readiness bundle). On success it builds a `local_phase` startup input with the resolved spike path as the target `phase` and threads `spikeIntakeState` onto the output. A spike with no Recommendation reports `spike_in_progress`. The spike path is exempt from the worktree-isolation guard because there is no issue to key a worktree on (`planFileExempt: true`).
56
+ `buildSpikeInput` (`scripts/loop/resolve-dev-loop-startup.mjs`) reads the file read-only — no tracker mutation, no GitHub call, no issue or PR number.
57
+
58
+ <!-- rule: SPIKE-STARTUP-SCAFFOLD-GATE -->
59
+ Startup MUST require the exploration scaffold: a missing/unreadable file, or one failing `validateSpikeExplorationSections`, MUST throw, and startup MUST fail closed (exit 1, no readiness bundle). On success it builds a `local_phase` startup input with the resolved spike path as the target `phase` and threads `spikeIntakeState` onto the output. A spike with no Recommendation reports `spike_in_progress`. The spike path is exempt from the worktree-isolation guard because there is no issue to key a worktree on (`planFileExempt: true`).
57
60
 
58
61
  ### 3. Run under the relaxed gate profile
59
62
 
@@ -67,7 +70,8 @@ When the exploration concludes, fill in `## Recommendation`. The intake state is
67
70
  node scripts/refine/exit-spike.mjs --spike-file <spike.md> --disposition <discard|graduate> [--plan-file <path>] [--json]
68
71
  ```
69
72
 
70
- `evaluateSpikeExit` (`packages/core/src/loop/spike-exit-contract.mjs`) is eligible only from `spike_ready_for_exit`. From any other state it fails closed with `not_ready_for_exit`; an unrecognized disposition fails closed with `unknown_disposition`. On a fail-closed path the CLI makes zero tracker mutation, writes no plan file, and exits 1.
73
+ <!-- rule: SPIKE-EXIT-ELIGIBILITY -->
74
+ `evaluateSpikeExit` (`packages/core/src/loop/spike-exit-contract.mjs`) MUST be eligible only from `spike_ready_for_exit`: from any other state it MUST fail closed with `not_ready_for_exit`, and an unrecognized disposition MUST fail closed with `unknown_disposition`. On a fail-closed path the CLI MUST make zero tracker mutation, write no plan file, and exit 1.
71
75
 
72
76
  ## Exit dispositions
73
77
 
@@ -75,22 +79,25 @@ The two dispositions are `SPIKE_EXIT_DISPOSITION.DISCARD` (`"discard"`) and `SPI
75
79
 
76
80
  ### discard
77
81
 
78
- The recommendation is "don't pursue". The spike is dropped with zero tracker artifacts; the findings document on disk is the whole record. The CLI writes nothing and creates no GitHub artifact. `--plan-file` is irrelevant for a discard.
82
+ <!-- rule: SPIKE-DISCARD-ZERO-MUTATION -->
83
+ The recommendation is "don't pursue". Discard MUST drop the spike with zero tracker artifacts — the findings document on disk is the whole record. The CLI MUST write nothing and MUST create no GitHub artifact. `--plan-file` is irrelevant for a discard.
79
84
 
80
85
  ### graduate
81
86
 
82
- The recommendation is "pursue this". `buildGraduatedPlanBody` builds a #947-consumable plan-file body from the spike's `Question`, `Approach`, `Findings`, and `Recommendation`, and `exit-spike.mjs` writes it to the required `--plan-file` path. The mapping:
87
+ <!-- rule: SPIKE-GRADUATE-PLAN-FILE-REQUIRED -->
88
+ The recommendation is "pursue this". `buildGraduatedPlanBody` builds a #947-consumable plan-file body from the spike's `Question`, `Approach`, `Findings`, and `Recommendation`, and `exit-spike.mjs` writes it to the required `--plan-file` path — `--plan-file` MUST be present for a graduate exit; the CLI MUST NOT guess an output path. The mapping:
83
89
 
84
90
  - `Question` + `Approach` become the Objective's context.
85
91
  - `Recommendation` becomes the In-scope work.
86
92
  - `Findings` are recorded as supporting evidence.
87
93
  - A fixed Explicit non-goals block keeps the plan from re-opening the concluded exploration.
88
94
 
89
- The emitted body carries the four plan-file base sections (`## Status`, `## Objective`, `## In scope`, `## Explicit non-goals` — see [Plan-file Contract](plan-file-contract.md)), so it passes `validatePlanFile` and enters the local-first plan→PR promotion path (#952) unchanged: refine it, hold the local human-review checkpoint, then `promote-plan.mjs`. Graduation is idempotent — `buildGraduatedPlanBody` is pure, so re-running reproduces the same plan file. `buildGraduatedPlanBody` fails closed (throws) on an empty required section, so a graduate exit cannot emit a plan the validator would reject. `--plan-file` is required for graduate (the CLI never guesses an output path).
95
+ The emitted body carries the four plan-file base sections (`## Status`, `## Objective`, `## In scope`, `## Explicit non-goals` — see [Plan-file Contract](plan-file-contract.md)), so it passes `validatePlanFile` and enters the local-first plan→PR promotion path (#952) unchanged: refine it, hold the local human-review checkpoint, then `promote-plan.mjs`. Graduation is idempotent — `buildGraduatedPlanBody` is pure, so re-running reproduces the same plan file, and it fails closed (throws) on an empty required section, so a graduate exit cannot emit a plan the validator would reject.
90
96
 
91
97
  ## Relaxed gate profile
92
98
 
93
- A spike's deliverable is a findings doc, so it does not carry the full production draft → pre-approval → Copilot gate set. `gates.spike` is a relaxed profile, shipped in `packages/core/src/config/extension-defaults.yaml`:
99
+ <!-- rule: SPIKE-RELAXED-GATE-PROFILE -->
100
+ A spike's deliverable is a findings doc, so it MUST NOT carry the full production draft → pre-approval → Copilot gate set; it runs under `gates.spike`, a relaxed profile shipped in `packages/core/src/config/extension-defaults.yaml`:
94
101
 
95
102
  ```yaml
96
103
  gates:
@@ -2,29 +2,44 @@
2
2
 
3
3
  Canonical owner for agent stop / wait / block conditions across all workflow families.
4
4
 
5
+ ## Terms
6
+
7
+ | Term | Definition |
8
+ |---|---|
9
+ | <!-- term: state:blocked --> `blocked` | Lifecycle state requiring a human decision before the loop continues. |
10
+ | <!-- term: state:done --> `done` | Terminal lifecycle state. |
11
+ | <!-- term: state:approval_ready --> `approval_ready` | Lifecycle state where approval evidence is ready but merge authorization is not implied. |
12
+ | <!-- term: state:merge_ready --> `merge_ready` | Lifecycle state where merge preconditions are ready to evaluate. |
13
+ | <!-- term: state:waiting --> `waiting` | Lifecycle state for healthy external waits. |
14
+ | <!-- term: state:waiting_for_initial_copilot_implementation --> `waiting_for_initial_copilot_implementation` | Bootstrap wait state for the first Copilot PR. |
15
+ | <!-- term: state:waiting_for_copilot_review --> `waiting_for_copilot_review` | Copilot review-settle wait state for the current head. |
16
+ | <!-- term: state:waiting_for_merge_authorization --> `waiting_for_merge_authorization` | Stop state for merge-ready work without explicit merge authorization. |
17
+ | <!-- term: reason:needs_reconcile --> `needs_reconcile` | Reconcile reason for ambiguous, contradictory, or unsupported state. |
18
+
5
19
  ## Genuine stop conditions
6
20
 
7
- | Condition | Strategy | Behavior |
8
- |---|---|---|
9
- | `blocked` lifecycle state | all | Stop for human decision |
10
- | `done` lifecycle state | all | Terminal stop |
11
- | `approval_ready` without explicit merge auth | `final_approval` | Stop at approval gate |
12
- | `merge_ready` without explicit merge auth | all | Stop at `waiting_for_merge_authorization` |
13
- | `autonomy.humanMergeOnly: true` | all | Always stop at merge for human action, even with explicit merge auth agent never runs `gh pr merge` (see [Merge preconditions](merge-preconditions.md)) |
14
- | Ambiguous / contradictory state | all | Fail closed to `needs_reconcile` |
15
- | Missing authoritative startup inputs | `dev-loop` | Fail closed |
21
+ | Rule ID | Condition | Strategy | Behavior |
22
+ |---|---|---|---|
23
+ | <!-- rule: STOP-BLOCKED-001 --> `STOP-BLOCKED-001` | `blocked` lifecycle state | all | The loop MUST stop for a human decision. |
24
+ | <!-- rule: STOP-DONE-001 --> `STOP-DONE-001` | `done` lifecycle state | all | The loop MUST treat the state as terminal. |
25
+ | <!-- rule: STOP-APPROVAL-001 --> `STOP-APPROVAL-001` | `approval_ready` without explicit merge authorization | `final_approval` | The loop MUST stop at the approval gate. |
26
+ | <!-- rule: STOP-MERGE-AUTH-001 --> `STOP-MERGE-AUTH-001` | `merge_ready` without explicit merge authorization | all | The loop MUST stop at `waiting_for_merge_authorization`. |
27
+ | <!-- rule: STOP-HUMAN-MERGE-001 --> `STOP-HUMAN-MERGE-001` | `autonomy.humanMergeOnly: true` | all | The loop MUST stop at merge for human action even with explicit merge authorization; the agent MUST NOT run `gh pr merge` (see [Merge preconditions](merge-preconditions.md)). |
28
+ | <!-- rule: STOP-RECONCILE-001 --> `STOP-RECONCILE-001` | Ambiguous or contradictory state | all | The loop MUST fail closed to `needs_reconcile`. |
29
+ | <!-- rule: STOP-STARTUP-INPUTS-001 --> `STOP-STARTUP-INPUTS-001` | Missing authoritative startup inputs | `dev-loop` | The loop MUST fail closed. |
16
30
 
17
31
  ## Non-stop conditions
18
32
 
19
- | Condition | Strategy | Behavior |
20
- |---|---|---|
21
- | `waiting` lifecycle state | `wait_watch` | Healthy wait; re-dispatch from main session |
22
- | `waiting_for_initial_copilot_implementation` | `issue_intake` | Bootstrap wait with 1h watch budget |
23
- | `waiting_for_copilot_review` | `copilot_pr_followup` | Continuation boundary, not completion |
24
- | Quiet watcher observations | `wait_watch` | Observational only, do not surface |
33
+ | Rule ID | Condition | Strategy | Behavior |
34
+ |---|---|---|---|
35
+ | <!-- rule: STOP-WAIT-001 --> `STOP-WAIT-001` | `waiting` lifecycle state | `wait_watch` | The loop MUST treat this as a healthy wait and re-dispatch from the main session. |
36
+ | <!-- rule: STOP-INITIAL-COPILOT-001 --> `STOP-INITIAL-COPILOT-001` | `waiting_for_initial_copilot_implementation` | `issue_intake` | The loop MUST use the bootstrap wait with a one-hour watch budget. |
37
+ | <!-- rule: STOP-COPILOT-REVIEW-001 --> `STOP-COPILOT-REVIEW-001` | `waiting_for_copilot_review` | `copilot_pr_followup` | The loop MUST treat this as a continuation boundary, not completion. |
38
+ | <!-- rule: STOP-QUIET-WATCHER-001 --> `STOP-QUIET-WATCHER-001` | Quiet watcher observations | `wait_watch` | The loop MUST treat quiet observations as observational only and MUST NOT surface them as stops by themselves. |
25
39
 
26
40
  ## Cross-references
27
41
 
28
42
  - [Confirmation rules](confirmation-rules.md)
29
43
  - [Merge preconditions](merge-preconditions.md)
30
44
  - [Public Dev Loop Contract](public-dev-loop-contract.md)
45
+ - [Contract style guide](contract-style-guide.md)
@@ -1,7 +1,8 @@
1
1
  # Tracker-First Story-to-PR Contract
2
2
 
3
- This document defines the adapter-agnostic MVP contract for the tracker-first
4
- story-to-PR workflow in `dev-loops`.
3
+ Canonical owner for the adapter-agnostic MVP contract for the tracker-first
4
+ story-to-PR workflow in `dev-loops`, and for the tracker-first loop state
5
+ machine (#449).
5
6
 
6
7
  **MVP invariant: one tracker work item → one GitHub PR.**
7
8
 
@@ -30,7 +31,8 @@ Implementation for each:
30
31
  | Epic / PRD reference | Optional linked metadata only; no roll-up or automation in this slice |
31
32
  | ADR / RFC reference | Optional linked metadata only; no decision-sync in this slice |
32
33
 
33
- **Invariant:** For any single tracker work item in scope, there is at most
34
+ <!-- rule: TRACKER-ONE-ACTIVE-PR -->
35
+ For any single tracker work item in scope, there MUST be at most
34
36
  one active GitHub PR at a time. Multi-PR workflows and roll-up automation
35
37
  are out of scope for this slice.
36
38
 
@@ -57,6 +59,9 @@ already-correct PR leaves it unchanged.
57
59
 
58
60
  ### 3.1 Required PR Metadata
59
61
 
62
+ <!-- rule: TRACKER-PROJECTION-REQUIRED-METADATA -->
63
+ Every projected PR MUST carry the following fields:
64
+
60
65
  | Field | Rule |
61
66
  |---|---|
62
67
  | PR title | `[{TRACKER_ID}] {tracker item title}` — bracketed tracker identifier followed by the item title verbatim |
@@ -64,7 +69,7 @@ already-correct PR leaves it unchanged.
64
69
  | PR body — Summary section | Required. Brief description of the change implemented in this PR. |
65
70
  | PR body — Acceptance Criteria section | Required. Copied or linked from the tracker item's acceptance criteria. |
66
71
  | Labels | Required: `tracker:{TRACKER_ID}`. Optional reference labels: `epic:{EPIC_ID}`, `prd:{PRD_ID}` when references are present. |
67
- | Draft state | PR must start as a draft. It must not be marked ready-for-review until development work is complete. |
72
+ | Draft state | PR MUST start as a draft. It MUST NOT be marked ready-for-review until development work is complete. |
68
73
 
69
74
  ### 3.2 Optional Reference Metadata
70
75
 
@@ -85,9 +90,9 @@ They are read-only metadata in this slice and do not trigger any automation:
85
90
  2. **On tracker item field change**: Re-apply projection rules to PR title,
86
91
  body, and labels. Leave any sections not covered by projection rules
87
92
  (e.g. reviewer-added review comments) intact.
88
- 3. **Idempotent regeneration**: If the PR already has the correct title, body
89
- sections, and labels, no mutation is performed. Projection is a no-op
90
- when the current state already matches the target.
93
+ 3. <!-- rule: TRACKER-PROJECTION-IDEMPOTENT --> **Idempotent regeneration**: Projection MUST be a no-op when the PR already has the correct
94
+ title, body sections, and labels re-applying the rules to an already-correct PR MUST NOT
95
+ mutate it.
91
96
 
92
97
  ### 3.4 PR Body Template
93
98
 
@@ -120,7 +125,7 @@ implementations map canonical action names to tracker-native field updates.
120
125
  | `pr_merged` — PR merged | `set_done` | Tracker moves to done/completed terminal state |
121
126
  | `pr_closed_unmerged` — PR closed without merge | `none` | No automatic terminal transition; human decision required |
122
127
  | `no_tracker_item` | `none` | No tracker item to update |
123
- | `blocked_needs_user_decision` | `none` | Stop and report; no automatic tracker update |
128
+ | <!-- rule: TRACKER-BLOCKED-FAIL-CLOSED --> `blocked_needs_user_decision` | `none` | An unexpected or contradictory snapshot MUST stop and report; the helper MUST NOT apply an automatic tracker update |
124
129
 
125
130
  ### 4.2 Event Triggers
126
131
 
@@ -96,6 +96,38 @@ to match `UI_E2E_CHECK_NAMES` so the gate can read a real signal:
96
96
  So a deck- or article-only PR has a CI check that can actually satisfy the gate;
97
97
  without these jobs such a PR would fail closed with no satisfiable signal.
98
98
 
99
+ ## Verifying a UI change: render and look, but don't persist ad-hoc checks
100
+
101
+ When you change a rendered artifact's layout, **verify it visually before merging** —
102
+ render the built page and inspect real geometry (`getBoundingClientRect` for
103
+ edges/widths, a full-page screenshot to actually look at it) at desktop and mobile
104
+ widths. This intermediary e2e pass is a required *verification strategy*: a layout
105
+ claim ("the columns align now") is only credible once measured against the rendered
106
+ page, not the source CSS.
107
+
108
+ It is **not** something to persist into the codebase. The registered, always-on
109
+ coverage is the shared fit/CSP suite (`defineArticleSuite`/`defineDeckSuite`); do
110
+ not grow it with one-off pixel/alignment assertions written to confirm a single
111
+ fix. Use a throwaway script to render, measure, and screenshot while iterating,
112
+ then drop it. Adding a bespoke geometry assertion per fix bloats the suite and
113
+ couples it to incidental layout numbers.
114
+
115
+ ## An assertion must be able to fail on the defect
116
+
117
+ A UI assertion that is **true for the broken state validates the bug**. Real
118
+ example (the layout regression this rule comes from): a check asserted
119
+ `margin-left > 1px` on the prose to prove it was "centered" — but the visible bug
120
+ *was* the prose floating in a narrow centered measure, misaligned from the
121
+ full-width boxes beside it. `margin-left` was large in exactly the broken state, so
122
+ the check passed green on every gate while the page rendered visibly broken.
123
+
124
+ Before trusting any UI assertion, confirm it **fails on the defect**: run it against
125
+ the broken layout, or inject the defect (e.g. shove one block's left edge) and check
126
+ that the assertion goes red. An assertion that cannot fail on the bug it names is
127
+ not coverage — it is false confidence. Prefer asserting the invariant that actually
128
+ defines "correct" (e.g. all content blocks share one left edge within tolerance)
129
+ over a proxy that happens to correlate with it.
130
+
99
131
  ## Non-goals
100
132
 
101
133
  Not always-on screenshot testing; not mandatory multi-browser. The criterion is
@@ -4,9 +4,8 @@ Canonical owner for validation requirements across all workflow families.
4
4
 
5
5
  ## Default validation
6
6
 
7
- - `npm run verify` is the default repo-level local validation path
8
- - Must pass before: PR creation, gate entry, merge
9
- - At minimum: `npm test && npm run test:dev-loop`
7
+ <!-- rule: VALIDATE-VERIFY-BEFORE-GATE -->
8
+ `VALIDATE-VERIFY-BEFORE-GATE`: `npm run verify` is the default repo-level local validation path and MUST pass before PR creation, gate entry, and merge; at minimum this means `npm test && npm run test:dev-loop`.
10
9
 
11
10
  ## Gate-specific requirements
12
11
 
@@ -17,8 +16,8 @@ Canonical owner for validation requirements across all workflow families.
17
16
 
18
17
  ## Coverage requirements
19
18
 
20
- - ≥90% coverage for lines, statements, functions, and branches on changed files
21
- - Test-first for all non-trivial logic
19
+ <!-- rule: VALIDATE-COVERAGE-THRESHOLD -->
20
+ `VALIDATE-COVERAGE-THRESHOLD`: Changed files MUST have ≥90% coverage for lines, statements, functions, and branches, and non-trivial logic MUST be test-first.
22
21
 
23
22
  ## Cross-references
24
23
 
@@ -17,6 +17,35 @@ template.
17
17
  | Gate state (detectors) + strategy defaults | `currentGate`, `worktreeRequired` |
18
18
  | Settings (`.devloops` at repo root + `defaults.yaml`) | `gateConfig`, `stopRules`, `asyncStartMode`, `requireDraftFirst`, `maxCopilotRounds` |
19
19
  | Gate state (detectors) | `currentHeadSha`, `ciStatus`, `unresolvedThreadCount`, `copilotRoundCount` |
20
+ | Canonical sanctioned-command map (`scripts/loop/sanctioned-commands.mjs`) | `sanctionedCommands` |
21
+
22
+ ## Sanctioned commands (MANDATORY DEFAULT — issue #1081)
23
+
24
+ `sanctionedCommands` is a **mandatory default** element of every handoff
25
+ envelope. It is the operation → wrapper command map (which wrapper performs
26
+ which GitHub/loop operation), plus the forbidden and orchestrator-owned lists.
27
+ The `loop build-envelope` CLI injects it into every emitted envelope, so a
28
+ spawned dev-loop subagent receives it verbatim without the briefer adding it —
29
+ no re-deriving which wrapper does `gh pr ready` etc.
30
+
31
+ The **single source of truth** is `scripts/loop/sanctioned-commands.mjs` (a
32
+ frozen data module). `@dev-loops/core` stays consumer-agnostic: it defines the
33
+ envelope SHAPE and carries whatever `sanctionedCommands` object the consumer
34
+ supplies; the repo-specific `scripts/...` paths live only in the consumer
35
+ module. Do not duplicate the map into prose — reference the module.
36
+
37
+ ```typescript
38
+ sanctionedCommands?: {
39
+ reads: Record<string, string>; // operation → wrapper path (some also accept `loop info`)
40
+ edits: Record<string, string>;
41
+ lifecycle: Record<string, string>;
42
+ forbidden: string[]; // raw `gh pr view/checks/edit`, `node -e`, `python -c`, transcript tailing, sleep-poll loops
43
+ orchestratorOwned: string[]; // `gh pr merge`, board status transitions — never done by a subagent
44
+ };
45
+ ```
46
+
47
+ A contract test (`test/contracts/sanctioned-commands-exist.test.mjs`) asserts
48
+ every mapped wrapper exists on disk and fails closed if one is renamed/removed.
20
49
 
21
50
  ## Acceptance templates
22
51
 
@@ -111,6 +140,15 @@ interface HandoffEnvelope {
111
140
  scopeConstraint?: string;
112
141
  customStopAt?: string;
113
142
  };
143
+
144
+ // Mandatory default (issue #1081). Source: scripts/loop/sanctioned-commands.mjs
145
+ sanctionedCommands?: {
146
+ reads: Record<string, string>;
147
+ edits: Record<string, string>;
148
+ lifecycle: Record<string, string>;
149
+ forbidden: string[];
150
+ orchestratorOwned: string[];
151
+ };
114
152
  }
115
153
  ```
116
154
 
@@ -121,6 +159,7 @@ interface HandoffEnvelope {
121
159
  3. Execute `nextAction`.
122
160
  4. Respect `stopRules` — do not proceed past a gated stop point without authorization.
123
161
  5. Use `acceptance` to self-validate before declaring completion.
162
+ 6. Use `sanctionedCommands` as the authoritative operation → wrapper map: never call a raw `gh`/`node -e`/`python -c` for an operation the map covers, and never perform an `orchestratorOwned` action.
124
163
 
125
164
  ## Backward compatibility
126
165