dev-loops 0.2.7 → 0.4.0

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 (76) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +1 -1
  3. package/.claude/agents/developer.md +1 -0
  4. package/.claude/agents/fixer.md +1 -0
  5. package/.claude/agents/review.md +30 -0
  6. package/.claude/hooks/_run-context.mjs +9 -16
  7. package/.claude/skills/copilot-pr-followup/SKILL.md +62 -6
  8. package/.claude/skills/dev-loop/SKILL.md +6 -6
  9. package/.claude/skills/docs/anti-patterns.md +2 -0
  10. package/.claude/skills/docs/copilot-loop-operations.md +3 -3
  11. package/.claude/skills/docs/issue-intake-procedure.md +4 -0
  12. package/.claude/skills/docs/merge-preconditions.md +65 -1
  13. package/.claude/skills/docs/stop-conditions.md +1 -0
  14. package/.claude/skills/docs/tracker-first-loop-state.md +6 -6
  15. package/.claude/skills/local-implementation/SKILL.md +25 -5
  16. package/AGENTS.md +1 -1
  17. package/CHANGELOG.md +69 -0
  18. package/README.md +8 -2
  19. package/agents/developer.agent.md +1 -0
  20. package/agents/fixer.agent.md +1 -0
  21. package/agents/review.agent.md +30 -0
  22. package/cli/index.mjs +60 -8
  23. package/extension/README.md +1 -1
  24. package/package.json +3 -3
  25. package/scripts/README.md +8 -7
  26. package/scripts/_core-helpers.mjs +1 -0
  27. package/scripts/claude/headless-dev-loop.mjs +53 -13
  28. package/scripts/claude/headless-info-smoke.mjs +45 -11
  29. package/scripts/github/build-adjacent-bundle.mjs +448 -0
  30. package/scripts/github/{create-draft-pr.mjs → create-pr.mjs} +28 -12
  31. package/scripts/github/detect-checkpoint-evidence.mjs +95 -4
  32. package/scripts/github/offer-human-handoff.mjs +147 -0
  33. package/scripts/github/post-gate-findings.mjs +392 -0
  34. package/scripts/github/probe-ci-status.mjs +468 -0
  35. package/scripts/github/reconcile-draft-gate.mjs +2 -2
  36. package/scripts/github/request-copilot-review.mjs +72 -11
  37. package/scripts/github/resolve-handoff-candidates.mjs +412 -0
  38. package/scripts/github/upsert-checkpoint-verdict.mjs +599 -17
  39. package/scripts/github/verify-fresh-review-context.mjs +12 -1
  40. package/scripts/github/write-gate-context.mjs +634 -0
  41. package/scripts/github/write-gate-findings-log.mjs +1 -1
  42. package/scripts/loop/_stale-runner-detection.mjs +1 -1
  43. package/scripts/loop/_worktree-path.mjs +27 -0
  44. package/scripts/loop/cleanup-worktree.mjs +175 -0
  45. package/scripts/loop/copilot-pr-handoff.mjs +1 -1
  46. package/scripts/loop/detect-change-scope.mjs +36 -11
  47. package/scripts/loop/detect-pr-gate-coordination-state.mjs +30 -18
  48. package/scripts/loop/detect-stale-runner.mjs +3 -4
  49. package/scripts/loop/detect-tracker-first-loop-state.mjs +38 -11
  50. package/scripts/loop/ensure-worktree.mjs +219 -0
  51. package/scripts/loop/outer-loop.mjs +1 -1
  52. package/scripts/loop/pr-runner-coordination.mjs +1 -1
  53. package/scripts/loop/pre-flight-gate.mjs +10 -7
  54. package/scripts/loop/pre-push-main-guard.mjs +4 -4
  55. package/scripts/loop/provision-worktree.mjs +243 -0
  56. package/scripts/loop/resolve-dev-loop-startup.mjs +5 -5
  57. package/scripts/loop/run-queue.mjs +112 -16
  58. package/scripts/loop/run-watch-cycle.mjs +75 -22
  59. package/scripts/projects/add-queue-item.mjs +80 -48
  60. package/scripts/projects/archive-done-items.mjs +136 -39
  61. package/scripts/projects/ensure-queue-board.mjs +67 -65
  62. package/scripts/projects/list-queue-items.mjs +59 -57
  63. package/scripts/projects/move-queue-item.mjs +125 -125
  64. package/scripts/projects/reorder-queue-item.mjs +67 -48
  65. package/scripts/projects/sync-item-status.mjs +199 -0
  66. package/skills/copilot-pr-followup/SKILL.md +62 -6
  67. package/skills/dev-loop/SKILL.md +2 -2
  68. package/skills/dev-loop/scripts/log-bash-exit-1.mjs +2 -2
  69. package/skills/dev-loop/scripts/phase-files.mjs +2 -2
  70. package/skills/docs/anti-patterns.md +2 -0
  71. package/skills/docs/copilot-loop-operations.md +3 -3
  72. package/skills/docs/issue-intake-procedure.md +4 -0
  73. package/skills/docs/merge-preconditions.md +65 -1
  74. package/skills/docs/stop-conditions.md +1 -0
  75. package/skills/docs/tracker-first-loop-state.md +6 -6
  76. package/skills/local-implementation/SKILL.md +25 -5
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-loops",
3
- "version": "0.2.7",
3
+ "version": "0.4.0",
4
4
  "description": "Agent-harness-agnostic dev-loop: agents, skills, and hooks for GitHub/Copilot-driven development loops.",
5
5
  "author": {
6
6
  "name": "Manuel Fittko",
@@ -22,7 +22,7 @@ The envelope is the primary handoff artifact — it is derived from resolver out
22
22
 
23
23
  **Construction sequence:**
24
24
 
25
- 1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops@0.2.7 loop startup --issue <n>` for issues, or `npx dev-loops@0.2.7 loop startup --pr <n>` for PRs.
25
+ 1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops@0.4.0 loop startup --issue <n>` for issues, or `npx dev-loops@0.4.0 loop startup --pr <n>` for PRs.
26
26
  2. Pass the resolver output, resolved settings (merged from `.devloops` and `.pi/dev-loop/defaults.yaml`), and current gate state to `buildDevLoopHandoffEnvelope()`.
27
27
  3. **Validate the envelope** with `validateHandoffEnvelope()` before consuming any field. If validation returns `ok: false`, reject the handoff with the structured error — do not load requiredReads, do not execute nextAction, do not delegate.
28
28
  4. Read the envelope as the first artifact.
@@ -17,6 +17,7 @@ You are a focused implementation agent. You take a single clearly-scoped coding
17
17
  - Do not re-plan the broader milestone unless a blocker forces it.
18
18
  - Stay within the requested scope and files unless a small adjacent fix is required to complete the task safely.
19
19
  - Preserve existing project conventions and package/runtime behavior.
20
+ - Tooling internals: use a tool's CLI, `--help`, and `skills/docs/` rather than reading its source. See [Anti-patterns](../skills/docs/anti-patterns.md#core-anti-patterns).
20
21
 
21
22
  ## Engineering Principles
22
23
  - Prefer KISS: choose the simplest implementation that fully satisfies the task.
@@ -22,6 +22,7 @@ You are a focused review-fix agent. You take an existing pull request with revie
22
22
  - If no code change is needed, reply with the reasoning and only then resolve if the concern is truly addressed.
23
23
  - When unsure about correctness, architecture, security, or product tradeoffs, pause and ask for expert judgment rather than guessing. Use the available project workflow for expert review when possible, or clearly report the decision needed.
24
24
  - Keep fixes tightly scoped to the review feedback unless a small adjacent change is required for correctness.
25
+ - Tooling internals: use a tool's CLI, `--help`, and `skills/docs/` rather than reading its source. See [Anti-patterns](../skills/docs/anti-patterns.md#core-anti-patterns).
25
26
 
26
27
  ## Review Workflow
27
28
  1. Read unresolved review threads and any general review comments.
@@ -24,6 +24,36 @@ You are a focused pull request review agent. You review an implementation for co
24
24
  - Only perform a full re-review when the caller explicitly requests one (e.g., "full review", "review from scratch", "re-review everything"), or when no prior review by that reviewer exists.
25
25
  - Explicitly state the delta scope at the top of the output (e.g., "Delta review covering commits since `abc1234` on 2026-05-07").
26
26
 
27
+ ## Scoped angle-review mode
28
+
29
+ This agent has two modes. The default mode is the full-PR review described in the rest of this file. The **scoped angle-review mode** is the per-angle reviewer of the gate-review fan-out ([Gate Review Sub-Loop Contract](../docs/gate-review-sub-loop-contract.md)): the gate skill builds ONE deterministic, neutral context bundle (diff + adjacent code) and seeds each independent `review` agent with that identical bundle, each scoped to exactly one review concern. You receive only the neutral artifact + your angle — never the main (orchestrating) agent's conversation, opinions, or state.
30
+
31
+ You are in scoped angle-review mode when the invocation supplies a single review `<angle>` plus a gate-context artifact path (`tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.json`, written by `scripts/github/write-gate-context.mjs`). Review like an external code reviewer hunting real bugs, not a process auditor: your job is to find concrete defects in the changed code and its call paths, not to remark that "there is no test" or that a doc section is thin. In that mode:
32
+
33
+ - **Fresh-context guard (mandatory):** run `node scripts/github/verify-fresh-review-context.mjs --scope <angle>` at startup. If it reports `fresh: false` (exit 1) or any error, refuse to proceed and report contamination — do not review on inherited context. "Fresh" here means your context is the **neutral builder artifact + your angle**, and explicitly NOT the main agent's conversation/state or a prior reviewer session's state. The injected neutral bundle is the INTENDED seed (allowed); main-agent or cross-session state bleed still fails closed.
34
+ - **Use the PROVIDED neutral bundle as your base; do NOT re-derive it from scratch:** the gate-context artifact carries the diff (`scope.diffPath`) and a deterministic, neutral adjacent-code bundle in `adjacentCode` (each changed file plus its 1-hop import in/out-edges, with a `stripped`/`truncated`/`missing` manifest). Start from this bundle — it is the build-once, work-deduped seed shared verbatim across all reviewers. Only widen (load more files) per-angle when the bundle genuinely lacks something your angle needs; do not re-build the whole diff/adjacent-code graph yourself.
35
+ - **Single-angle scope:** review ONLY the supplied angle's concern. Read the gate-context artifact for the resolved angle set, change scope (branch, head SHA, touched files), acceptance-criteria pointer, and validation posture. Do not review other angles; the fan-in pass consolidates across angles.
36
+ - **Read the FULL diff, not just hunks:** the gate-context artifact records `scope.diffPath` (a repo-relative path to the complete captured diff) and `scope.changedFiles` (full repo-relative paths). Read the entire diff from `scope.diffPath`; if it is `null` or missing, reconstruct it yourself with `git diff` against the change's base (e.g. `git diff origin/main...HEAD` or the branch/head SHA in `scope`). Do not review off a partial or summarized hunk list — you must see every changed line for your angle.
37
+ - **Reason about ADJACENT related code from the bundle first:** the `adjacentCode` bundle already includes the changed files' callers, callees, and imports (1-hop). Read those entries before opening anything else. For each changed function, trace the real call paths a defect would flow through, and confirm that caller and callee contracts still match (argument shapes, return shapes, error/null conventions, units). If the bundle truncated or stripped a file you need (see its `truncated`/`stripped` manifest), open that file directly to widen.
38
+ - **Review ADVERSARIALLY — hunt concrete defects:** actively try to break the changed code. Hunt for edge cases, missing or wrong input validation, numeric coercion bugs (NaN, Infinity, floats where integers are assumed, negative values, string-to-number coercion), null/undefined handling, off-by-one and boundary conditions, mismatched caller/callee contracts, and dedup/identity bugs (wrong key, reference vs. value equality, unstable ordering). For every finding, give the concrete `file:line` plus the specific failing scenario or input that triggers the defect — not high-altitude "is there a test?" commentary.
39
+ - **Strictly read-only, but WIDEN SCOPE when needed:** never edit files, stage, commit, push, request reviews, resolve threads, or post PR comments. Findings are returned via the output artifact only; fixes are applied later by the fix cycle, not by this agent. Because you are read-only, you MAY open any additional repository files required to judge your angle (callers, callees, contracts, sibling modules, configs, tests) — widening to read adjacent code is expected and safe. Record every file/module you consulted beyond the briefing's `changedFiles`/`diffPath` in the optional `contextWidened` field on your output artifact.
40
+ - **Structured findings artifact:** return a single JSON object the fan-in consolidator (`@dev-loops/core/loop/gate-fanin`) can parse, written to the deterministic per-angle path `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json`:
41
+
42
+ ```json
43
+ {
44
+ "angle": "<angle>",
45
+ "verdict": "clean" | "findings_present",
46
+ "findings": [
47
+ { "severity": "must-fix" | "worth-fixing-now" | "defer", "file": "<path>", "line": 0, "summary": "<concise>", "recommendation": "<concise fix>" }
48
+ ],
49
+ "contextWidened": ["<adjacent-path-consulted>", "..."]
50
+ }
51
+ ```
52
+
53
+ `verdict` is `clean` iff `findings` is empty; otherwise `findings_present`. `severity` uses the gate vocabulary (`must-fix` | `worth-fixing-now` | `defer`). `file`/`line`/`recommendation` are optional per finding. `contextWidened` is optional: list the adjacent files/modules you opened beyond the briefing to judge this angle (omit or leave empty if you reviewed only `changedFiles`).
54
+
55
+ When NOT given an angle scope, behave exactly as the full-PR review agent described below.
56
+
27
57
  ## Review Focus
28
58
  - Scope correctness: does the implementation match the PR description's change summary, the stated acceptance criteria, and the relevant plan?
29
59
  - Acceptance criteria coverage: are the stated acceptance criteria complete, testable, and actually satisfied?
@@ -2,15 +2,11 @@
2
2
  /**
3
3
  * Neutral run-id / async-context contract.
4
4
  *
5
- * The dev-loop async path historically keyed off Pi's `PI_SUBAGENT_RUN_ID` env var to
5
+ * The dev-loop async path keys off the harness-neutral `DEVLOOPS_RUN_ID` env var to
6
6
  * identify an inspectable per-subagent run (runner ownership, async-start enforcement,
7
- * human-comment gating). This module generalizes that into a harness-neutral
8
- * `DEVLOOPS_RUN_ID`, keeping `PI_SUBAGENT_RUN_ID` as a backward-compatible alias, and
9
- * provides a mint-and-propagate path for harnesses (e.g. Claude Code) that inject no
10
- * native per-subagent run id.
11
- *
12
- * Marker precedence is neutral-first: a present `DEVLOOPS_RUN_ID` wins; otherwise the Pi
13
- * alias is honored. Existing Pi runs that set only `PI_SUBAGENT_RUN_ID` behave identically.
7
+ * human-comment gating), and provides a mint-and-propagate path for harnesses (e.g. Claude
8
+ * Code) that inject no native per-subagent run id. The harness sets `DEVLOOPS_RUN_ID` when
9
+ * dispatching an async subagent.
14
10
  *
15
11
  * This module is pure except for the explicit file/IO helpers (writeRunContext/readRunContext),
16
12
  * which take an injectable `fs` and `root` for testability.
@@ -22,16 +18,13 @@ import path from "node:path";
22
18
 
23
19
  /**
24
20
  * Env var names that carry the async-context run id, in resolution precedence order.
25
- * Neutral `DEVLOOPS_RUN_ID` first; Pi `PI_SUBAGENT_RUN_ID` retained as a compatibility alias.
21
+ * The neutral `DEVLOOPS_RUN_ID` is the sole marker.
26
22
  */
27
- export const RUN_ID_MARKERS = Object.freeze(["DEVLOOPS_RUN_ID", "PI_SUBAGENT_RUN_ID"]);
23
+ export const RUN_ID_MARKERS = Object.freeze(["DEVLOOPS_RUN_ID"]);
28
24
 
29
25
  /** Neutral env var name used when minting/propagating a run id. */
30
26
  export const NEUTRAL_RUN_ID_VAR = "DEVLOOPS_RUN_ID";
31
27
 
32
- /** Pi-compatibility alias env var name. */
33
- export const PI_RUN_ID_ALIAS_VAR = "PI_SUBAGENT_RUN_ID";
34
-
35
28
  /** State-file name (under `.pi/`, consistent with existing dev-loop checkpoint files). */
36
29
  export const RUN_CONTEXT_FILENAME = "dev-loop-run-context.json";
37
30
 
@@ -57,7 +50,7 @@ export function isClaudeHarness(env = process.env) {
57
50
  }
58
51
 
59
52
  /**
60
- * Resolve the active run id from the environment, neutral marker first.
53
+ * Resolve the active run id from the environment.
61
54
  *
62
55
  * @param {Record<string, string|undefined>} [env]
63
56
  * @returns {string|null} The trimmed run id, or null when none is set.
@@ -155,8 +148,8 @@ export function readRunContext({ root, fs = fsDefault }) {
155
148
  * Resolve the active run id, or mint one and persist a run-context state file.
156
149
  *
157
150
  * This is the "mint at startup and propagate" primitive a Claude dev-loop agent (or a
158
- * headless entry) calls before dispatching child work. When the env already carries a run
159
- * id (Pi alias or neutral), it is reused and no new id is minted.
151
+ * headless entry) calls before dispatching child work. When the env already carries a
152
+ * `DEVLOOPS_RUN_ID`, it is reused and no new id is minted.
160
153
  *
161
154
  * @param {object} [params]
162
155
  * @param {Record<string, string|undefined>} [params.env]
@@ -156,9 +156,9 @@ The outer-loop checkpoint is the canonical re-attachment artifact.
156
156
 
157
157
  Start every wait seam with a detector refresh: `detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>`.
158
158
 
159
- Allowed wait tools: `detect-copilot-loop-state.mjs` (one-shot), `dev-loops loop watch-cycle` (persistent), `copilot-pr-handoff.mjs --watch-status` (refresh after timeout/idle), `gh run watch <run-id> --repo <owner/name>` (CI with known run id). Otherwise exit and resume later from fresh detector call.
159
+ Allowed wait tools: `detect-copilot-loop-state.mjs` (one-shot), `dev-loops loop watch-cycle` (persistent), `copilot-pr-handoff.mjs --watch-status` (refresh after timeout/idle), `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic CI: CircleCI / Actions / external commit-status), `gh run watch <run-id> --repo <owner/name>` (Actions-only fallback when the run id is known). Otherwise exit and resume later from fresh detector call.
160
160
 
161
- Practical rules: do not poll manually. `waiting_for_copilot_review` → `run-watch-cycle.mjs` or report-and-resume. `waiting_for_ci` with pending/none CI → `gh run watch <run-id> --repo <owner/name>` (known run id) or report-and-resume. Bounded CI exception: zero current-head suites + previous-head green + local `npm run verify` passed → rerun detector with `--local-validation-head-sha` for `crediblyGreen` promotion. `ciStatus=failure` → stop/fix, never wait.
161
+ Practical rules: do not poll manually. `waiting_for_copilot_review` → `run-watch-cycle.mjs` or report-and-resume. `waiting_for_ci` with pending/none CI → `dev-loops loop watch-ci --repo <owner/name> --pr <number>` (provider-agnostic; covers CircleCI / Actions / external commit-status) or report-and-resume; `gh run watch <run-id>` is an Actions-only fallback. `dev-loops loop watch-cycle` also auto-routes a `waiting_for_ci` boundary to this CI watcher. Bounded CI exception: zero current-head suites + previous-head green + local `npm run verify` passed → rerun detector with `--local-validation-head-sha` for `crediblyGreen` promotion. `ciStatus=failure` → stop/fix, never wait.
162
162
 
163
163
  Preferred approach:
164
164
  - route decisions through `copilot-pr-handoff.mjs` output; enter watcher only on `action: "watch"` with `watchEntryConfirmed=true`; prefer `dev-loops loop watch-cycle` for deterministic handoff → watch
@@ -238,7 +238,7 @@ When unresolved feedback exists, use a narrow follow-up loop:
238
238
  - if that local validation is still known red, continue remediation instead of re-requesting Copilot
239
239
  - after a fix push advances the PR head SHA, re-run `detect-copilot-loop-state.mjs` for the new head and apply the [Copilot CI Status Contract](../docs/copilot-ci-status-contract.md). Previous-head CI is stale; only current-head results unblock CI-dependent steps. if GitHub CI/checks for the updated head are known red for a fixable issue, continue remediation instead of re-requesting Copilot. only once the updated head is green or credibly green, explicitly re-request Copilot review for the new head. Always use `request-copilot-review.mjs` — never `gh api POST repos/.../requested_reviewers` directly.
240
240
  - only enter a wait/watch loop if the request result is confirmed as `requested` or `already-requested`
241
- - for `requested` / `already-requested`, immediately re-baseline with `detect-copilot-loop-state.mjs`; if the returned state is `waiting_for_copilot_review`, use `dev-loops loop watch-cycle` or stop/resume later, and if the returned state is `waiting_for_ci`, use `gh run watch` for a known run id or stop/resume later after that single detector refresh
241
+ - for `requested` / `already-requested`, immediately re-baseline with `detect-copilot-loop-state.mjs`; if the returned state is `waiting_for_copilot_review`, use `dev-loops loop watch-cycle` or stop/resume later, and if the returned state is `waiting_for_ci`, use `dev-loops loop watch-ci` (provider-agnostic CI wait; `gh run watch` is an Actions-only fallback) or stop/resume later after that single detector refresh
242
242
  - if the request result is `unavailable`, report that limitation and stop unless the user explicitly wants passive waiting anyway
243
243
  - if the request command fails unexpectedly, stop and report the error rather than sleeping and hoping for a new review
244
244
  13. after a confirmed re-requested Copilot pass, refresh PR thread state again before reporting completion; if fresh Copilot threads exist, return to this follow-up loop rather than stopping at `review requested`
@@ -251,6 +251,24 @@ Do not treat `fix applied locally` as the end of the loop when the workflow also
251
251
 
252
252
  For every `draft_gate` or `pre_approval_gate` comment, you MUST run:
253
253
 
254
+ For a gate that ran via the fan-out/fan-in sub-loop, pass the structured per-angle review results via `--findings-json` (NOT the wall-of-text `--findings-summary`). The operator/loop writes that JSON file from the collected per-angle results — the same per-angle `{angle, verdict, findings}` objects the fan-out reviewers wrote to `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json` that feed `consolidateFanin`. The helper renders a readable per-angle breakdown and derives the single-line `**Findings summary:**` digest itself:
255
+
256
+ ```sh
257
+ node <resolved-skill-scripts>/github/upsert-checkpoint-verdict.mjs \
258
+ --repo <owner/name> \
259
+ --pr <number> \
260
+ --gate <draft_gate|pre_approval_gate> \
261
+ --head-sha <current_head_sha> \
262
+ --verdict <clean|findings_present|blocked> \
263
+ --findings-json <path-to-per-angle-results.json> \
264
+ --next-action "<next action>" --findings-severity-counts '{"must-fix":0,"worth-fixing-now":0,"defer":0}' \
265
+ --execution-mode fanout_fanin
266
+ ```
267
+
268
+ `--findings-json` accepts the per-angle review-results array (`[{ angle, verdict?, findings:[{severity, summary, file?, line?, disposition?}] }]`, the primary shape that feeds `consolidateFanin`); it also accepts the flat per-finding array that `consolidateFanin`/`toFindingsLogShape` produce (`[{ severity, summary, angle?, ... }]`), grouping it by each finding's `.angle`. A non-empty input matching neither shape is rejected rather than silently rendering all-clean. If the structured results are not available, fall back to `--findings-summary "<summary>"` (the inline_single_agent fallback path).
269
+
270
+ For a gate that ran inline (single agent, not via the sub-loop):
271
+
254
272
  ```sh
255
273
  node <resolved-skill-scripts>/github/upsert-checkpoint-verdict.mjs \
256
274
  --repo <owner/name> \
@@ -259,13 +277,26 @@ node <resolved-skill-scripts>/github/upsert-checkpoint-verdict.mjs \
259
277
  --head-sha <current_head_sha> \
260
278
  --verdict <clean|findings_present|blocked> \
261
279
  --findings-summary "<summary>" \
262
- --next-action "<next action>" --findings-severity-counts '{"must-fix":0,"worth-fixing-now":0,"defer":0}'
280
+ --next-action "<next action>" --findings-severity-counts '{"must-fix":0,"worth-fixing-now":0,"defer":0}' \
281
+ --execution-mode inline_single_agent --inline-reason "<why>"
263
282
  ```
264
283
 
284
+ `--execution-mode <fanout_fanin|inline_single_agent>` records how the gate review ran (default `inline_single_agent`). When the gate did not run via the fan-out/fan-in sub-loop ([Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md)), you MUST pass `--execution-mode inline_single_agent --inline-reason "<why>"` — silent inline runs are no longer allowed: inline mode requires a non-empty `--inline-reason` and emits a stderr warning. Because inline is the default mode, a bare call with neither flag now fails with an argument error, so always pass `--execution-mode` explicitly (and `--inline-reason` for inline). The recorded `executionMode` is surfaced by `detect-checkpoint-evidence.mjs` and gated by `gates.requireFanoutEvidence`.
285
+
265
286
  Do NOT use `gh pr comment`, `gh api`, or `gh pr review` for gate comments.
266
287
 
267
288
  `--force --force-reason` on `upsert-checkpoint-verdict.mjs` is a narrow operator-authorized CI override for the helper itself, not the default gate path. Use it only when the helper refuses gate entry solely because the current head is `blocked_needs_user_decision` with `ciStatus="failure"`, and only after the user explicitly authorizes ignoring that current-head CI failure for this one gate-comment upsert. It does **not** bypass stale-head checks, unresolved-thread / unsettled-review refusal, non-draft `draft_gate` refusal, merge conflicts, or other legality checks.
268
289
 
290
+ ### Gate fan-out/fan-in procedure (agent-orchestrated)
291
+
292
+ Both gates run this same checkpoint review chain. It is an **agent-orchestrated skill procedure** — a node script cannot spawn the per-angle reviewers, so the conductor agent drives the fan-out and uses the pure `@dev-loops/core/loop/gate-fanin` helpers only for consolidation, batching, and ledger mapping. The cost model is **build once, seed many**: the context-builder script builds ONE deterministic, neutral context bundle (diff + adjacent code) and each independent reviewer is seeded with that identical bundle verbatim instead of re-deriving it (work-dedup; no fork primitive, no Workflow tool). See [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md).
293
+
294
+ 1. **Context (Phase 1) — build the neutral bundle ONCE:** build/read the gate-context artifact via `scripts/github/write-gate-context.mjs` (`buildGateContext` resolves the angle set through `resolveGateAnglesDynamic`; mandatory angles always survive). The artifact at `tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.json` carries the resolved angles + change scope + acceptance-criteria pointer. When the resolver was handed a diff with `diffOutput`, `buildGateContext` also persists the FULL diff to `tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.diff` and records `scope.diffPath` (relative) plus `scope.changedFiles` (full repo-relative paths). It ADDITIONALLY builds a deterministic, neutral `adjacentCode` bundle — each changed file plus its 1-hop import in/out-edges (callers/callees/imports), with size guards (skip lockfiles/generated/binary/minified; cap per-file bytes; truncate the long tail) recorded in a `stripped`/`truncated`/`missing` manifest. This single bundle is the build-once seed; reviewers do not re-derive the diff + adjacent code.
295
+ 2. **Fan-out (Phase 2) — seed each reviewer with the bundle verbatim:** plan batches with `planFanoutBatches(angles, resolveMaxFanoutReviewers(config))` (default cap 8). Spawn one scoped `review` agent per resolved angle (plain Agent tool), parallel up to the cap, batching any overflow sequentially. When `degraded` is true, record the sequential degradation in the gate evidence. Reviewers are independent: each is seeded with the IDENTICAL neutral bundle (the gate-context artifact's `scope.diffPath` + `adjacentCode`) verbatim plus its single angle, and never inherits the conductor agent's conversation or opinions. Each reviewer follows the [review agent scoped angle-review mode](../../agents/review.agent.md): fresh context, single angle, read-only, writing its per-angle findings artifact to `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json`. Brief each reviewer to use the provided bundle as its base and to review like an external code reviewer hunting real bugs: read the FULL diff (from `scope.diffPath`, or `git diff` when null) and the bundled adjacent code (callers, callees, imports) rather than re-deriving them, then review adversarially for concrete defects (edge cases, input validation, numeric coercion incl. NaN/Infinity/floats/negatives, null/undefined, boundary conditions, mismatched caller/callee contracts, dedup/identity bugs) with `file:line` + the failing scenario — not process nits like "no test exists". Reviewers are read-only and MAY widen scope (open adjacent repo files beyond the bundle) only when their angle genuinely needs more; that widening is recorded in the optional `contextWidened` field on each per-angle findings artifact.
296
+ 3. **Fan-in (Phase 3):** consolidate the per-angle artifacts via `consolidateFanin({ angleResults, blockCleanOnFindingSeverities })` (blocking severities from `resolveGateConfig(config, <configKey>).blockCleanOnFindingSeverities`, where `<configKey>` is the config key for the gate — `draft` for `draft_gate`, `preApproval` for `pre_approval_gate` — not the `draft_gate|pre_approval_gate` artifact name). It yields the gate `verdict` (`clean` when no blocking-severity finding remains, `findings_present` when one does, `blocked` when any per-angle artifact is malformed/missing). Map consolidated findings with `toFindingsLogShape(...)` and write the disposition ledger via `write-gate-findings-log.mjs` **before** the visible comment. The ledger is written regardless of any comment opt-out. Then, when `resolveGatePostFindingsComments(config)` is true (default; opt-out via `gates.postFindingsComments: false`), post the consolidated findings as a visible, marker-tagged PR comment via `node <resolved-skill-scripts>/github/post-gate-findings.mjs --repo <owner/name> --pr <number> --gate <gate> --head-sha <current_head_sha> --findings '<toFindingsLogShape JSON>'` so the findings are auditable and Copilot/humans are aware of them. The helper is idempotent per gate (exactly one comment per gate, updated in place on each run; the reviewed head is shown in the body) and posts a brief "no findings" note when the consolidated set is empty. When `postFindingsComments` is false the helper no-ops with a `skipped` result and you skip this post step; the ledger still records the findings.
297
+ 4. **Verdict (Phase 4):** post the verdict with the [Gate comment command](#mandatory-gate-comment-command-contract) using `--execution-mode fanout_fanin` and `--findings-json <path>`. Write that JSON from the collected per-angle results (the same per-angle `{angle, verdict, findings}` artifacts at `tmp/gate-reviews/<repo-slug>/pr-<N>/<gate>-<headSha>/<angle>.json` that fed `consolidateFanin` in Phase 3; the flat `toFindingsLogShape` output is also accepted and grouped by `.angle`) so the verdict comment renders the structured per-angle breakdown instead of a wall-of-text summary. Do NOT use `--findings-summary` for a fan-out gate — that is the inline_single_agent fallback.
298
+ 5. **Retry (Phase 5):** on blocking findings, drive each internal fan-out finding through the SAME fix → reply-with-resolving-commit → resolve loop used for external Copilot review comments (Step 7 above): fix on the branch, push the resolving commit, then re-run only the `findings_present` angles from the previous pass (the context-builder and fan-in always re-run); repeat until the consolidated verdict is `clean` for the current head SHA. Internal fan-out findings and external review comments are handled identically by that loop.
299
+
269
300
  ### Draft gate contract (before marking PR ready for review)
270
301
 
271
302
  The canonical checkpoint verdict comment contract is [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). This section summarizes the procedural integration only.
@@ -273,11 +304,16 @@ The canonical checkpoint verdict comment contract is [Gate Review Comment Contra
273
304
  - **Gate name:** Draft gate
274
305
  - **Trigger / boundary:** right before running `gh pr ready` (draft → ready for review)
275
306
  - **Skip rule:** before entering the draft gate, run `detect-pr-gate-coordination-state.mjs` and check `draftGateAlreadySatisfied`. If `true`, skip the draft gate entirely — the draft→ready transition was already recorded. `draft_gate` is a one-time gate; do not re-post on new heads once clean draft-gate evidence exists for the transition record. (While the PR is still draft, advancing the head SHA does require a new draft-gate comment for the new head.) This skip rule applies only to the draft boundary.
276
- - **Execution directive:** run the checkpoint review chain defined in [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md) with the draft gate inspection angles resolved from config.
307
+ - **Execution directive:** run the [Gate fan-out/fan-in procedure](#gate-fan-outfan-in-procedure-agent-orchestrated) (the agent-orchestrated chain defined in [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md)) with the draft gate inspection angles resolved from config.
277
308
  - **Review angles:** resolved at runtime from config via `resolveGateAngles(config, "draft")` from `@dev-loops/core/config`. Default config enables all configured draft gate angle families; consumer repos may opt out individual angles via `excludeAngles`. Do **not** apply angles from the other gate; each gate owns its own angle list from config.
278
309
  - **CI prerequisite:** resolve the draft gate config first (`resolveGateConfig(config, "draft")`). When `requireCi=true` (default), wait for green current-head CI before entering `draft_gate`. When `requireCi=false`, the draft gate may proceed without green CI. This draft-only override does **not** relax `pre_approval_gate`; final approval and merge readiness still require green current-head CI.
279
310
  - **Pass criteria:** all configured draft gate angles pass; all findings at severities in `blockCleanOnFindingSeverities` are addressed; validation passes; no unrelated files are included.
280
311
  - **Next step after passing:** mark the PR ready for review.
312
+ - **Board status sync (best-effort, after ready-for-review):** once the PR is created/marked ready for review, sync the linked issue's board item to "In Progress" so the queue board reflects active work without a manual `dev-loops queue sync-status --to-column <col>`:
313
+ ```sh
314
+ node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "In Progress"
315
+ ```
316
+ Best-effort and NON-FATAL: it uses local `gh` auth (no CI/PAT), exits 0 when the board is not configured / the item is not on the board / the API fails, and never blocks marking the PR ready.
281
317
  - **Non-substitution rule:** a clean `draft_gate` comment only authorizes the draft → ready-for-review transition for that head SHA. It does **not** satisfy `pre_approval_gate`, final-approval readiness, or merge-ready requirements.
282
318
  - **Required PR comment:** post a visible checkpoint verdict comment using the mandatory [Gate comment command](#mandatory-gate-comment-command-contract). Keep validation reporting concise: include command names with pass/fail status. Do **not** paste raw passing test output into the visible gate comment. If you include a failing validation excerpt, keep it focused and truncate it to a deterministic retained-prefix length before posting the comment. See [Gate Review Comment Contract](../../docs/gate-review-comment-contract.md). Do not run `gh pr ready` unless a visible `clean` `draft_gate` checkpoint verdict comment exists for the current head SHA. A checkpoint verdict comment for an older head SHA does not satisfy this requirement for the current head. If findings exist, the PR stays draft and needs fixes before retry. If fixes advance the head SHA while still draft, post a new checkpoint verdict comment for the new head. If the checkpoint verdict comment cannot be posted, fail closed and do not run `gh pr ready`.
283
319
 
@@ -287,7 +323,7 @@ This is the default pre-approval gate for this workflow boundary. The canonical
287
323
 
288
324
  - **Gate name:** Pre-approval gate
289
325
  - **Trigger / boundary:** right before calling a PR/branch review-complete, approval-ready, merge-ready, or ready for final handoff
290
- - **Execution directive:** run the checkpoint review chain defined in [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md) with the pre-approval gate inspection angles resolved from config. Retry rule: in subsequent cycles, only re-run reviewers that produced `findings_present` in the previous pass.
326
+ - **Execution directive:** run the [Gate fan-out/fan-in procedure](#gate-fan-outfan-in-procedure-agent-orchestrated) (the agent-orchestrated chain defined in [Gate Review Sub-Loop Contract](../../docs/gate-review-sub-loop-contract.md)) with the pre-approval gate inspection angles resolved from config. The `acceptance-criteria` angle is mandatory for this gate (see `.devloops` `gates.preApproval.mandatoryAngles`) and always survives dynamic resolution. Retry rule: in subsequent cycles, only re-run reviewers that produced `findings_present` in the previous pass.
291
327
  - **Review angles:** resolved at runtime from config via `resolveGateAngles(config, "preApproval")` from `@dev-loops/core/config`. Default config enables all configured pre-approval gate angle families; consumer repos may opt out individual angles via `excludeAngles`.
292
328
  - **Persona mapping:** each angle resolves to a reviewer persona via `resolveReviewerRole(config, angle)` from `@dev-loops/core/config`. Include this prompt in each reviewer's briefing so the reviewer knows exactly what to look for.
293
329
  - **Pass criteria:** the sub-loop completes with verdict `clean`; all configured angles pass; if parallel execution is impractical, still run all configured lenses and explicitly record the limitation.
@@ -321,6 +357,8 @@ See [Merge Preconditions](../docs/merge-preconditions.md). Verify: zero unresolv
321
357
  After merge-ready preconditions pass, verify [Merge Preconditions](../docs/merge-preconditions.md) authoritatively before reporting merge-ready. Stop at the human approval checkpoint by default. Cross-check via `dev-loops gate capture-threads` (not prose assertion).
322
358
  Follow [Merge Preconditions](../docs/merge-preconditions.md): stop at `waiting_for_merge_authorization` after approval unless merge explicitly authorized. Run pre-merge gate evidence check before any `gh pr merge`.
323
359
 
360
+ When `approval.humanHandoff.enabled` is set, don't just park silently at this stop: run `dev-loops gate offer-human-handoff --repo <owner/name> --pr <number>` to surface candidate reviewers/assignees, then **offer** them to the operator. Only on operator confirmation, route the PR with `--assign <login>` / `--request-review <login>`. This is OFFER-only — never auto-assign. See the `approval.humanHandoff` section in [Merge Preconditions](../docs/merge-preconditions.md); it pairs with `autonomy.humanMergeOnly`.
361
+
324
362
  ### Mechanical pre-merge gate evidence check
325
363
 
326
364
  Immediately before any `gh pr merge`, run:
@@ -349,6 +387,24 @@ node <resolved-skill-scripts>/loop/checkpoint-contract.mjs --state skipped --rea
349
387
 
350
388
  Do not report completion or advance to the next PR queue item until `.pi/dev-loop-retrospective-checkpoint.json` is updated to `complete` or `skipped`.
351
389
 
390
+ ### Post-merge board archive (best-effort)
391
+
392
+ After the retrospective checkpoint write, run the post-merge board archive as a standard step of the post-merge hook (see [Merge Preconditions](../docs/merge-preconditions.md) "Post-merge"):
393
+
394
+ ```sh
395
+ node <resolved-skill-scripts>/projects/archive-done-items.mjs --repo <owner/name> || true
396
+ ```
397
+
398
+ Board and threshold resolve from `.devloops` (`queue.projectNumber`/`queue.boardTitle`, `queue.archiveOlderThanDays`, default 7d), using local `gh` auth — no CI, cron, or PAT. This step is best-effort and NON-FATAL: ignore any failure and never let it block the merge or the retrospective.
399
+
400
+ Alongside the archive step, sync the linked issue's board item to "Done" so the queue board reflects the merge without a manual `dev-loops queue sync-status --to-column <col>`:
401
+
402
+ ```sh
403
+ node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "Done"
404
+ ```
405
+
406
+ Same best-effort/NON-FATAL contract as the archive step: local `gh` auth (no CI/PAT), exits 0 when the board is not configured / the item is not on the board / the API fails, and never blocks the merge or the retrospective.
407
+
352
408
  ## Validation policy
353
409
 
354
410
  Follow [Validation Policy](../docs/validation-policy.md). Default: `npm run verify` before PR creation, gate entry, and merge. For repo-local examples: `npm run test:dev-loop` for skill scripts, contract tests for templates, `git diff --check` for docs. When CI runs exist, use `gh run watch` or `detect-copilot-loop-state.mjs` instead of `sleep`-based polling. Distinguish: locally validated, full PR-equivalent checks, awaiting CI.
@@ -25,7 +25,7 @@ Required installed runtime contract docs are shared bundled copies under `../doc
25
25
 
26
26
  > Under the Claude Code harness the dev-loop runs as a single agent: run these steps directly — no read-only boundary and no separate async-subagent dispatch. See [Main Agent Contract](../docs/main-agent-contract.md).
27
27
 
28
- Resolve authoritative state via the startup resolver (`npx dev-loops@0.2.7 loop startup --issue <n>` for issues, `npx dev-loops@0.2.7 loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops@0.2.7 loop build-envelope --input <resolver-output.json>`. The envelope determines `requiredReads`, `nextAction`, `stopRules`, and `acceptance` — load only those files, execute only that bounded task. It is the first handoff artifact consumed before loading any route pack. See [Workflow Handoff Contract](../docs/workflow-handoff-contract.md) for the derivation contract.
28
+ Resolve authoritative state via the startup resolver (`npx dev-loops@0.4.0 loop startup --issue <n>` for issues, `npx dev-loops@0.4.0 loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops@0.4.0 loop build-envelope --input <resolver-output.json>`. The envelope determines `requiredReads`, `nextAction`, `stopRules`, and `acceptance` — load only those files, execute only that bounded task. It is the first handoff artifact consumed before loading any route pack. See [Workflow Handoff Contract](../docs/workflow-handoff-contract.md) for the derivation contract.
29
29
 
30
30
  **Retrospective checkpoint gate:** the resolver reads `.pi/dev-loop-retrospective-checkpoint.json` and injects the state. When the checkpoint is `missing` and the repo config `workflow.requireRetrospective` (set via `.devloops` at repo root) is `true`, the resolver returns `needs_reconcile`. Complete or explicitly skip the retrospective before starting.
31
31
 
@@ -81,7 +81,7 @@ Do not preload route packs before the resolver selects the strategy.
81
81
 
82
82
  ## Async dispatch
83
83
 
84
- **Async dispatch rule (enforced):** the resolver fails closed for GitHub-first strategies when `canonicalStateSummary.requiresAsyncDispatch` is `true` (default `required` mode) — inline invocation without an async run-id marker (`DEVLOOPS_RUN_ID`, or the `PI_SUBAGENT_RUN_ID` alias) is rejected for those routes. Under the Claude Code harness this requirement is relaxed automatically. See [Startup procedure](#startup-procedure).
84
+ **Async dispatch rule (enforced):** the resolver fails closed for GitHub-first strategies when `canonicalStateSummary.requiresAsyncDispatch` is `true` (default `required` mode) — inline invocation without an async run-id marker (`DEVLOOPS_RUN_ID`) is rejected for those routes. Under the Claude Code harness this requirement is relaxed automatically. See [Startup procedure](#startup-procedure).
85
85
 
86
86
  ## Fallback gate-comment poster
87
87
 
@@ -99,10 +99,10 @@ When `@dev-loops/core` is available again, switch back to the full helper. The f
99
99
 
100
100
  ## Read-only info shortcut
101
101
 
102
- Info/handoff requests can be served directly via `npx dev-loops@0.2.7 loop info` (read-only; no full dev-loop run required):
103
- - `npx dev-loops@0.2.7 loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
104
- - `npx dev-loops@0.2.7 loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
105
- - `npx dev-loops@0.2.7 loop info --issue <n> --json` — machine-readable JSON output
102
+ Info/handoff requests can be served directly via `npx dev-loops@0.4.0 loop info` (read-only; no full dev-loop run required):
103
+ - `npx dev-loops@0.4.0 loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
104
+ - `npx dev-loops@0.4.0 loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
105
+ - `npx dev-loops@0.4.0 loop info --issue <n> --json` — machine-readable JSON output
106
106
 
107
107
  ## Guard rules
108
108
 
@@ -10,6 +10,8 @@ Canonical owner for anti-pattern guidance across all workflow families.
10
10
  4. **Merging directly to main without PR**: Use PR-based remote loop when practical.
11
11
  5. **Duplicate worktree paths**: Check `git worktree list` before creating new worktrees; reuse existing matching worktrees.
12
12
  6. **Main-checkout mutation**: Reserve main checkout for inspection/control; use `tmp/worktrees/` paths for mutation work.
13
+ 7. **Spelunking tooling internals instead of using the public surface**: Do not read installed package internals, scan tooling source, or run ad-hoc scripts to understand a tool's behavior. Use the CLI, its `--help` subcommands, and `skills/docs/`. Read tool source only when the task is to inspect or change that tool, or when a concrete failure path is inside it and no public CLI/docs path exists. Once a failure is concrete, search changed files for the exact pattern — don't run duplicate broad searches.
14
+ 8. **Hand-editing the queue file when a board is configured**: When a GitHub Projects board is configured (`queue.projectNumber`/`queue.boardTitle` in `.devloops`), the board is the authoritative queue MEMBERSHIP and ordering source — not just status. Add work via the board (`dev-loops queue add ... --column "Next Up"`), not by hand-editing `.pi/dev-loop-queue.json`; the queue runner reconciles board `Next Up` items into queue entries before each run. See the operator guides under `docs/` (queue board usage/setup) for the workflow.
13
15
 
14
16
  ## Light mode exception
15
17
 
@@ -51,7 +51,7 @@ For tracker-first MVP `story -> PR -> tracker sync` work, the source-repo refere
51
51
 
52
52
  4. Branch on the detector output instead of inventing a polling loop:
53
53
  - `state=waiting_for_copilot_review` with `snapshot.copilotReviewOnCurrentHead=false`: do **not** poll manually; either run `node <resolved-skill-scripts>/loop/run-watch-cycle.mjs --repo <owner/name> --pr <number>` for persistent async waiting or report the wait state and resume later after the single detector call
54
- - `state=waiting_for_ci` with `snapshot.ciStatus` in `{ "pending", "none" }`: do **not** poll manually by default; use `gh run watch <run-id> --repo <owner/name>` when the current-head run id is already known, otherwise report pending CI and resume later after the single detector refresh. Bounded exception: if GitHub created zero current-head check suites/statuses, the previous head rollup was green, and local `npm run verify` already passed for the same current head, rerun `detect-copilot-loop-state.mjs` with `--local-validation-head-sha <current-head-sha>` so the detector can promote that exact zero-suite case to `snapshot.ciStatus="crediblyGreen"` instead of waiting forever on raw `none`.
54
+ - `state=waiting_for_ci` with `snapshot.ciStatus` in `{ "pending", "none" }`: do **not** poll manually by default; use `dev-loops loop watch-ci --repo <owner/name> --pr <number>` to block-wait on the combined check/status state (provider-agnostic — CircleCI, GitHub Actions, and any external commit-status / check-run). `gh run watch <run-id>` is an Actions-only fallback when the current-head run id is already known and you know the gating checks are GitHub Actions. Either way, on `timeout`/`changed` report pending CI and resume later after the single detector refresh. Bounded exception: if GitHub created zero current-head check suites/statuses, the previous head rollup was green, and local `npm run verify` already passed for the same current head, rerun `detect-copilot-loop-state.mjs` with `--local-validation-head-sha <current-head-sha>` so the detector can promote that exact zero-suite case to `snapshot.ciStatus="crediblyGreen"` instead of waiting forever on raw `none`.
55
55
  - `snapshot.ciStatus="failure"` remains a stop/fix state, never a wait loop
56
56
 
57
57
  5. For reviewer-side draft-review work, run `node <resolved-skill-scripts>/loop/detect-reviewer-loop-state.mjs --repo <owner/name> --pr <number> [--reviewer-login <login>] [--local-state <path>]`.
@@ -172,9 +172,9 @@ Checkbox rule: acceptance criteria, definition-of-done items, and any task list
172
172
 
173
173
  New PRs in this workflow must be opened as **draft** PRs first when the repository enables `.devloops` at repo root `workflow.requireDraftFirst`. The built-in shipped default remains permissive; this repo opts in. Do not create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy for the current PR scope. The draft gate inspection is a real workflow boundary, so a new PR must exist in draft before `gh pr ready` is even eligible.
174
174
 
175
- Only use `node <resolved-skill-scripts>/github/create-draft-pr.mjs` when authoritative issue↔PR resolution says there is no already-open linked PR. If a PR already exists, reuse/update that canonical PR instead of opening another one. This wrapper preserves the underlying `gh pr create` output contract while enforcing draft-first mechanically.
175
+ Only use `node <resolved-skill-scripts>/github/create-pr.mjs` when authoritative issue↔PR resolution says there is no already-open linked PR. If a PR already exists, reuse/update that canonical PR instead of opening another one. This wrapper preserves the underlying `gh pr create` output contract while enforcing draft-first mechanically and self-assigning the PR by default (`--assignee @me`).
176
176
 
177
- MUST use `node <resolved-skill-scripts>/github/create-draft-pr.mjs --repo <owner/name> --assignee @me --base <base> --head <head> --title "..." --body-file <body-file>`.
177
+ MUST use `node <resolved-skill-scripts>/github/create-pr.mjs --repo <owner/name> --assignee @me --base <base> --head <head> --title "..." --body-file <body-file>` (always draft, self-assigned by default; `--assignee @me` is the default).
178
178
 
179
179
  ## Timeout and watch policy
180
180
 
@@ -224,6 +224,10 @@ gh pr edit <pr-number> --repo <resolved-repo> --title "..." --body-file <body-fi
224
224
  gh pr ready <pr-number> --repo <resolved-repo>
225
225
  gh pr review <pr-number> --repo <resolved-repo> --approve --body "..."
226
226
  node <resolved-skill-scripts>/github/detect-checkpoint-evidence.mjs --repo <resolved-repo> --pr <pr-number>
227
+ ```
228
+
229
+ The merge itself is gated, not implied by the lines above. Before any `gh pr merge`, follow [Merge Preconditions](./merge-preconditions.md): all preconditions (green CI, clean `draft_gate` + current-head `pre_approval_gate`, zero unresolved threads, explicit merge authorization) must hold. Critically, when `autonomy.humanMergeOnly: true` is set, merge is a fixed human-only action — `resolveEffectiveMergeAuthorized` fails closed, the agent **never** runs `gh pr merge`, and instead reports merge-ready + gate evidence and hands off to a human. Only when **not** `humanMergeOnly` and merge is explicitly authorized may the agent run:
230
+ ```sh
227
231
  gh pr merge <pr-number> --repo <resolved-repo> --squash --delete-branch
228
232
  ```
229
233
 
@@ -32,9 +32,73 @@ A marker is allowed only while the PR is still in draft; it must be removed befo
32
32
  - `"Merge authorized if gates green"` is valid explicit authorization
33
33
  - Implied approval from prior turns is not sufficient
34
34
 
35
+ ### `autonomy.humanMergeOnly` — fixed human-only merge (non-overridable)
36
+
37
+ When a repo sets `autonomy.humanMergeOnly: true` in `.devloops`, merge is a fixed
38
+ human action and this authorization step is **non-overridable**:
39
+
40
+ - `resolveAutonomyStopAt` always includes `merge`, even if `stopAt` is set to `[]`.
41
+ - The effective merge authorization fails closed: `resolveEffectiveMergeAuthorized`
42
+ returns `false` regardless of the `mergeAuthorized` envelope flag or an explicit
43
+ "merge" instruction. The lifecycle resolver therefore never advances to the merge
44
+ state and parks at the `pre_approval_gate` human-merge handoff.
45
+ - The agent still runs the full mechanical pre-merge evidence check and reports
46
+ merge-ready + gate evidence, then hands off to a human to perform `gh pr merge`.
47
+ The agent **never** runs `gh pr merge` itself.
48
+
49
+ This makes human-gated merge an enforced repo invariant, not a per-run default an
50
+ explicit instruction can unlock.
51
+
52
+ ### `approval.humanHandoff` — offer to assign a human at the handoff (opt-in)
53
+
54
+ When a repo sets `approval.humanHandoff.enabled: true` in `.devloops`, the loop
55
+ does not just park silently at the human-merge stop — at the
56
+ `pre_approval_gate` / `waiting_for_merge_authorization` boundary it **offers**
57
+ to route the PR to a named human (pairs with `autonomy.humanMergeOnly`: when
58
+ human-merge is enforced, the handoff names who should take it). Disabled by
59
+ default; no candidate sourcing when disabled.
60
+
61
+ ```yaml
62
+ approval:
63
+ humanHandoff:
64
+ enabled: true
65
+ candidatesFrom: [codeowners, recent-committers]
66
+ assignees: [alice, bob]
67
+ ```
68
+
69
+ At the handoff boundary, resolve and surface candidates:
70
+
71
+ ```sh
72
+ dev-loops gate offer-human-handoff --repo <owner/name> --pr <number>
73
+ ```
74
+
75
+ This prints the deduped, ordered candidate list (priority:
76
+ `assignees` > `codeowners` for the touched paths (last-match-wins) >
77
+ `recent-committers` to those paths, PR author/bots excluded). It assigns no one.
78
+
79
+ **OFFER-only — the operator confirms the assignee** (auto-assigning without
80
+ confirmation is a non-goal). On confirmation, perform the action:
81
+
82
+ ```sh
83
+ dev-loops gate offer-human-handoff --repo <owner/name> --pr <number> \
84
+ --assign <login> --request-review <login>
85
+ ```
86
+
87
+ which runs `gh pr edit --add-assignee` / `--add-reviewer` for the confirmed
88
+ human(s).
89
+
35
90
  ## Post-merge
36
91
 
37
- - Remove merged worktree: `git worktree remove --force <path> && git worktree prune`
92
+ - Remove merged worktree (canonical): `node scripts/loop/cleanup-worktree.mjs --repo-root <main> (--issue <n> | --pr <n>)`.
93
+ It resolves the namespaced path, runs `git worktree remove --force <path> && git worktree prune` (the underlying
94
+ mechanism) from the main checkout, and refuses any path not under `tmp/worktrees/dev-loops/`. See
95
+ [worktree guidance](../../docs/worktree-guidance.md).
96
+ - Archive long-done queue items (operator-induced, NOT a cron): `node scripts/projects/archive-done-items.mjs --repo <owner/name> || true`.
97
+ Runs as part of this post-merge hook. It applies the configured `queue.archiveOlderThanDays` (default `7d`) and archives
98
+ board items whose issue/PR has been closed at least that long. Best-effort: run it as a standard post-merge step but ignore
99
+ any non-zero exit (a successful run that finds nothing to archive exits 0; a board/config-resolution/API error exits
100
+ non-zero, which the `|| true` masks) — a failure here must never block merge completion. See
101
+ [projects queue usage](../../docs/projects-queue-usage.md).
38
102
  - Clean up stale branches
39
103
 
40
104
  ## Cross-references
@@ -10,6 +10,7 @@ Canonical owner for agent stop / wait / block conditions across all workflow fam
10
10
  | `done` lifecycle state | all | Terminal stop |
11
11
  | `approval_ready` without explicit merge auth | `final_approval` | Stop at approval gate |
12
12
  | `merge_ready` without explicit merge auth | all | Stop at `waiting_for_merge_authorization` |
13
+ | `autonomy.humanMergeOnly: true` | all | Always stop at merge for human action, even with explicit merge auth — agent never runs `gh pr merge` (see [Merge preconditions](merge-preconditions.md)) |
13
14
  | Ambiguous / contradictory state | all | Fail closed to `needs_reconcile` |
14
15
  | Missing authoritative startup inputs | `dev-loop` | Fail closed |
15
16
 
@@ -1,7 +1,7 @@
1
1
  # Tracker-First Story-to-PR Contract
2
2
 
3
3
  This document defines the adapter-agnostic MVP contract for the tracker-first
4
- story-to-PR workflow in `pi-dev-loops`.
4
+ story-to-PR workflow in `dev-loops`.
5
5
 
6
6
  **MVP invariant: one tracker work item → one GitHub PR.**
7
7
 
@@ -43,9 +43,9 @@ are out of scope for this slice.
43
43
  | PR lifecycle facts | GitHub | Draft / ready-for-review, open / merged / closed, branch, head SHA |
44
44
  | PR review and CI facts | GitHub | Reviewer assignments, review states, check-run results, merge status |
45
45
  | Decision content | ADR / RFC artifacts (when linked) | Architecture decisions and RFCs referenced from PR body |
46
- | PR projection and reverse-sync logic | `pi-dev-loops` | Title/body/label generation rules, state mapping, sync triggers |
46
+ | PR projection and reverse-sync logic | `dev-loops` | Title/body/label generation rules, state mapping, sync triggers |
47
47
 
48
- `pi-dev-loops` **does not** become the canonical owner of any business fields.
48
+ `dev-loops` **does not** become the canonical owner of any business fields.
49
49
  It provides projection and sync logic only.
50
50
 
51
51
  ## 3. PR Projection Contract
@@ -234,9 +234,9 @@ This contract is intentionally narrower than the parent epics:
234
234
 
235
235
  ## 7. Related
236
236
 
237
- - Parent workflow-family epic: [mfittko/pi-dev-loops#17](https://github.com/mfittko/pi-dev-loops/issues/17)
238
- - Umbrella artifact model epic: [mfittko/pi-dev-loops#19](https://github.com/mfittko/pi-dev-loops/issues/19)
239
- - This contract (first implementable slice): [mfittko/pi-dev-loops#21](https://github.com/mfittko/pi-dev-loops/issues/21)
237
+ - Parent workflow-family epic: [mfittko/dev-loops#17](https://github.com/mfittko/dev-loops/issues/17)
238
+ - Umbrella artifact model epic: [mfittko/dev-loops#19](https://github.com/mfittko/dev-loops/issues/19)
239
+ - This contract (first implementable slice): [mfittko/dev-loops#21](https://github.com/mfittko/dev-loops/issues/21)
240
240
  - Copilot loop state graph: [Copilot Loop State Graph](../../docs/copilot-loop-state-graph.md)
241
241
  - Reviewer loop state graph: [Reviewer Loop State Graph](../../docs/reviewer-loop-state-graph.md)
242
242
 
@@ -55,7 +55,7 @@ When the local spec already lives in a tracker issue:
55
55
  - sync durable scope / acceptance / status changes back to the tracker issue rather than maintaining a duplicate local phase doc
56
56
  - keep `tmp/` as temporary local execution state only; it does not become a second durable spec surface
57
57
  - for tracker-backed sessions, the handoff path is always: push the working branch → open a PR → merge via GitHub
58
- - for tracker-backed sessions, PR creation must always include `--assignee @me` so the new PR is self-assigned, 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-draft --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.
58
+ - for tracker-backed sessions, always open PRs through the canonical `dev-loops pr create` path, 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`. 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.
59
59
  - do not suggest a direct local-main merge for tracker-backed sessions; do not merge the working branch into local `main` at phase completion
60
60
 
61
61
  ## Primary execution rules
@@ -68,7 +68,13 @@ For the `local_implementation` strategy, before any planning or implementation m
68
68
  node scripts/loop/pre-flight-gate.mjs --expected-branch <working-branch> --check-subagents
69
69
  ```
70
70
 
71
- Before creating or reusing a worktree for local implementation, always run `git fetch origin` first. Always create worktrees from a freshly fetched `origin/main`. The fetched `origin/main` is the authoritative base for all worktree creation.
71
+ Before creating or reusing a worktree for local implementation, use the canonical lifecycle entrypoint, which fetches the base remote, then creates-or-reuses the worktree at its namespaced path and provisions it in one step:
72
+
73
+ ```sh
74
+ node scripts/loop/ensure-worktree.mjs --repo-root <main> --issue <n>
75
+ ```
76
+
77
+ This creates (or reuses) the worktree at `tmp/worktrees/dev-loops/<kind>-<n>` from a freshly fetched `origin/main` (the authoritative base for all worktree creation) and provisions its gitignored runtime files. Raw `git worktree add -b <branch> tmp/worktrees/dev-loops/<kind>-<n> origin/main` is the underlying mechanism `ensure-worktree.mjs` runs for you. See [worktree guidance](../../docs/worktree-guidance.md).
72
78
 
73
79
  This validates:
74
80
  - Worktree isolation (current directory is under `tmp/worktrees/`)
@@ -81,7 +87,21 @@ Note: `--check-subagents` is advisory — it reports availability but does not b
81
87
 
82
88
  This gate does **not** apply to other routed strategies (`copilot_pr_followup`, `external_pr_followup`, `reviewer_fixer`, `wait_watch`, `final_approval`, `issue_intake`). Those strategies have their own execution rules and may edit code from any checkout as needed.
83
89
 
84
- Development-only bypass (`PI_PREFLIGHT_BYPASS=1`) exists for testing the gate itself but must not be used in production workflow runs. The bypass variable is a testing convenience, not an operational escape hatch.
90
+ Development-only bypass (`DEVLOOPS_PREFLIGHT_BYPASS=1`) exists for testing the gate itself but must not be used in production workflow runs. The bypass variable is a testing convenience, not an operational escape hatch.
91
+
92
+ ## Narrow failure-triage fast path
93
+
94
+ When resuming local implementation with dirty work or an observed failing command, use this path before broad discovery:
95
+
96
+ 1. run startup once from the relevant worktree/context
97
+ 2. inspect current state with `git status` and the changed files
98
+ 3. reproduce or identify the failing command
99
+ 4. run an exact-pattern search in changed files for the suspect call site
100
+ 5. patch the minimum call sites
101
+ 6. run focused smoke checks for that failure
102
+ 7. run default verification
103
+
104
+ Follow [Anti-patterns](../docs/anti-patterns.md) for the general tooling-internals and duplicate-broad-search prohibition; this local fast path owns only the ordered failure-triage procedure above.
85
105
 
86
106
  ### Step 1
87
107
 
@@ -509,7 +529,7 @@ After the phase plan passes review:
509
529
  10. Update `tmp/phases/phase-x/manifest.json` and `tmp/phases/index.json`.
510
530
  11. Update [Implementation State](../../docs/IMPLEMENTATION_STATE.md).
511
531
  12. **Exit validation gate — no uncommitted changes.** Before the subagent session terminates, run `git status --porcelain` and verify the output is empty. If uncommitted changes exist, first determine whether they are intended implementation changes or unintended/post-validation deltas. Revert any unintended or speculative changes. For intended changes, rerun the narrowest justified validation (`npm run verify` or equivalent) before staging and committing with an appropriate message; for tracker-backed sessions, also push the branch. A subagent session that exits with uncommitted changes in the worktree is a workflow defect and must not be treated as a clean completion. After committing, verify `git status --porcelain` is empty before declaring phase completion.
512
- 13. For tracker-backed sessions, create a draft PR from the working branch using `dev-loops pr create-draft --assignee @me --repo <owner/name> --base main --head <branch> --title "..." --body-file <body-file>`. When the `dev-loops` CLI helper is unavailable, use the equivalent `create-draft-pr.mjs` script directly: `node <resolved-skill-scripts>/github/create-draft-pr.mjs --repo <owner/name> --assignee @me --base main --head <branch> --title "..." --body-file <body-file>`. The PR body must contain `Closes #N` (or `Fixes #N`) for the linked issue. Do not create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy.
532
+ 13. For tracker-backed sessions, create the PR from the working branch using the canonical `dev-loops pr create --assignee @me --repo <owner/name> --base main --head <branch> --title "..." --body-file <body-file>` (always draft, self-assigned by default; `--assignee @me` is the default and is shown here for clarity). When the `dev-loops` CLI helper is unavailable, use the equivalent `create-pr.mjs` script directly: `node <resolved-skill-scripts>/github/create-pr.mjs --repo <owner/name> --assignee @me --base main --head <branch> --title "..." --body-file <body-file>`. Never open a PR with raw `gh pr create`. The PR body must contain `Closes #N` (or `Fixes #N`) for the linked issue. Do not create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy.
513
533
  14. If authorized, merge the fully reviewed, locally validated phase branch back into local `main` (phase-doc-backed sessions) or proceed through the PR gate pipeline (tracker-backed sessions).
514
534
  15. If authorization for PR creation or merge is still pending (commit authorization is already enforced by the exit validation gate in step 12), mark the phase as `awaiting-finalization` rather than `completed`, and record exactly which finalization step is pending.
515
535
 
@@ -616,7 +636,7 @@ See [Stop Conditions](../docs/stop-conditions.md). Local-specific stops: phase c
616
636
  - Rerun validation after review-driven fixes.
617
637
  - A phase is not operationally closed until its branch state is captured in commit history and the reviewed branch has been finalized according to session type (merged into local `main` for phase-doc-backed sessions; merged via GitHub PR for tracker-backed sessions), unless authorization for that finalization is still pending.
618
638
  - For tracker-backed sessions, the handoff path is always: push the working branch → open a PR → merge via GitHub; never merge the working branch into local `main`.
619
- - PR creation must always include `--assignee @me` so the new PR is self-assigned, 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-draft --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.
639
+ - 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.
620
640
  - When authorization is pending, record the phase as `awaiting-finalization` and describe the exact missing step.
621
641
  - For phase-doc-backed sessions, merge the fully reviewed, locally validated branch back into local `main` when authorized.
622
642
 
package/AGENTS.md CHANGED
@@ -14,7 +14,7 @@
14
14
  - Prefer the GitHub-first routed path for branch/PR/CI/review work; use local implementation only when explicitly requested.
15
15
  - Use `npm run verify` as the default local validation path.
16
16
  - When creating GitHub issues via `gh issue create`, always include `--assignee @me` so the new artifact is self-assigned.
17
- - When creating PRs in this repo, use `dev-loops pr create-draft --assignee @me ...` so draft-first is enforced mechanically while preserving self-assignment.
17
+ - When creating PRs in this repo, use the canonical `dev-loops pr create ...` path (always draft, self-assigned by default; `--assignee @me` is the default) never raw `gh pr create`.
18
18
  - Never start implementation (file mutation, branch creation, PR creation) without explicit instruction. "Queue," "add to list," "track," "note" are NOT implementation triggers. Only proceed when the user says "start," "go," "implement," "do it," "work on," or equivalent imperative. Confirm if unsure.
19
19
  - All work must originate from a tracked artifact: a GitHub issue (tracker-first) or a persisted markdown plan file (local-planning). See [Artifact Authority Contract](skills/docs/artifact-authority-contract.md) for canonical mode definitions and settings. No work may originate from a PR or direct local change unless explicitly requested.
20
20
  - Implement one phase at a time; if durable repo docs explicitly record a reprioritization exception, follow [Implementation State](docs/IMPLEMENTATION_STATE.md) and the active phase doc.