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
@@ -0,0 +1,45 @@
1
+ # Release runbook
2
+
3
+ Releasing is fully automated from a tag. **Pushing a `v<version>` tag is the only
4
+ manual step.** Everything after the tag is hands-off.
5
+
6
+ ## Procedure
7
+
8
+ 1. On `main` (merged, green), bump the version and add the matching
9
+ `## <version> - <date>` section to `CHANGELOG.md` (the empty `## Unreleased`
10
+ heading stays above the latest version).
11
+ 2. Tag the release commit and push the tag:
12
+
13
+ ```bash
14
+ git tag v<version>
15
+ git push origin v<version>
16
+ ```
17
+
18
+ That is the whole manual flow. Do **not** create the GitHub Release by hand —
19
+ the workflow does it (and is idempotent if you already created one).
20
+
21
+ ## What happens automatically
22
+
23
+ - **`.github/workflows/release.yml`** fires on the `v*` tag push. It verifies the
24
+ tagged commit is on `origin/main`, extracts the `## <version>` block from
25
+ `CHANGELOG.md` via `scripts/release/extract-changelog-section.mjs`, and creates
26
+ the GitHub Release (`--latest`, notes = that CHANGELOG section). It is
27
+ **idempotent** (no-op if a Release for the tag already exists) and **fails
28
+ closed** if the version has no CHANGELOG section — an undocumented version never
29
+ gets an empty release.
30
+ - **`.github/workflows/npm-publish.yml`** fires on `release: published` (created by
31
+ the step above) and publishes the packages to npm.
32
+
33
+ ## Failure modes
34
+
35
+ - Tag not on `main`: the release workflow fails the on-main guard — re-tag the
36
+ correct commit.
37
+ - Missing CHANGELOG section: the extraction step exits non-zero and no Release is
38
+ created. The workflow checks out the tagged commit, so editing `CHANGELOG.md`
39
+ alone is not enough — commit the `## <version>` section, then move the tag to
40
+ the new commit and re-push:
41
+
42
+ ```bash
43
+ git tag -f v<version>
44
+ git push --force origin v<version>
45
+ ```
@@ -87,65 +87,87 @@ Callers have two supported integration options:
87
87
 
88
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
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
- ```
90
+ ## Advisory findings — never a merge gate (issue #1077, Reading B)
91
+
92
+ The retrospective is **advisory**: it runs, records flagged raw-call / discipline
93
+ observations honestly, and passes them back to the conductor (main agent) to
94
+ **decide** what to do with them but it **NEVER blocks a merge or any lifecycle
95
+ transition**. The pre-merge retrospective gate (`evaluateRetrospectiveMergeApproval`
96
+ and the `requireRetrospectiveGate` / `requireRetrospectiveInternalTooling` config
97
+ keys) has been **removed**. There is no `retrospective_gate_pending` / `blocked`
98
+ disposition on account of the internal-tooling raw-call record.
99
+
100
+ ### How findings travel (Reading B)
101
+
102
+ 1. **Deterministic return contract.** The loop subagent's handoff envelope carries
103
+ the retrospective findings as a structured `retrospectiveFindings` field — the
104
+ `check-retro-tooling.mjs` JSON output (`{ internalToolingOnly, rawCallViolations,
105
+ allowedWriteOps }`), not prose. The conductor reads that field. This is a hard
106
+ contract; see [Workflow Handoff Contract](./workflow-handoff-contract.md).
107
+ 2. **Durabilityadvisory PR comment.** The conductor posts a single advisory PR
108
+ comment carrying the findings (`rawCallViolations`, `internalToolingOnly`,
109
+ `allowedWriteOps`). Durable and on-GitHub, but **not a gate**. No disk artifact
110
+ is written for retrospective *findings*.
111
+ 3. **No config.** There is nothing to configure: the retrospective always runs and
112
+ always returns findings. `requireRetrospectiveGate` and
113
+ `requireRetrospectiveInternalTooling` no longer exist.
114
+
115
+ A PR that is otherwise green becomes merge-ready with the violations **recorded**,
116
+ not blocked. The conductor may note them, open a follow-up, coach or ignore.
117
+
118
+ ### Internal-tooling-only rule (issue #982) now advisory
119
+
120
+ This rule records the dev-loops maintainers' own dogfooding discipline: the loop's
121
+ own execution should use internal dev-loops tooling, not agent-level raw
122
+ `gh`/`python`/`node -e` escape hatches. **It no longer blocks.** The flagged calls
123
+ are reported as advisory findings via the envelope + PR comment.
124
+
125
+ **Flagged as raw-call violations:** `gh ...` (including `gh api`, `gh ... --jq`),
126
+ `python` / `python3`, `node -e` / `node --eval` (inline eval). **Allowed (NOT
127
+ violations):** dev-loops subcommands and `node scripts/*.mjs` invocations — those
128
+ scripts legitimately call `gh`/GraphQL internally; that is the tooling. The rule
129
+ targets the agent's own top-level shell calls, not a script's internals.
130
+
131
+ **Write-op allowlist (verifier only):** `gh pr merge`, `gh pr ready`,
132
+ `gh issue create`, `gh issue edit` have no internal wrapper today. The deterministic
133
+ verifier records these as `allowedWriteOps` rather than violations so they are
134
+ surfaced distinctly, not as breaches. Close the gap with a wrapper to remove an
135
+ entry. None of these block anything.
136
+
137
+ ### Deterministic verifier (findings-producer)
138
+
139
+ `node scripts/loop/check-retro-tooling.mjs [--transcript <path>] [--json]` reads a
140
+ newline-delimited transcript of the shell commands the agent ran (one top-level
141
+ command per line, via `--transcript` or stdin) and reports agent-level raw
142
+ `gh`/`python`/`python3`/`node -e`/`node --eval` calls. It is a **findings-producer**:
143
+ its JSON output (`{ ok, internalToolingOnly, rawCallViolations, allowedWriteOps }`)
144
+ is returned to the conductor via the envelope's `retrospectiveFindings` field (the
145
+ envelope carries the normalized shape `{ internalToolingOnly, rawCallViolations,
146
+ allowedWriteOps }` the redundant `ok` flag is dropped by normalization) — it
147
+ is **not** written to a checkpoint and **not** a gate. Exit code `1` when violations
148
+ are found, `0` when clean. The pure `analyzeTranscript(transcript)` export returns
149
+ `{ violations, allowedWriteOps, internalToolingOnly }`.
150
+
151
+ Matching rules: a tool name at the start of a command segment (start of line, or
152
+ after `&&`/`||`/`|`/`;`); `node` is a violation only with `-e`/`--eval`. Before
153
+ classifying, the verifier normalizes the segment head — it strips leading
154
+ `NAME=value` env-assignment prefixes (`GH_TOKEN=x gh api`), strips a leading
155
+ wrapper binary from `{sudo, env, xargs, time, nice, command}` (`sudo gh api`,
156
+ `xargs gh api`), and reduces a path-prefixed binary to its basename
157
+ (`./node_modules/.bin/gh`, `/usr/bin/python3`) — so the common prefixed/wrapped
158
+ raw-call forms are caught. Known limitation: it does NOT fully parse shell
159
+ quoting/substitution. A separator inside a quoted argument can over-report; deeply
160
+ obfuscated calls (command substitution `$(...)`, aliases, `eval`) may evade it —
161
+ prefer single-line, single-purpose commands in transcripts.
162
+
163
+ ### Lifecycle reconciliation
164
+
165
+ The retrospective is described consistently as a **post-merge / advisory
166
+ reflection**, never a pre-merge blocker. The former contradiction —
167
+ `lifecycle-state.mjs` documenting the retro as a post-merge write while
168
+ `pr-gate-coordination.mjs` enforced it pre-merge — is resolved by removing the
169
+ pre-merge gate: the merge lifecycle step proceeds, and the retrospective is an
170
+ advisory reflection whose findings reach the conductor via the envelope.
149
171
 
150
172
  ## Durable artifact format
151
173
 
@@ -162,25 +184,17 @@ The checkpoint file is written by `.pi/extensions/dev-loop-behavioral-review.ts`
162
184
 
163
185
  ### After retrospective is done (written by operator or skill)
164
186
 
165
- A minimal completion clears the startup gate. To also clear the **merge gate**
166
- (`requireRetrospectiveGate`), include the `behavioralReview`, `gateQuality`, and
167
- `mergeRecommendation` fields:
187
+ A minimal completion clears the startup/resume completion gate. The checkpoint
188
+ file carries **only completion state** — retrospective *findings*
189
+ (`behavioralReview`, `rawCallViolations`, `internalToolingOnly`) no longer live on
190
+ disk; they travel in the handoff envelope's `retrospectiveFindings` field and an
191
+ advisory PR comment (issue #1077, Reading B):
168
192
 
169
193
  ```json
170
194
  {
171
195
  "state": "complete",
172
196
  "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
- }
197
+ "notes": "Loop followed working agreement; minor drift on thread resolution."
184
198
  }
185
199
  ```
186
200
 
@@ -199,9 +213,9 @@ A minimal completion clears the startup gate. To also clear the **merge gate**
199
213
  | Artifact | Location |
200
214
  |---|---|
201
215
  | 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` |
216
+ | Internal-tooling verifier (findings-producer) | `scripts/loop/check-retro-tooling.mjs` (+ `test/loop/check-retro-tooling.test.mjs`) |
217
+ | Advisory findings envelope field | `packages/core/src/loop/handoff-envelope.mjs` `retrospectiveFindings` |
218
+ | Tests | `packages/core/test/retrospective-checkpoint.test.mjs`, `packages/core/test/pr-gate-coordination.test.mjs`, `packages/core/test/handoff-envelope.test.mjs` |
205
219
  | Extension (writes required marker, fires review prompt) | `.pi/extensions/dev-loop-behavioral-review.ts` |
206
220
  | Checkpoint file | `.pi/dev-loop-retrospective-checkpoint.json` |
207
221
  | AGENTS.md repo contract | [Agent Instructions](../../AGENTS.md) — concise repo contract and working rules |
@@ -0,0 +1,134 @@
1
+ # UI e2e scoping step
2
+
3
+ Canonical owner for **when the shared UI/mobile e2e loop is required**. Inclusion
4
+ is **path-triggered and deterministic** — it does not depend on a human annotating
5
+ the PR or a phase doc.
6
+
7
+ ## Trigger: a rendered-artifact change requires UI e2e coverage
8
+
9
+ A PR that **adds or modifies a rendered HTML artifact** MUST run the shared UI e2e
10
+ assertions (mobile + desktop) AND register that artifact in the e2e suite. A
11
+ "rendered artifact" is anything that renders to a served page or component:
12
+
13
+ - a presentation deck — `docs/presentations/*.html` (registered in `DECK_REGISTRY`)
14
+ - an article page — `docs/articles/*.html` (registered in `ARTICLE_REGISTRY`; the
15
+ intro article is the published landing page)
16
+ - the inspect-run viewer's served page/component — `scripts/loop/inspect-run-viewer.mjs`
17
+
18
+ The trigger is the PR's **changed-file set** matched against these explicit globs.
19
+ It is conservative on purpose (issue #976 scope): only artifacts that render to a
20
+ page/component, matched by exact directory + single-segment `*.html` globs (no
21
+ recursion) and the viewer source path.
22
+
23
+ Registration is keyed on the **full repo-relative path**, not the basename, so
24
+ `docs/articles/X.html` and `docs/presentations/X.html` (which share basenames,
25
+ e.g. `introducing-dev-loops.html`) are **distinct** artifacts and can never alias
26
+ onto each other — editing the article never counts as deck coverage and vice versa.
27
+
28
+ ### Examples — required
29
+
30
+ - `docs/presentations/introducing-dev-loops.html` edited → UI e2e **required**
31
+ (deck-smoke CI job runs the deck fit specs).
32
+ - `docs/articles/introducing-dev-loops.html` (the landing page) edited → UI e2e
33
+ **required** (article-smoke CI job runs the article fit specs); it is its own
34
+ registration, NOT covered by the same-named deck.
35
+ - A new `docs/articles/new-page.html` added → UI e2e **required**, and because the
36
+ new page is not yet in `ARTICLE_REGISTRY` the gate **fails closed** until it is
37
+ registered (add an entry + a thin spec calling `defineArticleSuite`).
38
+ - `scripts/loop/inspect-run-viewer.mjs` edited → UI e2e **required** (the viewer is
39
+ registered in `VIEWER_REGISTRY`).
40
+
41
+ ### Examples — not required
42
+
43
+ - `packages/core/src/loop/copilot-loop-state.mjs`, `README.md`, `*.test.mjs`,
44
+ `docs/articles/foo.md` (not `.html`), or a nested `docs/articles/sub/x.html`
45
+ (the glob is single-segment) → UI e2e **not required**; the gate passes through.
46
+
47
+ ## Register the artifact in the suite
48
+
49
+ "Registered" means the artifact appears in the shared registries:
50
+
51
+ - decks → `DECK_REGISTRY` in `test/playwright/harness/deck-fit-harness.mjs`
52
+ (served from `docs/presentations/<deck>`) with a thin spec calling `defineDeckSuite`.
53
+ - articles → `ARTICLE_REGISTRY` in `test/playwright/harness/deck-fit-harness.mjs`
54
+ (served from `docs/articles/<file>`) with a thin spec calling `defineArticleSuite`
55
+ (shared fit/CSP/no-horizontal-scroll assertions, minus the deck's per-section
56
+ named captures).
57
+ - the viewer → `VIEWER_REGISTRY` in
58
+ `test/playwright/harness/inspect-run-viewer-harness.mjs`.
59
+
60
+ The deterministic membership list the gate checks against lives in
61
+ `packages/core/src/loop/ui-e2e-scoping.mjs` (`REGISTERED_ARTIFACT_PATHS`, keyed by
62
+ full repo-relative path; `VIEWER_ARTIFACT_ID`). A sync test keeps
63
+ `REGISTERED_ARTIFACT_PATHS` from drifting from `DECK_REGISTRY` + `ARTICLE_REGISTRY`;
64
+ the single-valued `VIEWER_ARTIFACT_ID` is not import-checked against `VIEWER_REGISTRY`
65
+ (that harness pulls `@playwright/test` into core), so keep the two in sync by hand.
66
+
67
+ ## Fail-closed semantics
68
+
69
+ The check is wired as a gate precondition in `evaluatePrGateCoordination`
70
+ (`packages/core/src/loop/pr-gate-coordination.mjs`), at a seam distinct from the
71
+ mergeability (#980) and retrospective (#982) preconditions. It fails closed:
72
+
73
+ - **Unregistered rendered artifact touched** → gate blocks with
74
+ `nextAction: run_ui_e2e_suite`, reason naming the artifact and that it needs
75
+ registration in the suite.
76
+ - **Registered, but UI e2e suite did not pass for this head** (`uiE2ePassed` is
77
+ `false`/unknown) → gate blocks with the same action, reason that the suite must
78
+ run and pass first.
79
+ - **Non-UI change** → `required: false`, gate passes through untouched.
80
+
81
+ The detect layer reads the changed-file set from `gh pr view --json files` and
82
+ derives `uiE2ePassed` from the `statusCheckRollup` UI e2e check(s)
83
+ (`UI_E2E_CHECK_NAMES`): present UI e2e checks must all be `SUCCESS`; if none is
84
+ present the value is unknown and the gate fails closed.
85
+
86
+ Each rendered-artifact family has a stable, satisfiable CI job in
87
+ `.github/workflows/ci.yml`, path/diff-conditioned in the `changes` job and named
88
+ to match `UI_E2E_CHECK_NAMES` so the gate can read a real signal:
89
+
90
+ - `viewer-smoke` → inspect-run viewer spec (triggered by the viewer source set).
91
+ - `deck-smoke` → both presentation deck fit specs (triggered by
92
+ `docs/presentations/**` and the deck specs/harness/configs).
93
+ - `article-smoke` → both article fit specs (triggered by `docs/articles/**` and the
94
+ article specs/harness/configs).
95
+
96
+ So a deck- or article-only PR has a CI check that can actually satisfy the gate;
97
+ without these jobs such a PR would fail closed with no satisfiable signal.
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
+
131
+ ## Non-goals
132
+
133
+ Not always-on screenshot testing; not mandatory multi-browser. The criterion is
134
+ only the conservative path-glob + registry-membership + passing-coverage check.
@@ -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
 
@@ -43,11 +43,17 @@ Treat missing optional files as normal bootstrap conditions, not errors.
43
43
 
44
44
  ### Tracker-backed local implementation
45
45
 
46
- Local implementation supports two durable spec inputs:
46
+ 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:
47
+
48
+ Durable/committed spec artifacts:
47
49
 
48
50
  - phase-doc-backed local sessions ([Phase Plan](../../docs/phases/phase-x.md) is canonical)
49
51
  - tracker-backed local sessions (the tracker issue is canonical)
50
52
 
53
+ Non-durable spec-of-record (no committed plan artifact):
54
+
55
+ - 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)".
56
+
51
57
  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.
52
58
 
53
59
  When the local spec already lives in a tracker issue:
@@ -130,33 +136,33 @@ Follow [Anti-patterns](../docs/anti-patterns.md) for the general tooling-interna
130
136
 
131
137
  Apply [Structural Quality](../docs/structural-quality.md) from the `deep` review angle.
132
138
 
133
- ## Light mode (small changes) — config-only
139
+ ## Light mode (small changes)
140
+
141
+ 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.
134
142
 
135
- 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.
143
+ **Escalation:**
144
+ 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.
145
+ 2. Label override — the `gate:full` label forces full fan-out regardless of PR size.
136
146
 
137
- **Eligibility:** ≤3 files AND ≤200 lines changed (configurable via `.devloops` field `localImplementation.lightMode`).
147
+ 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.
138
148
 
139
- Use `scripts/loop/detect-change-scope.mjs` to determine eligibility:
149
+ 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.
150
+
151
+ Use `scripts/loop/detect-change-scope.mjs` to determine scope:
140
152
  ```sh
141
153
  node scripts/loop/detect-change-scope.mjs
142
154
  ```
143
155
 
144
- **Planned light mode path (not yet wired):**
145
- 1. Validation (`npm run verify`)
146
- 2. Single review pass (not multi-angle fan-out)
147
- 3. Pre-approval gate
148
- 4. Finalization
149
-
150
156
  **Override threshold:**
151
157
  ```yaml
152
158
  localImplementation:
153
159
  lightMode:
154
160
  enabled: true
155
- maxFiles: 5
156
- maxLines: 300
161
+ maxFiles: 2
162
+ maxLines: 20
157
163
  ```
158
164
 
159
- Disabled by default (opt-in). Scope above threshold falls back to full fan-out/fan-in path.
165
+ Scope above threshold falls back to the full fan-out/fan-in path.
160
166
 
161
167
  ## Deterministic logging structure
162
168
 
@@ -264,6 +270,10 @@ For the **current phase only**, run this loop before implementation.
264
270
 
265
271
  ### 1. Create or update the durable phase doc and tmp scaffold
266
272
 
273
+ **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)".
274
+
275
+ Otherwise (default phase-doc path), create or update the durable phase doc.
276
+
267
277
  Use paths like:
268
278
  - `docs/phases/phase-0.md`
269
279
  - `tmp/phases/phase-0/`
@@ -512,7 +522,7 @@ After the phase plan passes review:
512
522
  3. Run local validation:
513
523
  - `npm run verify`
514
524
  - 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
515
- - 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
525
+ - 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
516
526
  - **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.
517
527
  4. Review the implementation against the merged phase plan.
518
528
  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:
@@ -643,6 +653,7 @@ See [Stop Conditions](../docs/stop-conditions.md). Local-specific stops: phase c
643
653
  - 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.
644
654
  - When authorization is pending, record the phase as `awaiting-finalization` and describe the exact missing step.
645
655
  - For phase-doc-backed sessions, merge the fully reviewed, locally validated branch back into local `main` when authorized.
656
+ - 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)).
646
657
 
647
658
  ## Commit policy
648
659