dev-loops 0.3.0 → 0.5.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 (85) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +1 -1
  3. package/.claude/agents/refiner.md +1 -0
  4. package/.claude/hooks/_run-context.mjs +9 -16
  5. package/.claude/skills/copilot-pr-followup/SKILL.md +9 -7
  6. package/.claude/skills/dev-loop/SKILL.md +17 -7
  7. package/.claude/skills/dev-loop/templates/slides-story-review.md +54 -0
  8. package/.claude/skills/docs/anti-patterns.md +1 -1
  9. package/.claude/skills/docs/artifact-authority-contract.md +86 -31
  10. package/.claude/skills/docs/copilot-loop-operations.md +1 -1
  11. package/.claude/skills/docs/issue-intake-procedure.md +4 -0
  12. package/.claude/skills/docs/local-planning-flow.md +63 -0
  13. package/.claude/skills/docs/local-planning-worked-example.md +139 -0
  14. package/.claude/skills/docs/merge-preconditions.md +100 -1
  15. package/.claude/skills/docs/plan-file-contract.md +37 -0
  16. package/.claude/skills/docs/retrospective-checkpoint-contract.md +55 -7
  17. package/.claude/skills/docs/spike-mode-contract.md +237 -0
  18. package/.claude/skills/docs/stop-conditions.md +1 -0
  19. package/.claude/skills/docs/tracker-first-loop-state.md +6 -6
  20. package/.claude/skills/local-implementation/SKILL.md +8 -2
  21. package/CHANGELOG.md +69 -0
  22. package/README.md +9 -2
  23. package/agents/refiner.agent.md +1 -0
  24. package/cli/index.mjs +21 -2
  25. package/extension/README.md +1 -1
  26. package/package.json +6 -4
  27. package/scripts/README.md +2 -2
  28. package/scripts/github/capture-review-threads.mjs +20 -2
  29. package/scripts/github/offer-human-handoff.mjs +147 -0
  30. package/scripts/github/probe-ci-status.mjs +468 -0
  31. package/scripts/github/probe-copilot-review.mjs +69 -3
  32. package/scripts/github/request-copilot-review.mjs +3 -3
  33. package/scripts/github/resolve-handoff-candidates.mjs +412 -0
  34. package/scripts/github/upsert-checkpoint-verdict.mjs +20 -5
  35. package/scripts/lib/jq-output.mjs +297 -0
  36. package/scripts/loop/_stale-runner-detection.mjs +1 -1
  37. package/scripts/loop/_worktree-path.mjs +27 -0
  38. package/scripts/loop/check-retro-tooling.mjs +246 -0
  39. package/scripts/loop/cleanup-worktree.mjs +175 -0
  40. package/scripts/loop/copilot-pr-handoff.mjs +22 -4
  41. package/scripts/loop/detect-pr-gate-coordination-state.mjs +35 -2
  42. package/scripts/loop/detect-stale-runner.mjs +3 -4
  43. package/scripts/loop/docs-grill-contract.mjs +70 -0
  44. package/scripts/loop/ensure-worktree.mjs +219 -0
  45. package/scripts/loop/info.mjs +21 -2
  46. package/scripts/loop/outer-loop.mjs +1 -1
  47. package/scripts/loop/pr-runner-coordination.mjs +21 -5
  48. package/scripts/loop/pre-flight-gate.mjs +10 -7
  49. package/scripts/loop/pre-push-main-guard.mjs +4 -4
  50. package/scripts/loop/provision-worktree.mjs +243 -0
  51. package/scripts/loop/resolve-dev-loop-startup.mjs +181 -10
  52. package/scripts/loop/resolve-pr-conflicts.mjs +357 -0
  53. package/scripts/loop/run-queue.mjs +25 -1
  54. package/scripts/loop/run-watch-cycle.mjs +152 -25
  55. package/scripts/loop/slides-story-review-contract.mjs +123 -0
  56. package/scripts/pages/build-site.mjs +136 -0
  57. package/scripts/projects/add-queue-item.mjs +32 -7
  58. package/scripts/projects/archive-done-items.mjs +2 -1
  59. package/scripts/projects/ensure-queue-board.mjs +2 -1
  60. package/scripts/projects/list-queue-items.mjs +14 -3
  61. package/scripts/projects/move-queue-item.mjs +14 -3
  62. package/scripts/projects/reorder-queue-item.mjs +5 -4
  63. package/scripts/projects/sync-item-status.mjs +2 -1
  64. package/scripts/refine/_refine-helpers.mjs +20 -0
  65. package/scripts/refine/exit-spike.mjs +186 -0
  66. package/scripts/refine/promote-plan.mjs +387 -0
  67. package/scripts/refine/refine-plan-file.mjs +165 -0
  68. package/scripts/refine/validate-plan-file.mjs +64 -0
  69. package/scripts/refine/validate-spike-file.mjs +87 -0
  70. package/skills/copilot-pr-followup/SKILL.md +9 -7
  71. package/skills/dev-loop/SKILL.md +13 -3
  72. package/skills/dev-loop/templates/slides-story-review.md +54 -0
  73. package/skills/docs/anti-patterns.md +1 -1
  74. package/skills/docs/artifact-authority-contract.md +86 -31
  75. package/skills/docs/copilot-loop-operations.md +1 -1
  76. package/skills/docs/issue-intake-procedure.md +4 -0
  77. package/skills/docs/local-planning-flow.md +63 -0
  78. package/skills/docs/local-planning-worked-example.md +139 -0
  79. package/skills/docs/merge-preconditions.md +100 -1
  80. package/skills/docs/plan-file-contract.md +37 -0
  81. package/skills/docs/retrospective-checkpoint-contract.md +55 -7
  82. package/skills/docs/spike-mode-contract.md +237 -0
  83. package/skills/docs/stop-conditions.md +1 -0
  84. package/skills/docs/tracker-first-loop-state.md +6 -6
  85. package/skills/local-implementation/SKILL.md +8 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-loops",
3
- "version": "0.3.0",
3
+ "version": "0.5.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.3.0 loop startup --issue <n>` for issues, or `npx dev-loops@0.3.0 loop startup --pr <n>` for PRs.
25
+ 1. Run the deterministic startup resolver to produce the authoritative state bundle: `npx dev-loops@0.5.0 loop startup --issue <n>` for issues, or `npx dev-loops@0.5.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.
@@ -44,6 +44,7 @@ For the active phase, require and produce:
44
44
  - Do not invent audit findings when no audit artifact was provided
45
45
  - when the phase includes watcher or predicate-driven behavior: explicit timeout semantics and negative-case expectations for non-target identities/events
46
46
  - when the phase relies on package-first shared helpers inside a source-loaded workspace: explicit integration expectations about whether local callers use published package imports or a thin source/workspace adapter during development
47
+ - cross-check the phase's claims against the contracts and docs they reference (the autonomous docs-grill step, see ../docs/docs-grill-step.md): surface code-vs-doc drift, stale references, and contract-surface inaccuracies as refinement findings while the claims are still being verified
47
48
 
48
49
  ## Working style
49
50
  - Prefer parallel fresh-context fan-out/fan-in when it improves refinement quality or surfaces materially different variants.
@@ -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`
@@ -309,7 +309,7 @@ The canonical checkpoint verdict comment contract is [Gate Review Comment Contra
309
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.
310
310
  - **Pass criteria:** all configured draft gate angles pass; all findings at severities in `blockCleanOnFindingSeverities` are addressed; validation passes; no unrelated files are included.
311
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 `add-queue-item --status`:
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
313
  ```sh
314
314
  node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "In Progress"
315
315
  ```
@@ -357,6 +357,8 @@ See [Merge Preconditions](../docs/merge-preconditions.md). Verify: zero unresolv
357
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).
358
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`.
359
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
+
360
362
  ### Mechanical pre-merge gate evidence check
361
363
 
362
364
  Immediately before any `gh pr merge`, run:
@@ -387,15 +389,15 @@ Do not report completion or advance to the next PR queue item until `.pi/dev-loo
387
389
 
388
390
  ### Post-merge board archive (best-effort)
389
391
 
390
- After the retrospective checkpoint write, optionally tidy the queue board locally:
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"):
391
393
 
392
394
  ```sh
393
- node <resolved-skill-scripts>/projects/archive-done-items.mjs --repo <owner/name>
395
+ node <resolved-skill-scripts>/projects/archive-done-items.mjs --repo <owner/name> || true
394
396
  ```
395
397
 
396
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.
397
399
 
398
- Alongside the archive step, sync the linked issue's board item to "Done" so the queue board reflects the merge without a manual `add-queue-item --status`:
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>`:
399
401
 
400
402
  ```sh
401
403
  node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "Done"
@@ -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.3.0 loop startup --issue <n>` for issues, `npx dev-loops@0.3.0 loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops@0.3.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.
28
+ Resolve authoritative state via the startup resolver (`npx dev-loops@0.5.0 loop startup --issue <n>` for issues, `npx dev-loops@0.5.0 loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `npx dev-loops@0.5.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,20 @@ 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.3.0 loop info` (read-only; no full dev-loop run required):
103
- - `npx dev-loops@0.3.0 loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
104
- - `npx dev-loops@0.3.0 loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
105
- - `npx dev-loops@0.3.0 loop info --issue <n> --json` — machine-readable JSON output
102
+ Info/handoff requests can be served directly via `npx dev-loops@0.5.0 loop info` (read-only; no full dev-loop run required):
103
+ - `npx dev-loops@0.5.0 loop info --issue <n>` — human-readable issue state summary (strategy, route, linked PR, next action)
104
+ - `npx dev-loops@0.5.0 loop info --pr <n>` — human-readable PR state summary (branch, CI, threads, rounds, action)
105
+ - `npx dev-loops@0.5.0 loop info --issue <n> --json` — machine-readable JSON output
106
+
107
+ ## Reading tool output (token-economical convention)
108
+
109
+ When you need a fact from a dev-loops JSON-emitting script, climb this ladder and stop at the first rung that answers the question — never read more output than you need:
110
+
111
+ 1. **Prefer the dev-loops subcommand / concise mode.** Use `loop info` or a script's `--concise`/`--summary` mode (e.g. `run-watch-cycle.mjs --concise`, `probe-copilot-review.mjs --concise`) for a human-readable digest. The concise modes surface loop state, Copilot round count, unresolved/actionable thread counts, round-cap-clean eligibility, CI status, next action, and the current round's new Copilot comment bodies.
112
+ 2. **`--silent` / `-s` for a yes/no check.** Reads ZERO output: `… --jq '<predicate>' --silent; echo $?` exits `0` for true / `1` for false. Without `--jq`, `--silent` maps the script's success (`ok:true`) to exit `0`, failure to `1`. Example: `probe-copilot-review.mjs --repo o/r --pr N --jq '.status=="idle"' -s`.
113
+ 3. **`--jq <filter>` to extract a single field.** The `--jq`-wired scripts (`probe-copilot-review.mjs`, `capture-review-threads.mjs`, `upsert-checkpoint-verdict.mjs`, the `scripts/projects/*` queue scripts) accept a gh-style `--jq` filter (jq subset: field access, `.[]`/`.[N]`, `|`, `select(...)`, `==`/`!=`/`<`/`<=`/`>`/`>=`, `length`, `keys`). It prints only the filtered value. An invalid filter fails closed (stderr + exit `2`), distinct from a clean predicate-false (silent exit `1`).
114
+ 4. **`gh … --jq` on a raw `gh` call** when no dev-loops script covers it.
115
+ 5. **NEVER `| python3` or `node -e`** to parse tool JSON. If a field you need is missing from a script's output, add it to the script (or its concise mode), not an inline parser.
106
116
 
107
117
  ## Guard rules
108
118
 
@@ -114,7 +124,7 @@ Info/handoff requests can be served directly via `npx dev-loops@0.3.0 loop info`
114
124
 
115
125
  **Bounded async task contract:** Break work into discrete tasks with clear inputs, explicit outputs, bounded scope. No shell polling — use `run-watch-cycle.mjs` or `gh run watch`.
116
126
 
117
- **Round-cap budget check (enforced):** After every watch cycle, fix pass, or reply-resolve, check whether completed Copilot review rounds have reached the maximum (default: 5). Stop re-requesting Copilot review when the limit is reached — never re-request after the cap.
127
+ **Round-cap budget check (enforced):** After every watch cycle, fix pass, or reply-resolve, check whether completed Copilot review rounds have reached the maximum (default: 5). Stop re-requesting Copilot review when the limit is reached — never re-request after the cap. Read these gate-cadence facts via the token-economical convention above (`run-watch-cycle.mjs --concise`, or `--jq`/`--silent` for a single field/predicate) — never `| python3` or `node -e`.
118
128
 
119
129
  ## Shorthand issue-based auto trigger contract
120
130
 
@@ -0,0 +1,54 @@
1
+ # Slides content & storytelling review prompt template
2
+
3
+ Use this template when running the bounded slides-story reviewer mode behind `dev-loop`.
4
+
5
+ You are a public-audience storytelling reviewer judging a deck's **narrative**, not its pixels. The visual designer/vision loop owns layout, spacing, and contrast; you own arc, message, sequencing, and audience fit.
6
+
7
+ ## Inputs
8
+
9
+ - `acceptanceCriteria`: required list of slice-level acceptance criteria the review is judging
10
+ - `storytellingBrief`: required short focus brief (audience, intended takeaway, what to watch for)
11
+ - `deckBundle.deckSourcePath`: required path to the deck source (e.g. the Slidev `.md`)
12
+ - `deckBundle.slideScreenshots[]`: optional captured slides from the UI smoke harness
13
+ - `slideId`
14
+ - `screenshotPath`
15
+
16
+ ## Review lens (public audience)
17
+
18
+ 1. **Arc**: hook → tension → resolution. Does slide 1 make a stranger care?
19
+ 2. **One message per slide**: each slide has a single takeaway; titles state the claim, not the topic.
20
+ 3. **Sequencing**: order builds understanding; no forward references; jargon introduced before it is used.
21
+ 4. **Audience calibration**: a public/non-insider reader can follow it — internal enum names, state-machine identifiers, and pills are translated or earn their keep, not dumped raw.
22
+ 5. **Cut / merge / reorder**: explicit recommendations — which slides to drop, combine, or move.
23
+ 6. **Close**: a memorable takeaway, not a feature list.
24
+
25
+ ## Review policy
26
+
27
+ 1. Fail closed on any missing, ambiguous, or unreadable required input — do not guess the narrative.
28
+ 2. Ground every finding in a specific `slideId` (and a `screenshotPath` when available).
29
+ 3. Return only deterministic findings tied to the brief and acceptance criteria; do not invent content.
30
+
31
+ ## Required output format
32
+
33
+ Allowed enum values:
34
+ - `outcome`: `"story_review_satisfied"` | `"needs_iteration"`
35
+ - `severity`: `"high"` | `"medium"` | `"low"`
36
+
37
+ Return strict JSON with this shape (example uses concrete values):
38
+
39
+ ```json
40
+ {
41
+ "outcome": "needs_iteration",
42
+ "summary": "short overall verdict on whether the deck lands",
43
+ "findings": [
44
+ {
45
+ "severity": "high",
46
+ "slideId": "hero",
47
+ "problem": "what is wrong with the narrative on this slide and why it fails the public audience",
48
+ "correctiveAction": "what to change next (cut/merge/reorder/reword)"
49
+ }
50
+ ]
51
+ }
52
+ ```
53
+
54
+ When `outcome` is `"needs_iteration"`, `findings` must be non-empty.
@@ -11,7 +11,7 @@ Canonical owner for anti-pattern guidance across all workflow families.
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
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 project add ... --status "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.
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.
15
15
 
16
16
  ## Light mode exception
17
17
 
@@ -1,6 +1,6 @@
1
1
  # Artifact authority contract
2
2
 
3
- This document is the canonical authority for the artifact-selection model: when work originates from a GitHub issue (tracker-first) vs a persisted markdown plan file (local-planning).
3
+ This document is the canonical authority for the artifact-selection model: whether a work item originates from a GitHub issue (tracker-first) or from a persisted markdown plan file (local-planning).
4
4
 
5
5
  This canonical owner lives in the shipped `skills/docs/` surface because installed skill/runtime consumers reliably own the skills subtree. In installed layouts, read the same contract via [Artifact Authority Contract](../docs/artifact-authority-contract.md) from the installed skill directory.
6
6
 
@@ -8,9 +8,11 @@ Other repo docs may summarize or link this contract, but they should not redefin
8
8
 
9
9
  ## Two-tier model
10
10
 
11
- dev-loops supports two mutually exclusive artifact authority modes. Every work item must originate from exactly one authoritative artifact a GitHub issue or a persisted markdown plan file. No work may originate from a PR or direct local change unless explicitly requested.
11
+ dev-loops supports two mutually exclusive artifact authority modes. Every work item originates from exactly one authoritative artifact: a GitHub issue or a persisted markdown plan file. Work originates from a PR or a direct local change only when explicitly requested.
12
12
 
13
- ### Tracker-first (default)
13
+ The shipped extension default selects local-planning; see [Shipped default posture](#shipped-default-posture) below. The mode names that follow describe the two tiers; "default" in their headings refers to the github-first code-level fallback in `BUILT_IN_DEFAULTS`, which the shipped extension layer overrides to local-first.
14
+
15
+ ### Tracker-first
14
16
 
15
17
  **GitHub issues are the authoritative artifact store.** Work originates from a GitHub issue. A linked PR is the execution artifact. GitHub is the canonical source of truth for issue identity, acceptance criteria, scope, and lifecycle state.
16
18
 
@@ -25,12 +27,12 @@ Key contract:
25
27
  - When an open linked PR exists, reuse it rather than opening another
26
28
  - Implementation may proceed through either the GitHub-first routed path or the local implementation strategy (see [Public Dev Loop Contract](public-dev-loop-contract.md) `targetPreference`)
27
29
 
28
- ### Local-planning (opt-out)
30
+ ### Local-planning
29
31
 
30
- **Persisted markdown plan files are the authoritative artifact store.** Work originates from a markdown plan file committed to the repository. No GitHub issue is required. GitHub PRs are still used for review and merge, but the plan file is the canonical spec.
32
+ **Persisted markdown plan files are the authoritative artifact store.** Work originates from a local markdown plan file in the repo working tree, and no GitHub issue is required. The plan file stays uncommitted through authoring, refinement, and local review; promotion is the step that commits it (the helper sequence commits it as part of opening the PR). GitHub PRs carry review and merge while the plan file stays the canonical spec.
31
33
 
32
34
  Artifacts:
33
- - **Planning artifact:** Persisted markdown plan file (e.g., `docs/phases/phase-<n>.md`)
35
+ - **Planning artifact:** Persisted markdown plan file (e.g., `docs/phases/phase-<n>.md`); its format and required base sections are defined in the [Plan-file Contract](plan-file-contract.md)
34
36
  - **Execution artifact:** Local branch and associated GitHub PR (created during implementation)
35
37
  - **No GitHub issue:** The plan file replaces the issue as the canonical spec
36
38
 
@@ -41,10 +43,10 @@ Key contract:
41
43
 
42
44
  ### Mode selection table
43
45
 
44
- | Mode | Canonical artifact | GitHub issue required | Settings values |
46
+ | Mode | Canonical artifact | GitHub issue required | Settings value |
45
47
  |---|---|---|---|
46
- | Tracker-first (default) | GitHub issue | Yes | `strategy.default: github-first` |
47
- | Local-planning (opt-out) | Markdown plan file | No | `strategy.default: local-first` |
48
+ | Tracker-first | GitHub issue | Yes | `strategy.default: github-first` |
49
+ | Local-planning (shipped default) | Markdown plan file | No | `strategy.default: local-first` |
48
50
 
49
51
  `inputSource.default` further disambiguates local-first startup:
50
52
  | inputSource | Meaning |
@@ -54,62 +56,115 @@ Key contract:
54
56
 
55
57
  ## Settings mechanism
56
58
 
57
- Artifact authority mode is controlled by `.devloops` at repo root:
59
+ The repo's default artifact-authority posture is declared by `strategy.default`, set in `.devloops` at repo root and resolved against the layered config defaults:
58
60
 
59
61
  ```yaml
60
62
  # .devloops
61
63
  strategy:
62
- default: github-first # tracker-first (GitHub issue required)
63
- # default: local-first # local-planning (markdown plan file)
64
+ default: local-first # local-planning (markdown plan file)
65
+ # default: github-first # tracker-first (GitHub issue required)
64
66
  inputSource:
65
67
  default: tracker # spec source for local-first: tracker (issue body) or phase-docs
66
68
  ```
67
69
 
68
- The `strategy.default` key serves dual purpose:
69
- 1. It selects the artifact authority mode (tracker-first vs local-planning)
70
- 2. It sets the default routing preference for `targetPreference` in dev-loop startup
70
+ The `strategy.default` key carries two jobs:
71
+ 1. It declares the repo's default artifact-authority posture (local-planning under `local-first`, tracker-first under `github-first`).
72
+ 2. It sets the routing preference (`targetPreference`) in dev-loop startup — `prefer_local` under `local-first`, `prefer_github_first` under `github-first`.
73
+
74
+ The authoritative artifact for a given run is selected by the explicit startup input. `scripts/loop/resolve-dev-loop-startup.mjs` takes `--issue` / `--pr` / `--input` / `--plan-file` (mutually exclusive), and `strategy.default` supplies the default routing preference; it does not force the artifact per invocation.
71
75
 
72
76
  The `inputSource.default` key disambiguates local-first startup:
73
- - `tracker` (default): local agent implements from the GitHub issue body; the issue is canonical spec
74
- - `phase-docs`: local agent implements from persisted phase docs; no tracker issue required
77
+ - `tracker` (default): the local agent implements from the GitHub issue body; the issue is the canonical spec
78
+ - `phase-docs`: the local agent implements from persisted phase docs; no tracker issue required
79
+
80
+ ### Shipped default posture
75
81
 
76
- These keys are already defined in `.pi/dev-loop/defaults.yaml` (shipped with dev-loops) and may be overridden in `.devloops` (per-repo).
82
+ The effective default for a consumer comes from the config-merge layering in `packages/core/src/config/config.mjs`. Precedence, low to high:
77
83
 
78
- ### Defaults resolution
84
+ 1. `BUILT_IN_DEFAULTS` (frozen in `config.mjs`) — `strategy.default: github-first`. This is the code-level fallback when no other layer sets the key.
85
+ 2. Extension-packaged defaults (`packages/core/src/config/extension-defaults.yaml`, loaded as the `extensionDefaults` layer) — `strategy.default: local-first`. This is the opinion the package ships and the layer that wins over the built-in fallback.
86
+ 3. Repo-local `.pi/dev-loop/defaults.*` (legacy) — applied when present.
87
+ 4. Repo `.devloops` at repo root — the per-repo override, highest precedence. When `.devloops` is absent, the legacy `.pi/dev-loop/settings.*` / `overrides.*` apply at this position instead.
79
88
 
80
- 1. `.pi/dev-loop/defaults.yaml` shipped default (`github-first`)
81
- 2. `.devloops` — per-repo override (takes precedence)
89
+ With nothing but the shipped package in place, the extension layer resolves `strategy.default` to `local-first`, so the shipped default posture is local-planning (epic #947, decision #7). A repo opts back into tracker-first by setting `strategy.default: github-first` in its own `.devloops`.
90
+
91
+ Two legacy repo-local layers also exist under `.pi/dev-loop/` (the package no longer ships a `.pi/dev-loop/defaults.yaml`). They differ in how they load: `.pi/dev-loop/defaults.*` is always applied when present, between the extension defaults and `.devloops`; `.pi/dev-loop/settings.*` (and the older `overrides.*`) load only when no `.devloops` is present — when `.devloops` exists it is authoritative and those files are ignored (with a deprecation warning). The full precedence, low to high, is: `BUILT_IN_DEFAULTS` < extension defaults < `.pi/dev-loop/defaults.*` < repo `.devloops` (or, when `.devloops` is absent, `.pi/dev-loop/settings.*` / `overrides.*`).
82
92
 
83
93
  ### Explicit non-knobs
84
94
 
85
95
  These are not valid artifact authority mode selectors:
86
- - `strategy.default: copilot` — not a valid mode; must be `github-first` or `local-first`
96
+ - `strategy.default: copilot` — not a valid mode; the enum accepts only `github-first` or `local-first` (`packages/core/src/config/config.mjs`)
87
97
  - Free-form string values — fail closed
88
- - Omitting `strategy.default` entirelydefaults to `github-first` from the shipped defaults
98
+ - Omitting `strategy.default` from every layer resolves to `github-first` from `BUILT_IN_DEFAULTS`; with the shipped extension layer present it resolves to `local-first`
99
+
100
+ ## Local-first plan-file flow end to end
101
+
102
+ Under local-planning, one plan file moves through four stages. Each stage has a shipped helper script; the start, refine, and promote stages also expose their pure logic as an `@dev-loops/core` contract, while the validate stage's `validatePlanFile` lives in its helper script (`scripts/refine/validate-plan-file.mjs`). The [Local-Planning Flow](local-planning-flow.md) skill doc walks the same sequence as operator steps, and the [Local-Planning Worked Example](local-planning-worked-example.md) shows one plan file evolving through every stage.
103
+
104
+ ### P1 — Plan-file artifact + config (#949)
105
+
106
+ The plan file is a phase-doc-format markdown document. Its directory is `localPlanning.plansDir`, which defaults to `docs/phases/` (built-in default in `config.mjs`, mirrored in `extension-defaults.yaml`; `resolvePlansDir(config)` resolves it). Its required base authoring sections — `## Status`, `## Objective`, `## In scope`, `## Explicit non-goals` — and the validator `scripts/refine/validate-plan-file.mjs` (`validatePlanFile`, distinct `missing_*` codes per absent or empty section) are defined in the [Plan-file Contract](plan-file-contract.md).
107
+
108
+ ### P2 — Intake (#950)
109
+
110
+ `scripts/loop/resolve-dev-loop-startup.mjs` accepts `--plan-file <path>` (mutually exclusive with `--issue`, `--pr`, `--input`). It validates the plan and threads an intake state onto its output. The pure contract `@dev-loops/core/loop/plan-file-intake-contract` (`packages/core/src/loop/plan-file-intake-contract.mjs`) defines `evaluatePlanFileIntakeState` and the three `PLAN_FILE_INTAKE_STATE` values:
111
+
112
+ | State | Meaning |
113
+ |---|---|
114
+ | `new_plan_needs_refinement` | Base sections valid; the refinement sections are not yet present |
115
+ | `plan_refined_ready_for_promotion` | Base sections valid and both `PLAN_FILE_REFINEMENT_SECTIONS` (`Acceptance criteria`, `Definition of done`) present |
116
+ | `ambiguous_fail_closed` | Base sections invalid, or only one refinement section present — the resolver does not route the plan forward |
117
+
118
+ In the CLI, a base-valid plan carrying only one refinement section is reported as `ambiguous_fail_closed` with exit 0 (the operator completes the missing section before refine/promote); a missing/unreadable plan, or one that fails the base-section validator, makes startup exit 1 with no readiness bundle.
119
+
120
+ ### P3 — Local refine + review checkpoint (#951)
121
+
122
+ `scripts/refine/refine-plan-file.mjs` drives the refine step; the pure contract `@dev-loops/core/loop/plan-file-refine-contract` (`packages/core/src/loop/plan-file-refine-contract.mjs`) exports `refinePlanFileInPlace`, which writes the refiner payload back into the single canonical plan file in place — the `Acceptance criteria` and `Definition of done` sections, a `Coverage matrix` section (`COVERAGE_MATRIX_HEADING`), and a `Docs-grill findings` section (`DOCS_GRILL_FINDINGS_HEADING`) — then stops at the `local_human_review` checkpoint (`PLAN_FILE_REFINE_STOP.LOCAL_HUMAN_REVIEW`) with the intake state advanced to `plan_refined_ready_for_promotion`. The module performs no GitHub mutation, no network calls, and no filesystem I/O; the caller reads and writes the plan file. The docs-grill runs as a step within refinement: the CLI classifies each finding with `classifyDocsGrillFinding` (`scripts/loop/docs-grill-contract.mjs`) and the contract records the dispositions. See the [Docs-Grill Step](../../docs/docs-grill-step.md).
123
+
124
+ ### P4 — Promotion + authority transfer (#952)
125
+
126
+ `scripts/refine/promote-plan.mjs` promotes a refined plan; the pure contract `@dev-loops/core/loop/plan-file-promote-contract` (`packages/core/src/loop/plan-file-promote-contract.mjs`) exports `evaluatePromoteEligibility` and `buildPromotionPrBody`. Promotion is PR-first: it commits the plan doc and opens exactly one draft PR via the canonical PR wrapper, and mints no GitHub issue. The plan↔PR link is bidirectional — the PR body carries the committed plan-doc path (the spec-of-record) and the plan front-matter carries `prNumber:` (`PLAN_FILE_PR_FRONT_MATTER_KEY`). Promotion is idempotent: a plan that already carries `prNumber` resolves to `already_promoted` (`PLAN_FILE_PROMOTE_ACTION.ALREADY_PROMOTED`) and opens nothing. The optional `prNumber` front-matter and its parser/serializer are described in the [Plan-file Contract](plan-file-contract.md).
127
+
128
+ ### P5 — Local-first noise profile (#953)
129
+
130
+ The shipped extension layer pairs local-first with a low-noise posture, in `packages/core/src/config/extension-defaults.yaml`:
131
+
132
+ | Key | Shipped value | Why |
133
+ |---|---|---|
134
+ | `strategy.default` | `local-first` | The shipped default posture (decision #7) |
135
+ | `autonomy.humanMergeOnly` | `true` | Local-first never auto-merges; a human always merges |
136
+ | `queue.maxAutoFiledIssues` | `1` | Local-first is PR-first, so auto-filing issues is near-zero; a low cap keeps tracker noise minimal |
137
+ | `gates.postFindingsComments` | `true` | Gate findings live on the PR (the spec-of-record and human-review surface) as evidence |
138
+
139
+ These values come from the existing config-merge layering, so no new resolver is involved: `BUILT_IN_DEFAULTS` keeps the github-first posture (`humanMergeOnly: false`, `maxAutoFiledIssues: 10`), and the extension layer sets the local-first values above. A repo `.devloops` can override any of them.
89
140
 
90
141
  ## dev-loops own mode
91
142
 
92
- dev-loops is **tracker-first (opted in, GitHub backend).**
143
+ dev-loops runs **local-planning**, set in its repo-root `.devloops`.
93
144
 
94
- - **Mode:** Tracker-first
95
- - **Settings:** `.pi/dev-loop/defaults.yaml` sets `strategy.default: github-first`
96
- - **Artifact authority:** GitHub issues are the canonical spec for all work in this repository
97
- - **No local-planning override:** This repo does not opt out to local-planning mode
98
- - **Why tracker-first:** All work in this repo originates from GitHub issues. The public dev-loop contract, Copilot follow-up state machines, and gate pipeline all assume issues are the primary artifact. Self-improvement work on dev-loops itself follows the same tracker-first contract.
145
+ - **Mode:** Local-planning
146
+ - **Settings:** repo-root `.devloops` sets `strategy.default: local-first` and `inputSource.default: tracker`
147
+ - **Artifact authority:** the canonical spec for a work item is its plan artifact; the repo dogfoods the same local-first posture the package ships as default
148
+ - **Per-run input:** with `inputSource.default: tracker`, a local-first session can still implement from a GitHub issue body when one is supplied (the issue is the spec source for that run); `phase-docs` switches the source to a committed plan file
149
+ - **Why local-planning:** the repo runs the local-first plan-file flow (plan-file refine review promote) on its own work so the shipped default posture is exercised end to end.
99
150
 
100
151
  ## Relationship to other docs
101
152
 
102
153
  | Doc | Relationship |
103
154
  |---|---|
104
155
  | [Public Dev Loop Contract](public-dev-loop-contract.md) | This contract is the canonical entrypoint; artifact authority contract defines the artifact model it assumes |
105
- | [Tracker-First Loop State](tracker-first-loop-state.md) | That doc defines the PR-level state machine for tracker-first PR workflows it is about execution state, not artifact authority |
156
+ | [Plan-file Contract](plan-file-contract.md) | Defines the plan-file format (phase-doc format) and its required base sections for local-planning mode |
157
+ | [Local-Planning Flow](local-planning-flow.md) | Operator sequence for the local-first flow: validate → start → refine → promote, naming the shipped helper scripts |
158
+ | [Local-Planning Worked Example](local-planning-worked-example.md) | One plan file shown through every stage, with the file content evolving |
159
+ | [Spike-mode Contract](spike-mode-contract.md) | Time-boxed exploratory runs; a graduated spike emits a plan file that enters this local-planning tier |
160
+ | [Tracker-First Loop State](tracker-first-loop-state.md) | Defines the PR-level state machine for tracker-first PR workflows; that is execution state, separate from artifact authority |
106
161
  | [Main Agent Contract](main-agent-contract.md) | Defines the delegation boundary; artifact authority defines which artifacts govern work |
107
162
  | AGENTS.md | Repo constitution; cites the work-origin rule and points to this contract |
108
163
  | [Dev Loop Skill](../dev-loop/SKILL.md) | Public entrypoint skill; cites the work-origin rule and points to this contract |
109
164
 
110
165
  ### Distinction: artifact authority vs tracker-first PR workflow
111
166
 
112
- `tracker-first-loop-state.md` defines a state machine for PR lifecycle management when a tracker item (e.g., Shortcut story) drives a GitHub PR. That is a **PR-level workflow contract**, not the artifact authority model. The term "tracker-first" in that doc refers to tracker-driven PR state transitions it does not redefine the artifact authority contract defined here.
167
+ `tracker-first-loop-state.md` defines a state machine for PR lifecycle management when a tracker item (e.g., Shortcut story) drives a GitHub PR. It is a **PR-level workflow contract**. The term "tracker-first" there refers to tracker-driven PR state transitions, a separate concern from the artifact authority model this doc defines.
113
168
 
114
169
  ## Non-goals
115
170
 
@@ -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>]`.
@@ -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
 
@@ -0,0 +1,63 @@
1
+ # Local-planning flow
2
+
3
+ The operator sequence for the local-first plan-file flow. The [Artifact Authority Contract](artifact-authority-contract.md) owns the model and the per-phase contract details; this doc names the shipped helper scripts in the order they run for one plan file. The [Local-Planning Worked Example](local-planning-worked-example.md) shows a single plan file evolving through these steps.
4
+
5
+ The flow applies under local-planning mode (`strategy.default: local-first`, the shipped default — see the contract's [Shipped default posture](artifact-authority-contract.md#shipped-default-posture)). The plan file lives under `localPlanning.plansDir`, which defaults to `docs/phases/`.
6
+
7
+ ## Stages
8
+
9
+ | Stage | Helper script | Pure logic |
10
+ |---|---|---|
11
+ | Validate | `scripts/refine/validate-plan-file.mjs` | `validatePlanFile` (in the helper script; not a `@dev-loops/core` export) |
12
+ | Start | `scripts/loop/resolve-dev-loop-startup.mjs --plan-file <path>` | `evaluatePlanFileIntakeState` (`@dev-loops/core/loop/plan-file-intake-contract`) |
13
+ | Refine | `scripts/refine/refine-plan-file.mjs` | `refinePlanFileInPlace` (`@dev-loops/core/loop/plan-file-refine-contract`) |
14
+ | Promote | `scripts/refine/promote-plan.mjs` | `evaluatePromoteEligibility` / `buildPromotionPrBody` (`@dev-loops/core/loop/plan-file-promote-contract`) |
15
+
16
+ ## 1. Author and validate the plan
17
+
18
+ Author a phase-doc-format plan under `docs/phases/` with the four base sections `## Status`, `## Objective`, `## In scope`, `## Explicit non-goals`. Check it against the base-section contract:
19
+
20
+ ```
21
+ node scripts/refine/validate-plan-file.mjs --input docs/phases/phase-<n>.md --json
22
+ ```
23
+
24
+ The JSON payload reports `{ checker: "validate-plan-file", ok, errors }`. Each absent or empty-body base section contributes one entry with a distinct `missing_*` code; `ok` is `true` when every base section is present with a non-empty body.
25
+
26
+ ## 2. Start the local-planning session
27
+
28
+ Hand the plan to startup with `--plan-file` (mutually exclusive with `--issue`, `--pr`, and `--input`):
29
+
30
+ ```
31
+ node scripts/loop/resolve-dev-loop-startup.mjs --plan-file docs/phases/phase-<n>.md
32
+ ```
33
+
34
+ Startup validates the plan and threads an intake state onto its output. A plan with valid base sections and no refinement sections is `new_plan_needs_refinement`; the same plan once it also carries `Acceptance criteria` and `Definition of done` is `plan_refined_ready_for_promotion`. A base-valid plan that carries only one of the two refinement sections is reported as `ambiguous_fail_closed` with exit 0 and is not routed forward — the operator completes the missing section first. A plan that is missing/unreadable or fails the base validator makes startup fail closed (exit 1, no readiness bundle).
35
+
36
+ ## 3. Refine in place and stop at the local human-review checkpoint
37
+
38
+ The refine step writes the refiner output back into the same plan file:
39
+
40
+ ```
41
+ node scripts/refine/refine-plan-file.mjs --plan-file docs/phases/phase-<n>.md --payload <payload.json>
42
+ ```
43
+
44
+ `refinePlanFileInPlace` appends the `Acceptance criteria` and `Definition of done` sections, a `Coverage matrix` section, and a `Docs-grill findings` section, then advances the intake state to `plan_refined_ready_for_promotion` and stops at the `local_human_review` checkpoint. The docs-grill runs as a step within refinement: each finding is classified with `classifyDocsGrillFinding` (see the [Docs-Grill Step](../../docs/docs-grill-step.md)) and the dispositions are recorded into the plan. The step makes no GitHub or network call; the human reviews the refined plan before anything is promoted.
45
+
46
+ ## 4. Promote to a single draft PR
47
+
48
+ Once the human approves the refined plan, promote it:
49
+
50
+ ```
51
+ node scripts/refine/promote-plan.mjs --plan-file docs/phases/phase-<n>.md
52
+ ```
53
+
54
+ Promotion is PR-first: it commits the plan doc and opens exactly one draft PR via the canonical PR wrapper, and mints no GitHub issue. It records the plan↔PR link bidirectionally — the PR body references the committed plan-doc path, and the plan's front-matter gains a `prNumber:` entry. The committed plan doc is the spec-of-record; the draft PR enters the standard draft → pre-approval → human-merge flow. Promotion is idempotent: re-running on a plan that already carries `prNumber` resolves to `already_promoted` and opens nothing.
55
+
56
+ ## Relationship to other docs
57
+
58
+ | Doc | Relationship |
59
+ |---|---|
60
+ | [Artifact Authority Contract](artifact-authority-contract.md) | Canonical model and per-phase contract details for the local-first flow |
61
+ | [Plan-file Contract](plan-file-contract.md) | Plan-file format, base sections, validator, and the optional `prNumber` front-matter |
62
+ | [Local-Planning Worked Example](local-planning-worked-example.md) | One plan file shown through every stage of this sequence |
63
+ | [Docs-Grill Step](../../docs/docs-grill-step.md) | The in-loop grill that runs as a step within refinement |