dev-loops 0.5.0 → 0.7.1

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 (177) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +2 -1
  3. package/.claude/agents/review.md +2 -1
  4. package/.claude/commands/loop-auto.md +7 -0
  5. package/.claude/commands/loop-continue.md +15 -0
  6. package/.claude/commands/loop-enqueue.md +22 -0
  7. package/.claude/commands/loop-grill.md +17 -0
  8. package/.claude/commands/loop-info.md +7 -0
  9. package/.claude/commands/loop-queue-status.md +24 -0
  10. package/.claude/commands/loop-start-spike.md +16 -0
  11. package/.claude/commands/loop-start.md +7 -0
  12. package/.claude/commands/loop-status.md +6 -0
  13. package/.claude/hooks/_bash-command-classify.mjs +333 -29
  14. package/.claude/hooks/_hook-decisions.mjs +138 -15
  15. package/.claude/hooks/_run-context.mjs +11 -4
  16. package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
  17. package/.claude/skills/copilot-pr-followup/SKILL.md +24 -12
  18. package/.claude/skills/dev-loop/SKILL.md +19 -10
  19. package/.claude/skills/docs/acceptance-criteria-verification.md +6 -2
  20. package/.claude/skills/docs/anti-patterns.md +2 -1
  21. package/.claude/skills/docs/artifact-authority-contract.md +22 -4
  22. package/.claude/skills/docs/copilot-loop-operations.md +1 -1
  23. package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
  24. package/.claude/skills/docs/issue-intake-procedure.md +1 -0
  25. package/.claude/skills/docs/local-planning-flow.md +1 -1
  26. package/.claude/skills/docs/local-planning-worked-example.md +1 -1
  27. package/.claude/skills/docs/merge-preconditions.md +17 -1
  28. package/.claude/skills/docs/plan-file-contract.md +1 -1
  29. package/.claude/skills/docs/public-dev-loop-contract.md +1 -1
  30. package/.claude/skills/docs/release-runbook.md +45 -0
  31. package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
  32. package/.claude/skills/docs/ui-e2e-scoping-step.md +134 -0
  33. package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
  34. package/.claude/skills/local-implementation/SKILL.md +26 -15
  35. package/.claude/skills/loop-grill/SKILL.md +163 -0
  36. package/AGENTS.md +1 -0
  37. package/CHANGELOG.md +125 -0
  38. package/README.md +29 -10
  39. package/agents/dev-loop.agent.md +11 -3
  40. package/agents/developer.agent.md +1 -1
  41. package/agents/docs.agent.md +1 -1
  42. package/agents/fixer.agent.md +1 -1
  43. package/agents/quality.agent.md +1 -1
  44. package/agents/refiner.agent.md +1 -1
  45. package/agents/review.agent.md +3 -2
  46. package/cli/index.mjs +35 -40
  47. package/extension/README.md +4 -4
  48. package/extension/checks.ts +1 -5
  49. package/extension/harness-types.ts +1 -0
  50. package/extension/index.ts +16 -1
  51. package/extension/pi-extension-adapter.ts +2 -0
  52. package/extension/presentation.ts +16 -13
  53. package/lib/dev-loops-core.mjs +141 -0
  54. package/package.json +10 -8
  55. package/scripts/claude/generate-claude-assets.mjs +15 -1
  56. package/scripts/github/capture-review-threads.mjs +2 -9
  57. package/scripts/github/comment-issue.mjs +174 -0
  58. package/scripts/github/create-label.mjs +133 -0
  59. package/scripts/github/detect-checkpoint-evidence.mjs +170 -14
  60. package/scripts/github/detect-linked-issue-pr.mjs +22 -6
  61. package/scripts/github/edit-pr.mjs +259 -0
  62. package/scripts/github/fetch-ci-logs.mjs +208 -0
  63. package/scripts/github/list-issues.mjs +184 -0
  64. package/scripts/github/manage-sub-issues.mjs +46 -10
  65. package/scripts/github/offer-human-handoff.mjs +8 -5
  66. package/scripts/github/post-gate-findings.mjs +14 -2
  67. package/scripts/github/probe-ci-status.mjs +8 -2
  68. package/scripts/github/probe-copilot-review.mjs +21 -14
  69. package/scripts/github/ready-for-review.mjs +26 -8
  70. package/scripts/github/reconcile-draft-gate.mjs +7 -3
  71. package/scripts/github/reply-resolve-review-thread.mjs +16 -5
  72. package/scripts/github/reply-resolve-review-threads.mjs +69 -7
  73. package/scripts/github/request-copilot-review.mjs +8 -2
  74. package/scripts/github/resolve-handoff-candidates.mjs +7 -3
  75. package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
  76. package/scripts/github/stage-reviewer-draft.mjs +10 -2
  77. package/scripts/github/tick-verified-checkboxes.mjs +202 -0
  78. package/scripts/github/upsert-checkpoint-verdict.mjs +31 -10
  79. package/scripts/github/verify-fresh-review-context.mjs +150 -31
  80. package/scripts/github/view-pr.mjs +150 -0
  81. package/scripts/github/write-gate-context.mjs +248 -61
  82. package/scripts/github/write-gate-findings-log.mjs +75 -1
  83. package/scripts/lib/jq-output.mjs +18 -0
  84. package/scripts/loop/_handoff-contract.mjs +1 -0
  85. package/scripts/loop/_post-convergence-change.mjs +211 -0
  86. package/scripts/loop/_pr-runner-coordination.mjs +70 -0
  87. package/scripts/loop/_repo-root-resolver.mjs +47 -0
  88. package/scripts/loop/build-handoff-envelope.mjs +14 -4
  89. package/scripts/loop/check-retro-tooling.mjs +14 -3
  90. package/scripts/loop/checkpoint-contract.mjs +7 -4
  91. package/scripts/loop/cleanup-worktree.mjs +12 -3
  92. package/scripts/loop/conductor-monitor.mjs +12 -18
  93. package/scripts/loop/copilot-pr-handoff.mjs +135 -14
  94. package/scripts/loop/debt-remediate.mjs +24 -12
  95. package/scripts/loop/detect-change-scope.mjs +36 -7
  96. package/scripts/loop/detect-copilot-loop-state.mjs +11 -5
  97. package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
  98. package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
  99. package/scripts/loop/detect-internal-only-pr.mjs +8 -2
  100. package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
  101. package/scripts/loop/detect-pr-gate-coordination-state.mjs +179 -66
  102. package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
  103. package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
  104. package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
  105. package/scripts/loop/ensure-worktree.mjs +8 -3
  106. package/scripts/loop/info.mjs +12 -4
  107. package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
  108. package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
  109. package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
  110. package/scripts/loop/inspect-run-viewer.mjs +67 -4
  111. package/scripts/loop/inspect-run.mjs +8 -2
  112. package/scripts/loop/outer-loop.mjs +8 -4
  113. package/scripts/loop/pr-runner-coordination.mjs +2 -9
  114. package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
  115. package/scripts/loop/pre-flight-gate.mjs +9 -9
  116. package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
  117. package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
  118. package/scripts/loop/provision-worktree.mjs +74 -3
  119. package/scripts/loop/resolve-dev-loop-startup.mjs +76 -7
  120. package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
  121. package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
  122. package/scripts/loop/run-conductor-cycle.mjs +13 -2
  123. package/scripts/loop/run-queue.mjs +18 -9
  124. package/scripts/loop/run-refinement-audit.mjs +8 -9
  125. package/scripts/loop/run-watch-cycle.mjs +2 -9
  126. package/scripts/loop/sanctioned-commands.mjs +104 -0
  127. package/scripts/loop/steer-loop.mjs +29 -16
  128. package/scripts/loop/validate-pr-body-spec.mjs +207 -0
  129. package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
  130. package/scripts/pages/build-site.mjs +59 -8
  131. package/scripts/pages/build-state-atlas.mjs +441 -0
  132. package/scripts/projects/_resolve-project.mjs +148 -0
  133. package/scripts/projects/add-queue-item.mjs +60 -54
  134. package/scripts/projects/archive-done-items.mjs +49 -84
  135. package/scripts/projects/ensure-queue-board.mjs +10 -36
  136. package/scripts/projects/list-queue-items.mjs +116 -65
  137. package/scripts/projects/move-queue-item.mjs +28 -49
  138. package/scripts/projects/reconcile-queue.mjs +253 -0
  139. package/scripts/projects/reorder-queue-item.mjs +41 -47
  140. package/scripts/projects/resolve-active-board-item.mjs +255 -0
  141. package/scripts/projects/sync-item-status.mjs +15 -10
  142. package/scripts/refine/_refine-helpers.mjs +21 -5
  143. package/scripts/refine/exit-spike.mjs +18 -8
  144. package/scripts/refine/promote-plan.mjs +22 -16
  145. package/scripts/refine/prose-linkage-detector.mjs +1 -1
  146. package/scripts/refine/refine-plan-file.mjs +13 -4
  147. package/scripts/refine/refinement-completeness-checker.mjs +1 -1
  148. package/scripts/refine/scaffold-spike-file.mjs +179 -0
  149. package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
  150. package/scripts/refine/tree-integrity-validator.mjs +1 -1
  151. package/scripts/refine/validate-plan-file.mjs +1 -1
  152. package/scripts/refine/validate-spike-file.mjs +1 -1
  153. package/scripts/refine/verify.mjs +11 -6
  154. package/scripts/release/assert-core-dependency-version.mjs +123 -0
  155. package/scripts/release/extract-changelog-section.mjs +125 -0
  156. package/skills/copilot-pr-followup/SKILL.md +24 -12
  157. package/skills/dev-loop/SKILL.md +22 -6
  158. package/skills/docs/acceptance-criteria-verification.md +6 -2
  159. package/skills/docs/anti-patterns.md +2 -1
  160. package/skills/docs/artifact-authority-contract.md +22 -4
  161. package/skills/docs/copilot-loop-operations.md +1 -1
  162. package/skills/docs/cross-harness-regression-contract.md +60 -0
  163. package/skills/docs/issue-intake-procedure.md +1 -0
  164. package/skills/docs/local-planning-flow.md +1 -1
  165. package/skills/docs/local-planning-worked-example.md +1 -1
  166. package/skills/docs/merge-preconditions.md +17 -1
  167. package/skills/docs/plan-file-contract.md +1 -1
  168. package/skills/docs/public-dev-loop-contract.md +1 -1
  169. package/skills/docs/release-runbook.md +45 -0
  170. package/skills/docs/retrospective-checkpoint-contract.md +90 -76
  171. package/skills/docs/ui-e2e-scoping-step.md +134 -0
  172. package/skills/docs/workflow-handoff-contract.md +39 -0
  173. package/skills/local-implementation/SKILL.md +26 -15
  174. package/skills/loop-grill/SKILL.md +165 -0
  175. package/scripts/loop/conductor.mjs +0 -233
  176. package/scripts/loop/detect-stale-runner.mjs +0 -265
  177. package/scripts/loop/pre-push-main-guard.mjs +0 -117
@@ -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
 
@@ -39,11 +39,17 @@ Treat missing optional files as normal bootstrap conditions, not errors.
39
39
 
40
40
  ### Tracker-backed local implementation
41
41
 
42
- Local implementation supports two durable spec inputs:
42
+ Local implementation supports these spec-of-record inputs. The first two are durable/committed artifacts; the third is a spec-of-record that is NOT a durable committed artifact:
43
+
44
+ Durable/committed spec artifacts:
43
45
 
44
46
  - phase-doc-backed local sessions ([Phase Plan](../../docs/phases/phase-x.md) is canonical)
45
47
  - tracker-backed local sessions (the tracker issue is canonical)
46
48
 
49
+ Non-durable spec-of-record (no committed plan artifact):
50
+
51
+ - lightweight PR-body-as-spec sessions (`--lightweight`; the PR description is the canonical spec-of-record but NOT a durable committed artifact — the resolver output carries `canonicalSpecSource: pr_body` and the derived handoff envelope carries `specSource: pr_body`; no phase/plan doc minted or committed). Same gate sequence as the phase-doc path; only the backing artifact differs. See [Artifact Authority Contract](../docs/artifact-authority-contract.md) "Lightweight (PR-body-as-spec)".
52
+
47
53
  Tracker-backed local implementation stays inside the existing `local_implementation` path. For sub-issue tree decomposition, see [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md) (this is a source-repo reference; it is not part of the bundled `../docs/` runtime contract surface for installed skill copies). It does not introduce a new routing mode.
48
54
 
49
55
  When the local spec already lives in a tracker issue:
@@ -126,33 +132,33 @@ Follow [Anti-patterns](../docs/anti-patterns.md) for the general tooling-interna
126
132
 
127
133
  Apply [Structural Quality](../docs/structural-quality.md) from the `deep` review angle.
128
134
 
129
- ## Light mode (small changes) — config-only
135
+ ## Light mode (small changes)
136
+
137
+ Light mode is wired into gate dispatch. A PR that is **under threshold** (≤ `maxLines` lines changed AND ≤ `maxFiles` files, per `.devloops` field `localImplementation.lightMode` — this repo sets `maxLines: 20`, `maxFiles: 2`; the shipped built-in default has light mode disabled (`enabled: false`); if enabled without explicit values it falls back to `maxFiles: 3` / `maxLines: 200`) AND carries no `gate:full` label collapses BOTH `draft_gate` and `pre_approval_gate` to a single inline single-agent correctness + no-op check. The Copilot review request and its polling are skipped.
130
138
 
131
- Light mode is currently **config-only**: the schema, resolver, and scope detector are implemented, but no functional wiring exists yet in the local-implementation flow. When scope is small enough, the intent is to skip fan-out/fan-in and use a single review pass instead. Light mode will still require validation and pre-approval gate once wired.
139
+ **Escalation:**
140
+ 1. Auto-escalate — if the inline check surfaces any finding whose severity is in the gate's `blockCleanOnFindingSeverities` (i.e. ≥ `worth-fixing-now`), dispatch escalates to full fan-out.
141
+ 2. Label override — the `gate:full` label forces full fan-out regardless of PR size.
132
142
 
133
- **Eligibility:** ≤3 files AND ≤200 lines changed (configurable via `.devloops` field `localImplementation.lightMode`).
143
+ The `draft_gate` boundary (draft ready) is still recorded because `requireDraftFirst` is honored: light mode only changes HOW the gate runs (inline vs fan-out), not WHETHER the draft boundary exists.
134
144
 
135
- Use `scripts/loop/detect-change-scope.mjs` to determine eligibility:
145
+ Dispatch is resolved deterministically via `resolveGateDispatchMode(config, gate, { scope, hasFullLabel, inlineFindingSeverities })` from `@dev-loops/core/config`. `scripts/loop/resolve-gate-dispatch.mjs` is the CLI wrapper the orchestrator calls, combining `detect-change-scope.mjs` output with the `gate:full` label fact.
146
+
147
+ Use `scripts/loop/detect-change-scope.mjs` to determine scope:
136
148
  ```sh
137
149
  node scripts/loop/detect-change-scope.mjs
138
150
  ```
139
151
 
140
- **Planned light mode path (not yet wired):**
141
- 1. Validation (`npm run verify`)
142
- 2. Single review pass (not multi-angle fan-out)
143
- 3. Pre-approval gate
144
- 4. Finalization
145
-
146
152
  **Override threshold:**
147
153
  ```yaml
148
154
  localImplementation:
149
155
  lightMode:
150
156
  enabled: true
151
- maxFiles: 5
152
- maxLines: 300
157
+ maxFiles: 2
158
+ maxLines: 20
153
159
  ```
154
160
 
155
- Disabled by default (opt-in). Scope above threshold falls back to full fan-out/fan-in path.
161
+ Scope above threshold falls back to the full fan-out/fan-in path.
156
162
 
157
163
  ## Deterministic logging structure
158
164
 
@@ -260,6 +266,10 @@ For the **current phase only**, run this loop before implementation.
260
266
 
261
267
  ### 1. Create or update the durable phase doc and tmp scaffold
262
268
 
269
+ **Lightweight (PR-body-as-spec) exception:** when the session is lightweight — the resolver output carries `canonicalSpecSource: pr_body` and the derived handoff envelope carries `specSource: pr_body` (started via `resolve-dev-loop-startup.mjs --issue <n> --lightweight`) — SKIP the durable phase-doc mint entirely. The PR description is the spec-of-record; do NOT create or commit any `docs/phases/*.md` for this session. The ephemeral `tmp/phases/` scaffold may still be used for local execution state. All other steps (read prior learning, plan, implement) proceed unchanged, and the gate sequence (draft → pre-approval fanout → detect-evidence → human merge) is identical. See [Artifact Authority Contract](../docs/artifact-authority-contract.md) "Lightweight (PR-body-as-spec)".
270
+
271
+ Otherwise (default phase-doc path), create or update the durable phase doc.
272
+
263
273
  Use paths like:
264
274
  - `docs/phases/phase-0.md`
265
275
  - `tmp/phases/phase-0/`
@@ -508,7 +518,7 @@ After the phase plan passes review:
508
518
  3. Run local validation:
509
519
  - `npm run verify`
510
520
  - when a narrower local check is more honest for the touched slice, say so explicitly and run the narrowest justified subset instead of pretending the full verify path was unnecessary
511
- - for user-facing HTML/UI/component slices when the user opts in, add a bounded deterministic browser smoke harness (prefer fixture-backed Playwright WebKit plus screenshot capture); use `npm run test:playwright:viewer` when that viewer/browser surface is part of the slice, and wire it into CI once it becomes required validation for that slice
521
+ - for a *rendered* HTML artifact (a deck `docs/presentations/*.html`, an article `docs/articles/*.html`, or the inspect-run viewer source) UI e2e coverage is **required and auto-scoped** — not opt-in. Touching one obliges you to register it in the shared harness and have a passing UI e2e suite, or the gate fails closed (see [UI e2e scoping step](../docs/ui-e2e-scoping-step.md)); run the matching shared suite locally, e.g. `npm run test:playwright:intro-deck` / `:intro-article` / `:viewer`. For a bespoke local UI surface that is not a registered rendered artifact, the WebKit smoke seam (fixture-backed Playwright WebKit plus screenshot capture) remains available without being mandatory
512
522
  - **Commit immediately after validation passes.** Once `npm run verify` (or the narrowest justified validation subset) is green, stage and commit the current slice of work with an atomic commit message. For tracker-backed sessions, also push the commit. In non-interactive subagent sessions, commit authorization is implicit — the subagent was dispatched to implement and must commit before exiting. In interactive sessions, wait for coordination agent authorization. Do not defer commits — uncommitted changes when the session terminates are a workflow defect.
513
523
  4. Review the implementation against the merged phase plan.
514
524
  5. Run the default pre-approval gate as a full review / fix loop on the branch before calling it review-complete, approval-ready, merge-ready, or ready for final handoff:
@@ -639,6 +649,7 @@ See [Stop Conditions](../docs/stop-conditions.md). Local-specific stops: phase c
639
649
  - PR creation always goes through `dev-loops pr create`, which is ALWAYS draft and always assigned — self-assigned by default (`--assignee @me` when none is given; honors an explicit `--assignee <login>` / `-a <login>`), never via raw `gh pr create`, and the PR body must contain `Closes #N` (or `Fixes #N`) for the linked issue so GitHub auto-closes it on merge. When `.devloops` sets `workflow.requireDraftFirst` to true, use `dev-loops pr create --assignee @me ...`. 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 eligible.
640
650
  - When authorization is pending, record the phase as `awaiting-finalization` and describe the exact missing step.
641
651
  - For phase-doc-backed sessions, merge the fully reviewed, locally validated branch back into local `main` when authorized.
652
+ - Behind-branch integration before merge (for example a sibling PR merged first and both touch shared files): prefer a merge commit (`git merge origin/main`) over rebase + force-push. Since dev-loop PRs are squash-merged, intermediate branch history is discarded at merge time, so a merge commit lands an identical result on `main` while avoiding a non-fast-forward push to the remote. Force-push (`--force-with-lease` only, never bare `--force`) remains acceptable only for a branch the loop solely owns and where no integration alternative exists (rare); document that carve-out rather than leaving it implicit. After any integration that changes the head SHA, re-verify gate evidence at the new head (existing behavior — see [Merge Preconditions](../docs/merge-preconditions.md#required-before-merge)).
642
653
 
643
654
  ## Commit policy
644
655
 
@@ -0,0 +1,163 @@
1
+ ---
2
+ name: "loop-grill"
3
+ description: "Standalone pre-loop Socratic Q&A grill for issues (tracker-first) or local plan files (local-planning). Detects spec gaps, asks clarifying questions (interactive) or self-answers them from codebase context (--auto), then writes a ## Grill findings section back to the source artifact."
4
+ allowed-tools: Read Bash Edit Write
5
+ user-invocable: false
6
+ ---
7
+ <!-- GENERATED from skills/loop-grill/SKILL.md by scripts/claude/generate-claude-assets.mjs — do not edit; edit the source and regenerate. -->
8
+
9
+ # Loop-grill skill
10
+
11
+ A standalone, on-demand pre-loop grilling skill. Run it against an issue or a local plan file **before** the dev loop starts to surface underspecified acceptance criteria, fuzzy scope boundaries, unresolved primary actors, and undocumented hard-to-reverse decisions.
12
+
13
+ It is entirely separate from the in-loop docs-grill (`docs/docs-grill-step.md`), which audits code/doc drift while the loop runs. This skill operates on the *spec* before any implementation begins.
14
+
15
+ ## Interface
16
+
17
+ ```
18
+ /loop-grill <issue-number> # tracker-first, interactive
19
+ /loop-grill <issue-number> --auto # tracker-first, auto-answer
20
+ /loop-grill <path/to/plan.md> # local-planning, interactive
21
+ /loop-grill <path/to/plan.md> --auto # local-planning, auto-answer
22
+ ```
23
+
24
+ ## Argument validation (fail-closed)
25
+
26
+ Before doing anything else:
27
+
28
+ 1. Confirm exactly one positional argument is present (issue number or path). No argument → error, stop.
29
+ 2. Confirm the only optional flag is `--auto`. Any other flag → error, stop.
30
+ 3. **Tracker-first:** verify the issue exists. Non-existent issue → `Error: issue #<n> not found.`, stop.
31
+ 4. **Local-planning:** verify the file exists. Missing file → `Error: plan file not found: <path>`, stop.
32
+ 5. Never mutate any artifact when argument validation fails.
33
+
34
+ ## Step 1 — Load the target
35
+
36
+ - **Tracker-first:** fetch the issue body (title + description + any existing `## Grill findings` section).
37
+ - **Local-planning:** read the plan file from disk.
38
+
39
+ ## Step 1b — Surface external resources
40
+
41
+ Before detecting gaps, scan the loaded content for external resource references: links, other repo URLs, API endpoints, doc URLs, screenshots, or Playwright navigation descriptors.
42
+
43
+ - **Interactive mode:** if external resources are present, ask the operator to confirm they are accessible or to provide them before the Q&A starts.
44
+ - **`--auto` mode:** attempt to fetch each resource using bounded wrapper commands (e.g. `gh`, API wrapper scripts under `scripts/`). Do not fetch resources inline with raw `curl` or token-heavy calls. Flag any inaccessible resource as `unresolved` in the findings rather than silently skipping it.
45
+ - When no external resources are present, skip this step silently.
46
+
47
+ ## Step 2 — Detect gaps
48
+
49
+ Scan the loaded content and identify each gap. The minimum required gap detectors are:
50
+
51
+ | Gap kind | Detection signal |
52
+ |---|---|
53
+ | Missing acceptance criteria | No `## Acceptance criteria` section, or section is empty / stub |
54
+ | Missing scope boundary | No explicit in-scope / out-of-scope statement or non-goals section |
55
+ | Unresolved primary actor | No named user, system, or role that is the main beneficiary of the feature |
56
+ | Undocumented hard-to-reverse decision | Destructive or irreversible operations described without a rationale or rollback note |
57
+
58
+ Additional gaps discovered through semantic reading of the spec are also recorded.
59
+
60
+ For each gap, classify it as either:
61
+ - **Bounded choice** — the answer is one of a small discrete set (e.g. yes/no, A/B/C).
62
+ - **Open-ended** — the answer requires free-form elaboration.
63
+
64
+ ## Step 3 — Fill gaps
65
+
66
+ ### Interactive mode (default)
67
+
68
+ For each gap, in order:
69
+
70
+ - **Bounded choice gap:** use `AskUserQuestion` with the question text and the choice options, plus an "Other / free text" option. Block until the user answers.
71
+ - **Open-ended gap:** present the question as a plain text turn. Block until the user answers.
72
+
73
+ Record each answer with its source: `human`.
74
+
75
+ > `AskUserQuestion` is a Claude Code–native construct. If you are running outside Claude Code, use `--auto` mode instead.
76
+
77
+ ### Auto mode (`--auto`)
78
+
79
+ Answer every grilling question yourself without prompting the user. Source answers from (in priority order):
80
+
81
+ 1. **`codebase`** — inspectable source files, tests, scripts, config in the repository.
82
+ 2. **`docs`** — markdown files under `docs/`, `skills/docs/`, and adjacent contract docs.
83
+ 3. **`context`** — `CONTEXT.md` at the repo root, if present. When absent, skip silently — do not crash, no warning required.
84
+ 4. **`inferred`** — reasoning from the issue/plan text alone, with no external citation.
85
+
86
+ Record the evidence source for every answer. Flag a question as **`unresolved`** when:
87
+ - The only available source is `inferred`, **and**
88
+ - No codebase path, doc section, or issue/plan text can be cited as the basis for the answer.
89
+
90
+ Do not silently guess an `inferred` answer when no evidence can be cited — flag it `unresolved` instead.
91
+
92
+ ## Step 4 — Write back (replace-section semantics)
93
+
94
+ Write a `## Grill findings` section back to the source artifact using **replace-section** semantics:
95
+
96
+ - **Find** the existing `## Grill findings` section: the range from the `## Grill findings` heading through the next `##`-level heading (exclusive) or end of file.
97
+ - **Replace** that range in place with the new section content.
98
+ - If no `## Grill findings` section exists, **append** it.
99
+ - This makes re-runs idempotent — no accumulated noise, no duplicate sections.
100
+ - If parsing the section boundary fails, **abort with an error** rather than silently truncating.
101
+
102
+ **Tracker-first write-back:** update the GitHub issue body using:
103
+
104
+ ```
105
+ gh issue edit <n> --repo <owner/repo> --body-file <tmp-path>
106
+ ```
107
+
108
+ The `## Grill findings` section lives in the issue body, not as a comment. Do not use `comment-issue.mjs` here — that creates a comment, not a body update.
109
+
110
+ **GitHub body size guard:** issue bodies are capped at 65,536 characters. Before writing back, check whether the updated body would exceed this limit. If so, warn: `Warning: updated body would exceed GitHub's 65,536-character limit — write-back skipped. Truncate the findings or the issue body manually.` Do not silently truncate.
111
+
112
+ **Local-planning write-back:** update the plan file in place using the edit tool.
113
+
114
+ ## Output artifact format
115
+
116
+ The `## Grill findings` section written to the artifact:
117
+
118
+ ```markdown
119
+ ## Grill findings
120
+
121
+ <!-- loop-grill: <timestamp> mode:<interactive|auto> -->
122
+
123
+ ### Resolved gaps
124
+
125
+ | # | Gap | Question | Answer | Source |
126
+ |---|-----|----------|--------|--------|
127
+ | 1 | Missing AC | <question text> | <answer text> | codebase \| docs \| context \| inferred \| human |
128
+
129
+ ### Unresolved gaps
130
+
131
+ | # | Gap | Question | Reason unresolved |
132
+ |---|-----|----------|-------------------|
133
+ | 1 | Unresolved primary actor | <question text> | No citable evidence found |
134
+
135
+ ### Verdict
136
+
137
+ grill-clean
138
+ ```
139
+
140
+ Replace `grill-clean` with `N unresolved items` when unresolved gaps remain.
141
+
142
+ ## Step 5 — Emit verdict
143
+
144
+ After write-back, emit the verdict line to stdout:
145
+
146
+ - `grill-clean` when no unresolved gaps remain.
147
+ - `N unresolved items` (e.g. `3 unresolved items`) when gaps remain after all questions are answered.
148
+
149
+ ## CONTEXT.md degradation rule
150
+
151
+ When `CONTEXT.md` is absent from the repo root, skip the context-source check silently. Do not crash. Do not emit a warning. The grill continues with the remaining sources (`codebase`, `docs`, `inferred`).
152
+
153
+ ## Idempotency guarantee
154
+
155
+ Running `/loop-grill` twice on the same target must produce a single `## Grill findings` section, not two. The replace-section logic handles the already-present-section case. On the second run, if the gap set is identical to the first run, the section content is replaced with an equivalent section (same questions, same answers, updated timestamp).
156
+
157
+ ## Non-goals
158
+
159
+ - Auto-triggering from `issue_intake` — this is on-demand only.
160
+ - Replacing or modifying the in-loop docs-grill (`docs/docs-grill-step.md`, `scripts/loop/docs-grill-contract.mjs`) — different concern, different firing surface.
161
+ - Full DDD `CONTEXT.md` management.
162
+ - Scheduling or storing grill runs — stateless and on-demand.
163
+ - Any CI/CD integration.
package/AGENTS.md CHANGED
@@ -23,3 +23,4 @@
23
23
  - Keep workflow procedure out of AGENTS; put shared contracts under `skills/docs/`. Brief, high-signal main-agent dispatch contracts (e.g., sequential dispatch) are the narrow exception — they stay in AGENTS.md because AGENTS.md is the canonical main-agent instruction surface.
24
24
  - No PR scope has gate exemptions (#579): see skills/dev-loop/SKILL.md "No gate exemptions" section.
25
25
  - **Sequential dispatch contract (#693):** When the user says "sequential", "one at a time", "serially", or "in order" with multiple `dev-loop` targets, the main agent must dispatch `dev-loop` async subagents one at a time — each blocking on prior completion before the next starts. This is deterministic: the keyword must always produce serial execution, never concurrent. The main agent is the sequencer; do not dispatch all targets at once.
26
+ - **Harness-agnostic non-regression (#1086):** dev-loops is harness-agnostic (Pi + Claude Code). Any harness-specific change MUST NOT regress other harnesses — add/keep cross-harness regression coverage. Canonical procedure: [Cross-Harness Regression Contract](skills/docs/cross-harness-regression-contract.md).
package/CHANGELOG.md CHANGED
@@ -4,6 +4,131 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.7.1 - 2026-07-05
8
+
9
+ ### Changed
10
+
11
+ - **`captureDiffFromBase` pins `diff.renames`/`diff.algorithm`/`diff.context` and `core.abbrev`/`core.autocrlf` for cross-environment reproducibility (#1168).** The isolation `-c` overrides for the persisted `--base` neutral diff seed already made the diff byte-identical within a single run, but a machine with a contrary local gitconfig could still produce different bytes for the same base/HEAD pair — and, via `diff.renames`, a different `scope.changedFiles`/`adjacentCode` membership (rename vs delete+add). The five settings are now pinned alongside the existing overrides.
12
+
13
+ ### Removed
14
+
15
+ - **Dead `isFenceLine` field dropped from `stepFence` (#1166).** `stepFence` (`packages/core/src/loop/issue-refinement-artifact.mjs`) computed and returned `isFenceLine` on every branch, but none of its three call sites (`parseMarkdownSections`, `extractChecklistItems`, `sectionHasBody`) read it. Removed from the return shape and its JSDoc; `fence`/`insideFence` and all fence-detection logic are unchanged.
16
+
17
+ ### Fixed
18
+
19
+ - **Worktree provisioning creates the `@dev-loops/core` workspace link so `scripts/**` CLIs validate the branch's core (#1144).** Worktrees provisioned by `ensure-worktree.mjs`/`provision-worktree.mjs` have no `node_modules`, so any `scripts/**` CLI importing `@dev-loops/core` resolved the bare specifier by walking up to the MAIN checkout's `node_modules/@dev-loops/core` instead of the worktree's own `packages/core` — `npm run verify` in a worktree silently tested main's core, not the branch's. Provisioning now creates the relative symlink `node_modules/@dev-loops/core → ../../packages/core` unconditionally: idempotent on a correct existing link, replaces a stale/broken link, never clobbers a real file/dir already at the destination, and is reported in the provisioning summary.
20
+ - **Sibling logical columns resolved via `loadStateColumnMap` in pickup and archive (#1143).** `resolve-active-board-item.mjs` hardcoded the literal `"In Progress"` column for the live `/loop-continue` pickup query instead of resolving it through `loadStateColumnMap`/`LOGICAL_COLUMN.IN_PROGRESS` the way #1142 already did for Next Up, so a repo overriding `queue.statusColumns.in_progress` would have pickup silently miss the active item. An audit found the same gap in `archive-done-items.mjs` (`selectArchivable()` hardcoded `"Done"`). Both now resolve through the shared mapping, fail closed on a malformed `.devloops`, and honor configured overrides; `--help`/display literals in other scripts were left as-is (illustrative examples, not routing logic).
21
+ - **The post-convergence significance detector is content-aware, so comment/JSDoc-only changes don't reopen Copilot past the round cap (#1137).** Significance in `_post-convergence-change.mjs` (shared by `copilot-pr-handoff` and `detect-pr-gate-coordination-state`) was classified by path/size only, so a trivial comment fix could re-trip a re-request past the cap. A new `isCommentOnlyFileChange` classifier inspects the compare diff's patch hunks with a per-hunk block-comment state machine (reset at every `@@` header, conservative toward "significant") and filters comment-only JS/TS files out before the existing `>=20 lines`/`>=2 files` thresholds. A file with no `patch` (binary/oversized) is never treated as comment-only; the stale `+++`/`---` header-skip (never present in `gh compare` patches) was removed.
22
+ - **`copilot-pr-handoff` fails closed on a pending Copilot review at the round cap (#1165).** At the cap with a `--force-rerequest-review` in flight, handoff routed to `ROUND_CAP_CLEAN_FALLBACK` ("continue to pre_approval_gate") whenever the fail-silent secondary reopen-facts fetch (`gh pr view` + `gh compare`) failed — silently reading significance as false — while `detect-pr-gate-coordination-state`, reusing its already-validated PR data, correctly gated; the loop follows handoff as the pre-flight authority, so a significant change could skip Copilot's review (hit live on PR #1164). While a requested review is pending on the current head at the cap, handoff now surfaces `waiting_for_copilot_review` (action `watch`) before the fragile escape-hatch fetch can downgrade to "proceed"; a `--watch-status` refresh skips the guard so a request that never resolves re-settles to the clean fallback and the loop cannot dead-end.
23
+ - **Merge-block hook message explains that evidence writes and `gh pr merge` must be separate tool calls (#1172).** The "vanishing" gate-evidence ledgers on PRs #1167/#1169/#1171 were never swept: the PreToolUse bash gate evaluates `gh pr merge` before the tool call executes, so a compound command that both writes gate evidence (`write-gate-findings-log`/`upsert-checkpoint-verdict`) and merges in the same call is blocked at hook time — the write never runs. `decideBashGate` now appends a hint to the deny message in exactly that compound case (a bare `gh pr merge` keeps the standard message; no allow/deny boundary changes), and the write → verify → merge-alone contract is documented in `skills/docs/merge-preconditions.md`.
24
+ - **Light-mode-aware pre-merge gate accepts scoped inline verdicts (#1174).** `buildPreMergeGateCheck` rejected any `inline_single_agent` verdict whenever `gates.requireFanoutEvidence` was on (the repo default), so a genuinely under-threshold micro-PR's #1043 light-path verdict was unmergeable without a full fan-out (hit live on PR #1173, 1 file / 11 lines). An inline verdict is now accepted only when lightMode is enabled, the merge-base scope re-derived at merge time is under threshold (underivable scope → rejected, fail-closed), no `gate:full` label is present, and a non-empty inline reason is recorded; the findings-log ledger is still required, and the fan-out path (`fanout_fanin` verdicts, ledger, provenance enforcement) is unchanged.
25
+ - **`validate-pr-body-spec` enforces a `Closes #N` closing-keyword reference (#1181).** Five lightweight PRs merged without auto-closing their issues because the PR-body-as-spec validator never required the linkage. It now fails closed with `missing_closing_issue_reference` when the body carries no GitHub closing-keyword issue reference, supports `--expected-issue <n>` (`closes_wrong_issue` on a mismatch), and reports the extracted `closesIssues` — all keyword variants and the cross-repo `owner/repo#N` form accepted, while references inside fenced code blocks or inline code spans don't count.
26
+
27
+ ## 0.7.0 - 2026-07-05
28
+
29
+ ### Added
30
+
31
+ - **Lightweight local-first path — PR body as spec-of-record, no phase doc (#1025).** Local-first now supports a lightweight path for chores, bugfixes, and small mechanical changes where the PR description itself is the spec-of-record — no phase/plan doc is minted or committed. When active, `loop startup` no longer requires `--plan-file` and the pre-approval review agent reads the PR body (objective, in-scope/non-goals, testable acceptance criteria, definition of done, risks) instead of resolving a plan-file path; a new `validate-pr-body-spec` CLI enforces the required sections fail-closed. The gate sequence is unchanged (draft → pre-approval fanout → detect-evidence → human merge); only the backing artifact differs. Closes the phase-history pollution from minting a numbered phase doc for a six-file rename chore.
32
+ - **`lightMode` wired to gate dispatch — micro-PRs skip full fanout (#1043).** A PR under the `localImplementation.lightMode` threshold (`maxFiles`/`maxLines`, and no `gate:full` label) collapses the `draft_gate` + `pre_approval_gate` fanout to a single inline correctness + no-op check and skips Copilot polling; the draft→ready boundary is still recorded (`requireDraftFirst`). Two escalation paths: the inline check auto-escalates to full fanout on any finding at or above `worth-fixing-now`, and the maintainer-controlled `gate:full` label forces full fanout regardless of size. Scope detection fails closed — a diff whose size can't be determined runs the full fanout, not the light path.
33
+ - **Context-builder additive angle authority (#1048).** Gate angle resolution was subtractive-only (it could drop configured angles but never add one). With `gates.<gate>.additiveAngles` enabled (default off), the resolver may now pull in a review lens the diff's change categories imply even when it isn't in the gate's configured pool — drawn from `gates.anglePool` (or the union of known personas). Bounded and auditable: `excludeAngles` stays a hard ceiling, `mandatoryAngles` the floor, and each addition is recorded with an `"added"` rationale naming the triggering category alongside the existing kept/dropped entries.
34
+ - **Grouped board-summary mode for `queue list` (#1061).** `dev-loops queue list --summary` (alias `--group-by status`) returns a whole-board digest grouped by Status column in board order (`{ ok, groups: { <status>: { count, items } } }`), with `--done-limit` to cap the Done group, composing with `--jq`/`--silent`. One sanctioned call for the by-status overview that previously tempted an inline `| python3` grouping (retro rule 5); `--summary` is mutually exclusive with `--column`/`--limit`.
35
+ - **Cross-harness regression contract (#1086).** dev-loops is harness-agnostic (Pi + Claude Code); the rule "any harness-specific change MUST NOT regress other harnesses" is now a repo-wide working rule in `AGENTS.md`, with the detailed procedure in `skills/docs/cross-harness-regression-contract.md` (what counts as a harness-specific change, the required Claude-Code-path suites, and the additive-on-Pi / no-op-or-validated-on-Claude bar). Promotes the constraint from an ad-hoc section of #1084 to a universal, pre-0.7 enforced contract.
36
+
37
+ ### Changed
38
+
39
+ - **`LOGIC_CHANGE` maps to a core review subset instead of fallback-to-all (#1049).** `dynamicAngles` was effectively decorative for code PRs: any non-trivial code change classified as `ambiguous`, which fell back to the full 15-angle draft pool. `LOGIC_CHANGE` now maps to a core-for-code angle subset in `CATEGORY_ANGLE_MAP` and no longer forces `ambiguous`/fallback-to-all (reserved for genuinely unclassifiable diffs); other categories still union in their mapped angles. A typical code PR now resolves to a handful of angles, with peripheral lenses (link-check, config-drift, etc.) appearing only when the diff implicates their category. Mandatory floor and exclude ceiling unchanged; each dropped angle keeps its recorded rationale.
40
+ - **`--jq`/`--silent` are now a base-CLI guarantee for every JSON-emitting command (#1071).** The output contract was per-script opt-in, so JSON-emitting commands like `sync-item-status.mjs` rejected `--jq` and forced grep/inline-parse workarounds. Every JSON-emitting command now routes through the shared emit path (`scripts/lib/jq-output.mjs`), so `--jq` filtering, invalid-filter fail-closed (exit 2), and `--silent` exit-code-only come for free; a contract test enforces it so new commands inherit the guarantee and can't regress.
41
+ - **Sanctioned operation→wrapper command map baked into the default handoff (#1081).** Which dev-loops wrapper to use for which operation (reads: `view-pr`/`probe-ci-status`/`fetch-ci-logs`/`list-issues`/`probe-copilot-review`; edits: `edit-pr`/`comment-issue`; lifecycle: `ready-for-review`/`create-pr`/`request-copilot-review`/`reply-resolve`/gate-verdict) is now a structural part of every dev-loop handoff, sourced once (`scripts/loop/sanctioned-commands.mjs`, carried via the handoff envelope + `agents/dev-loop.agent.md`) instead of an ad-hoc per-handoff decision — so a spawned subagent no longer re-derives the map or rediscovers a wrapper mid-run.
42
+ - **Playwright configs consolidated into one projects-based, registry-driven config (#1056).** The five byte-identical `playwright.*.config.mjs` files (one per UI slice) collapse to a single `playwright.config.mjs` with one Playwright project per registered slice; `test:playwright:<slice>` scripts run `--project=<slice>`. Adding a UI artifact now needs only a registry entry (already required by the ui-e2e-scoping gate) and no new config file; per-slice output dirs / report paths are preserved.
43
+ - **Deduplicated the three Playwright smoke jobs via a composite action (#1058).** `viewer-smoke` / `deck-smoke` / `article-smoke` shared copy-pasted setup (checkout, Node, `npm ci`, WebKit cache-restore, `playwright install`). The shared setup moves into `.github/actions/playwright-webkit/action.yml`, keeping the three separate jobs and their zero-cost `changes`-gated skips (a matrix would have paid setup on skipped legs). `UI_E2E_CHECK_NAMES` and the scoping contract are unaffected.
44
+ - **Faster `test:scripts` real-git tests (#1129).** Real-git fixtures paid `git` fsync on every object write; `test:scripts` now runs with `core.fsync=none` / `core.fsyncObjectFiles=false` (via `GIT_CONFIG_*`), cutting the process-spawn-heavy suite's wall time with no behavior change. The originally-proposed concurrent single-pool `verify` runner was reverted (oversubscription risk); the fsync disable is the kept win.
45
+
46
+ ### Removed
47
+
48
+ - **`mermaid` is no longer a runtime dependency (#1089).** The inspect-run viewer serves the same pinned `mermaid.min.js` (11.15.0) from a vendored copy at `scripts/loop/inspect-run-viewer/vendor/mermaid.min.js` instead of resolving it out of `node_modules`; the `/assets/mermaid.min.js` route, `loadMermaidBrowserScript` seam, and offline behavior are unchanged. The vendored bundle is sha256-pinned by test (`test/loop/inspect-run-viewer-client.test.mjs`) and `test/extension-package-contract.test.mjs` guards that mermaid stays out of `dependencies`. Net install footprint shrinks (the mermaid transitive tree no longer installs); the npm tarball grows ~3.3MB unpacked from the vendored asset.
49
+ - **Breaking: dead-code deletion — ponytail audit batch 1 (#1088).** Removed unused public surface with no in-repo callers: the `@dev-loops/core` export subpaths `./debt/signals`, `./debt/score`, `./debt/finding`, and `./refinement/ac-dod-matrix` along with the now-dead `refinementContract` handoff-envelope plumbing (builder pass-through and validation in `handoff-envelope.mjs`) that only that module fed (the underlying `debt/score.mjs` module remains and is still tested internally); the `dev-loops-capture-deep-persona-signals` bin and the whole deep-persona-signals feature (`packages/core/src/debt/deep-persona-signals.mjs`); `createClaudeExtensionAdapter` (removed from the `@dev-loops/core/harness` exports along with its `claude-extension-adapter.mjs` module); the dead OUTER-graph tables `OUTER_STATE_TO_ROUTING_OUTCOME`, `OUTER_STATE_TO_OUTER_ACTION`, and `OUTER_NONTERMINAL_STATES` from `@dev-loops/core/loop/conductor-routing`; the config surface `localPlanning.plansDir` with its resolver `resolvePlansDir`, plus the unrelated dead config resolvers `resolveInternalPathPatterns` and `resolveMaxFanoutReviewers`; and the script CLIs `scripts/loop/conductor.mjs`, `scripts/loop/detect-stale-runner.mjs`, and `scripts/loop/pre-push-main-guard.mjs` (with their tests). Deprecation shim: a `localPlanning` block in a consumer `.devloops` file is still **accepted and ignored** (tolerated deprecated key in both zod schemas and `schemas/dev-loop-config.schema.json`) so existing configs keep parsing; remove the block at your convenience.
50
+
51
+ ### Fixed
52
+
53
+ - **Guard `draft_gate` self-heal against unbounded recursion on a lagged draft-state read (#1020).** `upsert-checkpoint-verdict`'s `postDraftGateViaDraftTransition` converts a `ready` PR to draft, re-posts the verdict, then restores ready — but when GitHub's draft-state read lagged the conversion mutation, the re-entry saw the PR still non-draft and recursed, hanging with a swallowed Node exit 13. A `_draftTransitionInProgress` reentrancy guard now breaks the loop and fails closed with the complete manual recovery commands instead of hanging.
54
+ - **Resolve repo root and ledgers worktree-relative, not from `process.cwd()` (#1052; folds #1050 and #1019).** Under the normal per-PR-worktree flow the session cwd and the PR worktree are different checkouts, so cwd-bound reads/writes silently diverged: the pre-merge fan-out ledger check bound to the session cwd and false-blocked clean gates (#1050, hit PR #1044 twice), and `detect-copilot-loop-state` read `maxCopilotRounds` from cwd instead of the worktree `.devloops` (#1019). A shared worktree resolver now derives the repo root from the PR worktree, so the ledger writer and the pre-merge check bind to the same checkout regardless of session cwd. Pure resolution-location correctness — no gate semantics or ledger-format change.
55
+ - **Block subagent-initiated external writes (#1051).** Gate/dev-loop subagents had twice minted unauthorized, user-attributed GitHub issues. The bash gate now blocks raw `gh issue create` / `gh issue comment` / `gh pr comment` on the target repo when initiated by a subagent and directs to the sanctioned path; contract-driven writes (gate-verdict comments, reply-resolve review threads, board sync) and the main-agent/operator path are unaffected. Unit + e2e coverage mirrors the `gh pr create` guard.
56
+ - **Wrapped `gh pr view` / `gh pr edit` so the retro internal-tooling gate is satisfiable (#1057).** The remaining PR-facing reads/edits a real gate run needs had no dev-loops wrapper, so an honest run could never record a clean retrospective (`internalToolingOnly: true`, `rawCallViolations: []`) and the gate blocked code-clean PRs by waiver. New `scripts/github/view-pr.mjs` and `edit-pr.mjs` (jq-output-backed, `--jq`/`--silent`) complete the coverage; `workflow.requireRetrospectiveInternalTooling` is re-armed to `true`.
57
+ - **Tick verified PR-body acceptance-criteria checkboxes after a clean `pre_approval_gate` (#1066).** The gate verified acceptance criteria but only updated the linked issue, so merged PRs displayed all `- [ ]` unchecked, reading as never-confirmed. A single `gh pr edit --body-file` now flips `- [ ]` → `- [x]` for the items the gate positively confirmed (CRLF preserved, duplicate flipped labels de-duped, only verified items ticked, unconfirmable items left unchecked — fail closed).
58
+ - **Key fresh-context review sentinels per head SHA / round (#1095).** On a `draft_gate` retry after a fix commit, the round-1 per-(cwd, scope) sentinel from `verify-fresh-review-context.mjs` was still present, so every retry reviewer failed closed and burned a launch to refuse. The sentinel key now includes the head-SHA round component, so a new head never collides with a prior round while same-round re-entry still fails closed; the orchestrator-owned sentinel lifecycle is documented in the gate-review sub-loop contract.
59
+ - **Honor `queue.statusColumns.next_up` across ordering, pickup, and the projects layer (#1098).** Board-sync resolved logical columns through the `statusColumns` display-name overrides, but the ordering (`resolveNextUpOrder`) and `add-queue-item --next-up` hardcoded the literal `"Next Up"`, so a repo that renamed its Next Up column got a driver querying a non-existent column (fail-closed board-query-error or empty). The logical→display resolution now flows through the same shared mapping everywhere; board-sync stays the single source of the mapping.
60
+ - **Auto-release the runner-coordination lock on run completion + document stale-lock takeover (#1109, Seam 1 of #1084).** A completed/stopped run left its `.pi/runner-coordination/<repo>/pr-<n>.json` claim in place, so a merge-authorized re-dispatch (fresh run id) was refused `ownership_lost` — hit live on a 9.5h-old stale lock. A run now releases its coordination claims on completion/stop, and the stale-lock takeover path is documented; a genuinely active (non-stale) claim still fails closed.
61
+ - **Record fan-out provenance + opt-in enforcement, route-to-conductor (#1110, Seam 2 dev-loops of #1084).** `requireFanoutEvidence` was artifact-only, so a single agent could self-produce every per-angle review plus a ledger and label the verdict `fanout_fanin`. The findings-log ledger now records fan-out provenance (distinct reviewer count and dispatch provenance), and an opt-in `requireFanoutProvenance` check in `buildPreMergeGateCheck` re-validates it (internally consistent across checkouts, `distinctReviewers >=` the floor) to distinguish real parallel review from single-agent artifacts; a child that cannot fan out fails closed with a route-to-conductor message.
62
+ - **Fix `tools:` frontmatter so pi provisions the subagent tool at child depth (#1111, Seam 2 core of #1084).** pi-subagents parses the `tools:` frontmatter line with a naive comma split (no real YAML), so the flow-sequence form `tools: [read, …, subagent]` read `subagent]` (bracket attached) and never matched — child fan-out was impossible on pi. The `tools:` lines in `agents/*.agent.md` and `.pi/agents/*.agent.md` drop the brackets for the comma-scalar form; the generated `.claude/` mirror regenerates byte-identical (`normalizeToolList` already splits on commas), so the fix is additive on pi and a no-op on Claude Code (governed by the #1086 cross-harness contract).
63
+ - **Make `copilot-pr-handoff` and gate-coordination agree past the Copilot round cap (#1126).** Round-cap enforcement lived only in `copilot-pr-handoff.mjs`, so after `maxCopilotRounds` was reached `detect-pr-gate-coordination-state.mjs` still surfaced `rerequest_copilot_review` — advertising a re-request the handoff would refuse. Both now defer to a shared round-cap + significant-change detector, so they agree at the cap boundary.
64
+ - **Treat body-mention cross-references as non-owning board links (#1130, #1124).** `detect-linked-issue-pr` accepted a bare `CrossReferencedEvent` as an issue's linked open ready PR, but that event fires on any body mention (`Seam 1 of #1084`, `part of #X`), so a sub-issue-tree PR dragged every mentioned sibling into In Progress and jammed `resolve-active-board-item` with "multiple in-progress". An owning link now requires `willCloseTarget: true` (or a `ConnectedEvent`); a direct `gatherLiveFacts` open-issue→linked-ready-PR unit test (#1124) covers the reconcile bridge.
65
+ - **Run gate fan-out reviewers in the PR's actual worktree/head (#1135).** Reviewers dispatched with worktree isolation got a fresh checkout of stale `main` and no access to the gitignored `tmp/gate-context` bundle, so a reviewer could silently review the wrong tree without the seeded context. The read-only per-angle review dispatch now runs in the PR worktree/head with the seeded gate-context bundle (a worktree-locality guard rejects an out-of-tree `--context-path`), honoring the build-once/seed-many contract.
66
+ - **`write-gate-context --base` builds the diff + adjacentCode bundle on the CLI path (#1140).** The library built the "build once, seed many" bundle only when handed a diff; the CLI exposed no diff flag, so every CLI-driven gate wrote a thin briefing (`scope.diffPath: null`, empty `changedFiles`, no `adjacentCode`) and each of N reviewers re-ran the same `git diff`. A new `--base <ref>` flag captures the full diff plus the per-file 1-hop adjacent-code bundle once (`scope.diffSource="base"`), so every reviewer is seeded with the identical neutral bundle; without it the CLI still emits an explicit thin briefing.
67
+ - **Fix `manage-sub-issues reorder` always failing (#1160).** Reorder seeded its cursor with `after_id=0`, but GitHub's priority endpoint rejects `0` with an empty-body HTTP 500, which `gh` surfaced as `unexpected end of JSON input` — the loop aborted on the first call so no reorder ever succeeded. The first item now uses `before_id=<current head>` (or skips when it is already first) and chains `after_id` after; `ghApi` also surfaces the HTTP status on empty-body non-2xx responses.
68
+ - **Eliminated the version-skew class: pinned consumer-facing CLI invocations + release-time core-version assertion (#1036, root cause of #1033).** Every consumer-facing CLI example now pins `npx dev-loops@<version>` (single source: the package version) instead of a bare `npx dev-loops` that resolves a possibly-stale global/cached copy: `README.md`, `docs/migrating-to-dev-loops.md`, and the `cli/index.mjs` help/setup strings. Global `npm install -g dev-loops` is reframed as an optional, drift-prone shell convenience — explicitly not the supported invocation path. A new fail-closed contract test (`test/contracts/cli-invocation-contract.test.mjs`) bans bare `npx dev-loops` across the README/docs/CLI surface. The Pi extension already resolves the CLI and `@dev-loops/core` from the installed pinned package (module imports, updated via `pi update git:...`), never a global install; `extension/README.md` now states that lockstep contract. Root-cause fix for the mis-published-bundle failure mode: `scripts/release/assert-core-dependency-version.mjs` (node-builtins-only) fails the release when root `dev-loops`'s `@dev-loops/core` dependency major.minor differs from the version being released, wired as a step in `.github/workflows/release.yml` before the GitHub Release is created; a unit test (`test/docs/assert-core-dependency-version.test.mjs`) rejects the #1033-shaped bad manifest (`0.6.2` / `^0.2.6`) and guards the real manifest. Non-goals (YAGNI): no per-startup runtime skew watcher, no auto `npm i -g` on update.
69
+
70
+ - **Bash gate now blocks raw `gh pr create`, closing the draft-first hole.** The PreToolUse bash gate already blocked `gh pr ready`/`gh pr merge` without evidence, but nothing guarded PR *creation* — so raw `gh pr create` (which defaults to ready-for-review) silently bypassed the `workflow.requireDraftFirst` contract. It now denies raw `gh pr create` on the target repo and directs callers to the canonical wrapper `scripts/github/create-pr.mjs` (`dev-loops pr create`), which always drafts and self-assigns. The wrapper runs `gh pr create` inside a node child process, so its command is unaffected; an explicit non-target `--repo` and non-target-repo cwd pass through. The create gate now denies an explicit `--repo` targeting the repo regardless of cwd (#1047) — raw `gh pr create --repo <target>` run from outside the repo previously slipped through. New `commandContainsGhPrCreate`/`extractRepoFlagFromGhPrCreateAnywhere` in `packages/core/src/loop/bash-command-classify.mjs`, a create branch in `decideBashGate` (`packages/core/src/claude/hook-decisions.mjs`), and detection wiring in `.claude/hooks/pre-tool-use-bash-gate.mjs`; unit + e2e coverage added. The `gh pr <verb>` matcher now also treats newlines/`\r` as segment separators and normalizes a leading env-assignment (`GH_TOKEN=x`), `command`/`env`/`exec` wrapper, or absolute/relative gh path before the `^gh` test — closing the same bypass gap identically for `gh pr ready`/`gh pr merge` (subshell/group forms remain out of scope). The create gate no longer short-circuits ready/merge gating in compound commands: an out-of-scope create (e.g. `gh pr create --repo other/repo && gh pr merge 5`) now falls through so the gated ready/merge segment is still evaluated instead of being allowed early. The create gate now evaluates each create segment's scope so a leading out-of-scope create can't shield a later in-scope raw create.
71
+
72
+ - **`strategy.default: local-first` now respected end to end as the built-in code default (#1033).** `BUILT_IN_DEFAULT_TARGET_PREFERENCE` and `BUILT_IN_DEFAULTS.strategy.default` changed from `github-first` to `local-first` in `packages/core/src/loop/public-dev-loop-routing.mjs` and `packages/core/src/config/config.mjs`. The `resolveTargetPreference` fallback in `scripts/loop/resolve-dev-loop-startup.mjs` (line 267) and the config-load-error fallback (line 675) were also changed from `prefer_github_first` to `prefer_local`, so configless repos now resolve to local-first through the CLI path too. The 32 tests that previously relied on the implicit github-first default were updated to pin `PREFER_GITHUB_FIRST` explicitly; one config test assertion and one routing-config test updated to match the new built-in default. Docs updated: `targetPreference` table in `skills/docs/public-dev-loop-contract.md` now shows `prefer_local (default)`; `BUILT_IN_DEFAULTS` description in `skills/docs/artifact-authority-contract.md` updated to `local-first`.
73
+
74
+ ## 0.6.2 - 2026-06-30
75
+
76
+ ### Changed
77
+
78
+ - **Terminology pass: 'named human/person' → 'contributor' across all doc surfaces (#1026).** Articles (`docs/articles/dev-loops-deep-dive.{md,html}`, `introducing-dev-loops.html`), decks (`docs/presentations/dev-loops-deep-dive.html`, `introducing-dev-loops.html`, `applied-dev-loops-presentation.md`), `skills/docs/merge-preconditions.md`, `schemas/dev-loop-config.schema.json`, and `packages/core/src/config/config.mjs` docstring. Deep-dive article subtitle tightened; attention-bottleneck section rewritten for clarity. `.md`/`.html` pair synced (`fast enough to be routine`).
79
+
80
+ ### Fixed
81
+
82
+ - **`deriveUiE2ePassed` blocked the gate on SKIPPED UI e2e checks (#1026).** `viewer-smoke` is `SKIPPED` (not `FAILURE`) on PRs that don't touch viewer files — it means "not applicable to this run." The function now treats `SKIPPED` the same as `SUCCESS` so a skipped check no longer incorrectly blocks `run_pre_approval_gate`. Test coverage added.
83
+
84
+ - **`plugin.json` was missing from the v0.6.1 version bump (#1026).** `.claude/.claude-plugin/plugin.json` was still at `0.6.0`; bumped to `0.6.1` (now `0.6.2` in this release). Stale generated `.claude/` assets regenerated.
85
+
86
+ ## 0.6.1 - 2026-06-30
87
+
88
+ ### Changed
89
+
90
+ - **Docs: refresh intro article + deck to the 0.6.0 command surface + current velocity snapshot (#1015, #1018).** `/dev-loops:continue` now shows both forms — `/dev-loops:continue 112` (issue or PR) and bare `/dev-loops:continue` (resumes the single in-progress board item, fail-closed on 0/multiple). Adds `/dev-loops:start-spike "…"` to the command list in both the intro article (`docs/articles/introducing-dev-loops.{md,html}`) and the intro deck (`docs/presentations/introducing-dev-loops.html`). The Pi command set in the catch-all router note now lists `start-spike`. The velocity snapshot paragraph is updated from the previous "two-week, ~100 PR" estimate to a verified four-day snapshot (87 PRs, ~22/day, v0.4.0 + v0.5.0).
91
+
92
+ - **Deep-dive deck: desktop responsiveness, mandatory snap, keyboard nav (#1021, #1022).** Desktop layout adjusts for wider viewports. Section scroll-snap is enforced. Keyboard navigation is enabled.
93
+
94
+ - **Namespace Claude Code slash commands with `loop-` prefix to avoid collisions (#1023).** The 6 dev-loop commands (`.claude/commands/`) are now `/loop-start`, `/loop-auto`, `/loop-continue`, `/loop-start-spike`, `/loop-info`, `/loop-status` — renamed from bare `/start` etc. which collided with Claude Code built-ins (notably `/status`). Source files renamed from `commands/*.command.md` to `commands/loop-*.command.md`; generated assets updated; no-drift test updated.
95
+
96
+ ### Fixed
97
+
98
+ - **`release.yml` auto-Release never ran — the CHANGELOG extractor pulled in `@dev-loops/core` (#1016, regression in #996's first run).** `scripts/release/extract-changelog-section.mjs` imported `isDirectCliRun` from `scripts/_core-helpers.mjs`, which re-exports from the `@dev-loops/core` workspace package. `release.yml` runs the extractor with **no `npm ci`** (a text parser needs no install), so on a `v*` tag push the import `ERR_MODULE_NOT_FOUND`ed before any notes were extracted and the GitHub Release was never created (the same hands-off-publish gap #996 set out to close). The extractor now imports **only `node:` builtins** — `isDirectCliRun` is inlined (`node:fs` `realpathSync` + `node:url` `fileURLToPath`), all behavior unchanged (`--version`/`--changelog`, leading-`v` strip, heading lookahead so `0.5.1` ≠ `0.5.10`, stop-at-next-`## `, no Unreleased bleed, exit 1 absent/empty, exit 2 arg/file error, the `extractChangelogSection()` export). A new guard test parses the script's `import` statements and asserts every specifier is `node:`-prefixed, so a future workspace import can't silently reintroduce the deps-free break. `release.yml` is unchanged — the standalone script is the fix.
99
+
100
+ - **Close `gh pr merge` bypass hole in the PreToolUse gate (#1024).** A hand-run `gh pr merge` could skip the pre-approval gate entirely because the PreToolUse Bash hook only blocked `gh pr ready`. The hook now also blocks `gh pr merge` unless `detect-checkpoint-evidence.mjs` confirms clean current-head `draft_gate` + `pre_approval_gate` evidence (fanout_fanin, no inline verdicts). Two compound-command bypass paths are also closed: (1) the all-segments scanner (`commandContainsGhPrReady`/`commandContainsGhPrMerge`) catches gated verbs in any shell segment, not just the first; (2) `gh pr ready N && gh pr merge N` no longer short-circuits to ready-only — both verbs are detected independently and the stricter merge gate is applied. Pi extension public API (`isGhPrReadyCommand`, extractors) retains first-segment-only semantics (correct: `false && gh pr ready 42` short-circuits so ready never ran).
101
+
102
+ ## 0.6.0 - 2026-06-29
103
+
104
+ ### Added
105
+
106
+ - **`/start-spike` — first-class command to start a spike from a question (#988, P2; closes #988).** `/dev-loops:start-spike <question>` (and the Pi `/dev-loops start-spike` subcommand) starts a time-boxed dev-loop spike, a thin wrapper over the already-shipped `--spike` intake (#964/#965/#966). Inline free-text scaffolds a startable findings artifact and `--file <path>` uses a pre-authored one; both then run `resolve-dev-loop-startup.mjs --spike <path>` and hand the bundle to the `dev-loop` skill (gates.spike, exit via `exit-spike.mjs` — discard/graduate per `skills/docs/spike-mode-contract.md`). Because the argument is free text/a path rather than a number, `start-spike` is parsed on a **separate path** from the numeric verbs (start/auto/continue/info), keeping their numeric-validation invariant intact. The one new piece is `scripts/refine/scaffold-spike-file.mjs --question <text> --out <path>`, a pure section builder + file writer that fills `## Question` from the arg and stubs `## Approach`/`## Findings` (leaving `## Recommendation` for the spike) so the result passes `validateSpikeExplorationSections` and is immediately startable (JSON `{ ok, path, question }` + `--jq`/`--silent` via the shared `scripts/lib/jq-output.mjs`). No new spike behavior, gate, or format. `node:test` coverage for the scaffold (startable/in-progress, empty-question fail-closed, CLI write + end-to-end startability), the separate free-text/`--file` parse path (numeric verbs unaffected), and the generated-command presence + no-strategy-leak guard. Also widened the Pi command-palette `description` string's `continue <pr>` to `continue [issue|pr]` for consistency with the P1 surface widening.
107
+
108
+ - **Path-triggered UI e2e auto-scoping gate criterion (#976, P2; replaces opt-in-by-annotation).** A PR that adds or modifies a *rendered* HTML artifact now MUST run the shared UI e2e assertions (mobile + desktop) AND register that artifact in the suite — inclusion is triggered by the changed-file set, never by a human annotating the PR/phase doc. The deterministic core is `packages/core/src/loop/ui-e2e-scoping.mjs` (`evaluateUiE2eScoping`): explicit, conservative path globs (`docs/articles/*.html`, `docs/presentations/*.html`, single-segment, plus the viewer source `scripts/loop/inspect-run-viewer.mjs`) classify changed paths into rendered-artifact descriptors and check each against the registry-membership list keyed by full repo-relative path (`REGISTERED_ARTIFACT_PATHS` mirrors `DECK_REGISTRY` at `docs/presentations/<deck>` + `ARTICLE_REGISTRY` at `docs/articles/<file>`; `VIEWER_ARTIFACT_ID` mirrors `VIEWER_REGISTRY`, kept in sync by hand — the deck/article sync test asserts only that `REGISTERED_ARTIFACT_PATHS` matches `DECK_REGISTRY` + `ARTICLE_REGISTRY`, since the viewer harness pulls `@playwright/test` into core). Full-path keying (not basename) means a deck and an article that share a basename are distinct artifacts. Each artifact family has a stable, path-conditioned CI job (`viewer-smoke`/`deck-smoke`/`article-smoke`) named to match `UI_E2E_CHECK_NAMES`, so a deck- or article-only PR has a satisfiable signal. It is wired as a gate precondition in `evaluatePrGateCoordination` (`packages/core/src/loop/pr-gate-coordination.mjs`), at a seam distinct from the mergeability (#980) and retrospective (#982) preconditions, and **fails closed**: a rendered-artifact change that is unregistered, or registered but whose UI e2e suite has not passed for this head, blocks with `nextAction: run_ui_e2e_suite` and a reason naming the artifact (and that it needs registration / the suite must pass). Non-UI changes pass through (`required: false`). The detect layer reads the changed-file set from `gh pr view --json files` (`extractChangedFiles`) and derives `uiE2ePassed` from the `statusCheckRollup` UI e2e check(s) (`deriveUiE2ePassed` / `UI_E2E_CHECK_NAMES`); a UI e2e check that is absent reads as unknown and fails closed. Standard-step doc: [ui-e2e-scoping-step](skills/docs/ui-e2e-scoping-step.md). `node:test` coverage: helper trigger/negative/fail-closed (unregistered + not-passed) cases + registry sync, gate-level integration (required-pass, non-UI, both fail-closed paths), and detect-helper unit tests.
109
+
110
+ - **`/continue` dual-routing — bare resumes the current in-progress board item (#988, P1).** `/dev-loops:continue` (and the Pi `/dev-loops continue` subcommand) is now dual-routed: with an argument it continues that artifact (the `continue` verb widened from PR-only to `either`, so `123`/`#123`/a GitHub issue-or-PR URL all normalize and the resolver picks the canonical artifact); bare (no argument) it picks up the single in-progress board item. The bare path adds one new thin helper, `scripts/projects/resolve-active-board-item.mjs --repo <owner/name> --project <number|id>`, a pure list→single-target collapse over `list-queue-items.mjs --column "In Progress"` (JSON `{ ok, target: { kind, number } }` + `--jq`/`--silent` via the shared `scripts/lib/jq-output.mjs`): exactly one in-progress item → that target (prefers the linked PR over the issue); **zero or more than one → fails closed** (exit 3) with a reason naming the items and instructing `/continue #N` — it never guesses. No new routing logic: both `/continue` branches hand the resolved target/intent to the existing `dev-loop` skill (`loop startup` → build-envelope → route), still stopping at the human-approval checkpoint. `/dev-loops:dev-loop` remains the catch-all router. `node:test` coverage for the resolver (1/0/multiple), the widened verb + bare/`#`/URL arg normalize, and the no-strategy-leak guard.
111
+
112
+ - Direct dev-loop slash commands as thin wrappers over the public contract (#972): `/dev-loops:start <issue>`, `/dev-loops:auto <issue>`, `/dev-loops:continue [issue|pr]`, `/dev-loops:info <issue|pr>`, and `/dev-loops:status` for the Claude Code plugin (generated under `.claude/commands/` from `commands/*.command.md`), with equivalent `/dev-loops start|auto|continue|info|status` subcommands in the Pi extension (`status` already existed; the start/auto/continue/info entrypoint subcommands are the new parity). `/dev-loops:dev-loop` remains the catch-all router; no new routing logic was added.
113
+
114
+ - **Wrapped the last three agent-level `gh` reads + re-armed the #982 internal-tooling discipline (#993).** Three reads the loop still shelled out for now have thin dev-loops wrappers, following the #981 convention (JSON result + `--jq`/`--silent` via the shared `scripts/lib/jq-output.mjs` helper — `JQ_OUTPUT_PARSE_OPTIONS`/`JQ_OUTPUT_USAGE`/`emitResult`, so the jq-subset filter, fail-closed-on-invalid-filter exit `2`, and exit-code-only `--silent` behavior are reused, not reimplemented): `scripts/github/fetch-ci-logs.mjs --repo --pr [--failed-only] [--tail <n>]` resolves the PR's current head SHA, lists the Actions runs for that commit, and returns each run's log tail (`--log-failed` for a failed run, `--log` otherwise; a per-run log fetch failure records an `<log unavailable: …>` note instead of aborting) — the LOG complement to `probe-ci-status.mjs`, which names the failed checks; `scripts/github/list-issues.mjs --repo [--state open|closed|all] [--label <l>…] [--limit <n>]` returns `{ ok, issues: [{ number, title, state, labels }] }` (state lowercased, labels flattened) for arbitrary issue queries the queue/board tool doesn't cover; `scripts/github/comment-issue.mjs --repo --issue (--body <text> | --body-file <path>)` posts a comment via `gh issue comment` and returns `{ ok, commentUrl }` (sibling to the PR comment/verdict posters). Each is a thin wrapper over `gh` (the script calling gh internally IS the allowed tooling). With these in place, every agent-level raw `gh` read now has a wrapper, so a fully clean internal-tooling record is achievable — the repo-root `.devloops` flips `workflow.requireRetrospectiveInternalTooling` back to `true` (it was set false in #995 pending this), re-arming the hard retro check (#982) on our own retros; the developer-mode rationale comment is restored to the enforce-on posture. The token-economical reading convention in `skills/dev-loop/SKILL.md` (+ generated `.claude/` mirror) names the three wrappers as THE path — never raw `gh run view`/`gh issue list`/`gh issue comment`. `node:test` coverage per wrapper (gh stubbed via the injected `run`): arg parsing/validation, the success path (failing-job log tail for a red PR, filtered issues, comment URL returned), and the shared output flags exercised through `runCli` — `--jq` extraction, invalid-filter fail-closed exit `2`, `--silent` exit-code-only, and gh-failure exit `1`.
115
+
116
+ - **Automated GitHub Release on tag push (#996).** Pushing a `v*` tag now creates the GitHub Release automatically, which fires `npm-publish.yml` — tag → Release → publish is hands-off (closing the gap where v0.5.0 was tagged but never published because no Release was created). New `.github/workflows/release.yml` triggers `on: push: tags: ['v*']`: it mirrors npm-publish's on-main guard (tag commit must be an ancestor of `origin/main`), extracts the release notes from the `## <version>` CHANGELOG.md section, is idempotent (no-op if a Release for the tag already exists), and fails closed if the version has no CHANGELOG section (no empty/auto-generated release for an undocumented version). Notes extraction is factored into `scripts/release/extract-changelog-section.mjs` (`--version <v> [--changelog <path>]`) with `node:test` coverage: present section, fail-closed absent version, the `## Unreleased`-above-latest layout, stop-at-next-`## ` boundary, and version-prefix disambiguation. `npm-publish.yml` is unchanged (still `on: release: published`). Tagging stays the only manual step — see [release-runbook](skills/docs/release-runbook.md).
117
+
118
+ ### Changed
119
+
120
+ - **Reconciled the UI-validation docs to the auto-scoped model and added a worked example (#977, docs-only; UI-e2e epic UE3, follows #975/#976).** The four `docs/ui-*.md` docs no longer describe UI validation as an opt-in-by-annotation convention. `docs/ui-validation-contract.md` now leads with the path-triggered, registry-backed, fail-closed criterion, lists the shared harness assertions (section visibility, CSP-meta lock, 390px mobile fit, wide-element negative control), and carries a worked end-to-end example for the intro deck (`docs/presentations/introducing-dev-loops.html`): `DECK_REGISTRY["intro-deck"]` + thin `defineDeckSuite` spec → `REGISTERED_ARTIFACT_PATHS` membership → `ui_e2e_scoping` gate requirement (fails closed if unregistered) → the satisfiable `deck-smoke` CI signal (`UI_E2E_CHECK_NAMES`). `docs/ui-smoke-harness.md` reframes `webkit-smoke-harness.mjs` as the lower WebKit/config/capture seam the shared deck/article/viewer suites build on and drops the stale "not mandatory CI" claim; `docs/ui-artifact-contract.md` renames CI promotion to auto-scoped CI enforcement; `docs/ui-designer-review-loop.md` notes it is the optional design-review pass, distinct from the required gate. All four now cross-link the canonical owner `skills/docs/ui-e2e-scoping-step.md` and the shared harness. No behavior/harness/gate change. Closes #939 (its slide responsive-fit goal is delivered by the #975 mobile-fit harness).
121
+
122
+ - **Taught the direct subcommand structure across the how-to surfaces (#1001, follows #972).** The intro article (`docs/articles/introducing-dev-loops.{md,html}`) and the intro deck setup slide (`docs/presentations/introducing-dev-loops.html`) now show the direct named commands from #972 (`/dev-loops:start`, `:auto`, `:continue`, `:info`, `:status`), framing `/dev-loops:dev-loop` as the catch-all router for plain-language intent (with the Pi `/dev-loops start|auto|continue|info|status …` parity noted on both surfaces — the article spells out the full set, the deck setup slide flags that Pi drops the colon). The deck slide stays within the 390px mobile-fit harness (#975); section ids unchanged. Conceptual/routing-contract prose (deep-dive article + deck, public-dev-loop-contract, main-agent-contract) was left untouched.
123
+
124
+ - **Per-artifact Playwright deck assertions extracted into a shared harness + registry (#975, generalizes #939).** The two presentation-deck specs (`test/playwright/intro-deck.spec.mjs`, `test/playwright/deep-dive-deck.spec.mjs`) were ~190 lines of near-identical assertions each (same server, same three tests, only the deck path / section ids / mobile-capture id differed). The reusable assertions now live once in `test/playwright/harness/deck-fit-harness.mjs`: the single-file deck server, the `networkidle`+`innerWidth===390`+`document.fonts.ready` layout-settle barrier, the per-element `getBoundingClientRect().right <= innerWidth + 1` mobile-fit check (no overflow-x scroller exemption) with the defensive `scrollingElement.scrollWidth` guard and the per-section vertical-clip check (`clientHeight < scrollHeight` while `overflow-y` is hidden/clip), section-id presence + no-horizontal-scroll, a CSP-`<meta>` guard (`default-src 'none'`), and the guard-the-guard test (a deliberately-wide element MUST fail the fit check). A `DECK_REGISTRY` holds each deck as data (`{ sliceId, deck, sectionIds, mobileCapture }`) and `defineDeckSuite(entry)` generates the full per-deck suite; the two deck specs shrink to thin registrations that resolve the deck path and call `defineDeckSuite`. The inspect-run viewer (an interactive dashboard, not a fit-checked deck) registers via `test/playwright/harness/inspect-run-viewer-harness.mjs` (`startViewer`/`openTab`/`waitForMermaidGraph`/`captureViewerState`/`VIEWER_REGISTRY`), removing the duplicated server-setup + repeated capture blocks from its spec, and now runs the shared `assertSectionIdsAndNoHorizontalScroll` over its registered panel ids. The per-artifact `playwright.*.config.mjs` files and `test:playwright:*` scripts are unchanged (each config's `testMatch` still scopes which registration runs). All existing assertions still pass — `intro-deck` 3/3, `deep-dive-deck` 3/3, `inspect-run-viewer` 7/7 green.
125
+
126
+ ### Fixed
127
+
128
+ - **Robust, bounded `<dev-loops-package-root>` resolution for user-level installs (#1009).** The `dev-loop` agent prompt (`agents/dev-loop.agent.md`) and the `dev-loop` skill (`skills/dev-loop/SKILL.md`) previously told the Pi runtime to resolve the package-local CLI by assuming a single fixed `../..` (agent) / `../../..` (skill) package-relative layout. Under a Pi user-level install the agent file is synced to `~/.agents/` and the package itself lives at `~/.pi/agent/npm/node_modules/dev-loops/` (skills are exposed via `package.json` `pi.skills`, loaded from that package location, not copied into `~/.agents/`), so the fixed relative guess missed the package root, the CLI could not be found, and the agent improvised an unbounded `find / -name dev-loop.agent.md` full-filesystem walk that stalled 60s+ and tripped the async needs-attention timeout. The resolution prose now lists an ordered set of **bounded** candidate roots — a best-effort `require.resolve('dev-loops/cli/index.mjs')` probe (cwd-dependent; reliable only when `dev-loops` is on Node's module search path; wrapped in try/catch so a miss exits non-zero silently instead of printing a stack trace that reads as a hard failure), the Pi user-agent npm root (`~/.pi/agent/npm/node_modules/dev-loops`, the reliable path for user-level installs), the legacy package-relative path, and the global npm root — taking the first whose `cli/index.mjs` exists. The prose now explicitly **forbids** `find /` / any unbounded filesystem walk and instructs the agent to stop and ask the orchestrator/operator for the path when every bounded candidate fails. Pi-only prose, stripped from the generated `.claude/` mirrors (which already pin `npx dev-loops@<version>` and rely on Node resolution); `.claude/` regenerated and in sync; cli-invocation contract test green.
129
+
130
+ - **Restored `PI_SUBAGENT_RUN_ID` as a recognized async-context run-id alias — the Pi async-start gate no longer fails closed (#1008, regression from #905).** The Pi runtime injects only `PI_SUBAGENT_RUN_ID` (never the neutral `DEVLOOPS_RUN_ID`) into each async-subagent child env, but #905 dropped that alias from `RUN_ID_MARKERS` as a "deliberate breaking change", leaving the sole marker absent under Pi. With `workflow.asyncStartMode: "required"` this made `validateAsyncStartContext` return `rejected` at startup step 1, so no dev-loop work could start under Pi. `RUN_ID_MARKERS` in `packages/core/src/loop/run-context.mjs` is now `["DEVLOOPS_RUN_ID", "PI_SUBAGENT_RUN_ID"]` — the neutral var stays the primary (and the only var dev-loops mints/propagates), with the Pi-injected name honored as a precedence-after alias. The alias threads through everywhere the primary does (`resolveRunId`/`ensureRunId` loop the markers; `ASYNC_CONTEXT_MARKERS` re-exports them, so the async-start gate recognizes it), and the rejection message names both again ("Set DEVLOOPS_RUN_ID (or the PI_SUBAGENT_RUN_ID alias) to proceed", matching #830). `PI_SUBAGENT_RUN_ID` is an externally-injected Pi-runtime contract var (not a dev-loops-owned `PI_*` name — #905's rename of owned vars to `DEVLOOPS_*` stands), so the harness-agnostic env guard (`test/contracts/cli-harness-agnostic.test.mjs`) allowlists it as a runtime-injected marker confined to the run-context/async-start modules + their tests (and the generated `.claude` mirror). `node:test`: Pi-only-marker recognition under `required` (the regression reproduction), neutral-primary precedence, and the both-markers rejection message.
131
+
7
132
  ## 0.5.0 - 2026-06-28
8
133
 
9
134
  ### Added
package/README.md CHANGED
@@ -30,21 +30,36 @@ Use **`dev-loop`** as the single public workflow entrypoint:
30
30
 
31
31
  The `dev-loop` entrypoint resolves authoritative state, picks the correct internal strategy, and routes work deterministically. Users never need to choose internal strategy names. See the canonical shorthand example mapping in the [Public Dev Loop Contract](./skills/docs/public-dev-loop-contract.md).
32
32
 
33
+ ### Direct commands
34
+
35
+ The same entrypoints are also available as direct named commands — thin wrappers over the public contract, no separate routing:
36
+
37
+ | Command | Equivalent intent |
38
+ | --- | --- |
39
+ | `/dev-loops:start <issue>` | start dev loop on issue `<issue>` |
40
+ | `/dev-loops:auto <issue>` | auto dev loop on issue `<issue>` (autonomous until human approval) |
41
+ | `/dev-loops:continue [issue\|pr]` | continue dev loop on `<issue\|pr>`; bare resumes the single in-progress board item |
42
+ | `/dev-loops:start-spike <question>` | start a time-boxed spike from a question (or `--file <path>` for a pre-authored findings file) |
43
+ | `/dev-loops:info <issue|pr>` | read-only state summary (`loop info`) |
44
+ | `/dev-loops:status` | dev-loop readiness (gh auth, git repo, subagent) |
45
+
46
+ `/dev-loops:dev-loop` remains the catch-all router. Inside Pi the same set is reachable as `/dev-loops start|auto|continue|start-spike|info|status …`.
47
+
33
48
  ## Install from npm
34
49
 
35
50
  ### CLI only
36
51
 
37
- ```bash
38
- npm install -g dev-loops # global install
39
- dev-loops --help # verify
40
- ```
41
-
42
- Or run directly without installing:
52
+ Run the CLI **version-pinned** to the plugin/extension you have installed. The pin is the
53
+ single source of truth (`<version>` = your installed `dev-loops` version) and cannot drift:
43
54
 
44
55
  ```bash
45
- npx dev-loops --help
56
+ npx dev-loops@<version> --help
46
57
  ```
47
58
 
59
+ A global `npm install -g dev-loops` is **not** the supported invocation path: the global
60
+ binary updates independently of the plugin/extension and silently drifts out of version
61
+ (#833/#1036). Install it only as an optional bare shell convenience.
62
+
48
63
  ### Claude Code plugin
49
64
 
50
65
  The repo ships a Claude Code plugin rooted at `.claude/` (manifest at
@@ -187,7 +202,7 @@ Phase 8 is the active durable phase; Phase 7 second-repo pilot is deferred. See
187
202
  Gate review angles, refinement settings, persona mappings, and workflow defaults are config-driven via `.pi/dev-loop/defaults.yaml`. Consumer repos override values in `.devloops` at repo root (legacy `.pi/dev-loop/settings.yaml` still loads with a deprecation warning). The loader also accepts `.yml` and `.json` extensions and legacy `overrides.*` files as fallback formats. See [Extension Documentation](./extension/README.md) for details.
188
203
 
189
204
  ```bash
190
- npx dev-loops gates # see what reviewers will check
205
+ npx dev-loops@<version> gates # see what reviewers will check
191
206
  ```
192
207
 
193
208
  Key surfaces:
@@ -231,9 +246,13 @@ subagent({
231
246
 
232
247
  Do not call internal routed skills such as `local-implementation`, `copilot-pr-followup`, or `final-approval` directly; `dev-loop` selects them from the current GitHub/repo state.
233
248
 
234
- The `/dev-loops` command surface is for readiness and configuration UX:
249
+ The `/dev-loops` command surface covers the direct dev-loop entrypoints plus readiness and configuration UX:
235
250
 
236
251
  ```bash
252
+ /dev-loops start <issue> # dispatch: start dev loop on issue <issue>
253
+ /dev-loops auto <issue> # dispatch: auto dev loop on issue <issue>
254
+ /dev-loops continue [issue|pr] # dispatch: continue dev loop on <issue|pr>; bare = current in-progress board item
255
+ /dev-loops info <issue|pr> # read-only state summary
237
256
  /dev-loops status
238
257
  /dev-loops doctor
239
258
  /dev-loops gates
@@ -245,7 +264,7 @@ The `/dev-loops` command surface is for readiness and configuration UX:
245
264
  npx dev-loops@<version> gates
246
265
  ```
247
266
 
248
- Use `npm install -g dev-loops` only if you want the shell command available outside Pi.
267
+ Use `npm install -g dev-loops` only as a bare shell convenience outside Pi; it is not version-pinned, can drift against the plugin/extension, and is not the supported invocation path (prefer `npx dev-loops@<version>`).
249
268
 
250
269
  The package exposes the `/dev-loops` extension command surface, the `dev-loops` shell CLI, and packaged skills from `package.json` `pi.skills`.
251
270