dev-loops 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +2 -1
- package/.claude/agents/review.md +2 -1
- package/.claude/commands/loop-auto.md +7 -0
- package/.claude/commands/loop-continue.md +15 -0
- package/.claude/commands/loop-enqueue.md +22 -0
- package/.claude/commands/loop-grill.md +17 -0
- package/.claude/commands/loop-info.md +7 -0
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/loop-start-spike.md +16 -0
- package/.claude/commands/loop-start.md +7 -0
- package/.claude/commands/loop-status.md +6 -0
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/_run-context.mjs +11 -4
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
- package/.claude/skills/copilot-pr-followup/SKILL.md +24 -12
- package/.claude/skills/dev-loop/SKILL.md +19 -10
- package/.claude/skills/docs/acceptance-criteria-verification.md +6 -2
- package/.claude/skills/docs/anti-patterns.md +2 -1
- package/.claude/skills/docs/artifact-authority-contract.md +22 -4
- package/.claude/skills/docs/copilot-loop-operations.md +1 -1
- package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
- package/.claude/skills/docs/issue-intake-procedure.md +1 -0
- package/.claude/skills/docs/local-planning-flow.md +1 -1
- package/.claude/skills/docs/local-planning-worked-example.md +1 -1
- package/.claude/skills/docs/merge-preconditions.md +17 -1
- package/.claude/skills/docs/plan-file-contract.md +1 -1
- package/.claude/skills/docs/public-dev-loop-contract.md +1 -1
- package/.claude/skills/docs/release-runbook.md +45 -0
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +26 -15
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +125 -0
- package/README.md +29 -10
- package/agents/dev-loop.agent.md +11 -3
- package/agents/developer.agent.md +1 -1
- package/agents/docs.agent.md +1 -1
- package/agents/fixer.agent.md +1 -1
- package/agents/quality.agent.md +1 -1
- package/agents/refiner.agent.md +1 -1
- package/agents/review.agent.md +3 -2
- package/cli/index.mjs +35 -40
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +16 -1
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +16 -13
- package/lib/dev-loops-core.mjs +141 -0
- package/package.json +10 -8
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +174 -0
- package/scripts/github/create-label.mjs +133 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +170 -14
- package/scripts/github/detect-linked-issue-pr.mjs +22 -6
- package/scripts/github/edit-pr.mjs +259 -0
- package/scripts/github/fetch-ci-logs.mjs +208 -0
- package/scripts/github/list-issues.mjs +184 -0
- package/scripts/github/manage-sub-issues.mjs +46 -10
- package/scripts/github/offer-human-handoff.mjs +8 -5
- package/scripts/github/post-gate-findings.mjs +14 -2
- package/scripts/github/probe-ci-status.mjs +8 -2
- package/scripts/github/probe-copilot-review.mjs +21 -14
- package/scripts/github/ready-for-review.mjs +26 -8
- package/scripts/github/reconcile-draft-gate.mjs +7 -3
- package/scripts/github/reply-resolve-review-thread.mjs +16 -5
- package/scripts/github/reply-resolve-review-threads.mjs +69 -7
- package/scripts/github/request-copilot-review.mjs +8 -2
- package/scripts/github/resolve-handoff-candidates.mjs +7 -3
- package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
- package/scripts/github/stage-reviewer-draft.mjs +10 -2
- package/scripts/github/tick-verified-checkboxes.mjs +202 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +31 -10
- package/scripts/github/verify-fresh-review-context.mjs +150 -31
- package/scripts/github/view-pr.mjs +150 -0
- package/scripts/github/write-gate-context.mjs +248 -61
- package/scripts/github/write-gate-findings-log.mjs +75 -1
- package/scripts/lib/jq-output.mjs +18 -0
- package/scripts/loop/_handoff-contract.mjs +1 -0
- package/scripts/loop/_post-convergence-change.mjs +211 -0
- package/scripts/loop/_pr-runner-coordination.mjs +70 -0
- package/scripts/loop/_repo-root-resolver.mjs +47 -0
- package/scripts/loop/build-handoff-envelope.mjs +14 -4
- package/scripts/loop/check-retro-tooling.mjs +14 -3
- package/scripts/loop/checkpoint-contract.mjs +7 -4
- package/scripts/loop/cleanup-worktree.mjs +12 -3
- package/scripts/loop/conductor-monitor.mjs +12 -18
- package/scripts/loop/copilot-pr-handoff.mjs +135 -14
- package/scripts/loop/debt-remediate.mjs +24 -12
- package/scripts/loop/detect-change-scope.mjs +36 -7
- package/scripts/loop/detect-copilot-loop-state.mjs +11 -5
- package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
- package/scripts/loop/detect-internal-only-pr.mjs +8 -2
- package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +179 -66
- package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
- package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
- package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
- package/scripts/loop/ensure-worktree.mjs +8 -3
- package/scripts/loop/info.mjs +12 -4
- package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
- package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
- package/scripts/loop/inspect-run-viewer.mjs +67 -4
- package/scripts/loop/inspect-run.mjs +8 -2
- package/scripts/loop/outer-loop.mjs +8 -4
- package/scripts/loop/pr-runner-coordination.mjs +2 -9
- package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
- package/scripts/loop/pre-flight-gate.mjs +9 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
- package/scripts/loop/provision-worktree.mjs +74 -3
- package/scripts/loop/resolve-dev-loop-startup.mjs +76 -7
- package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
- package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
- package/scripts/loop/run-conductor-cycle.mjs +13 -2
- package/scripts/loop/run-queue.mjs +18 -9
- package/scripts/loop/run-refinement-audit.mjs +8 -9
- package/scripts/loop/run-watch-cycle.mjs +2 -9
- package/scripts/loop/sanctioned-commands.mjs +104 -0
- package/scripts/loop/steer-loop.mjs +29 -16
- package/scripts/loop/validate-pr-body-spec.mjs +207 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
- package/scripts/pages/build-site.mjs +59 -8
- package/scripts/pages/build-state-atlas.mjs +441 -0
- package/scripts/projects/_resolve-project.mjs +148 -0
- package/scripts/projects/add-queue-item.mjs +60 -54
- package/scripts/projects/archive-done-items.mjs +49 -84
- package/scripts/projects/ensure-queue-board.mjs +10 -36
- package/scripts/projects/list-queue-items.mjs +116 -65
- package/scripts/projects/move-queue-item.mjs +28 -49
- package/scripts/projects/reconcile-queue.mjs +253 -0
- package/scripts/projects/reorder-queue-item.mjs +41 -47
- package/scripts/projects/resolve-active-board-item.mjs +255 -0
- package/scripts/projects/sync-item-status.mjs +15 -10
- package/scripts/refine/_refine-helpers.mjs +21 -5
- package/scripts/refine/exit-spike.mjs +18 -8
- package/scripts/refine/promote-plan.mjs +22 -16
- package/scripts/refine/prose-linkage-detector.mjs +1 -1
- package/scripts/refine/refine-plan-file.mjs +13 -4
- package/scripts/refine/refinement-completeness-checker.mjs +1 -1
- package/scripts/refine/scaffold-spike-file.mjs +179 -0
- package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
- package/scripts/refine/tree-integrity-validator.mjs +1 -1
- package/scripts/refine/validate-plan-file.mjs +1 -1
- package/scripts/refine/validate-spike-file.mjs +1 -1
- package/scripts/refine/verify.mjs +11 -6
- package/scripts/release/assert-core-dependency-version.mjs +123 -0
- package/scripts/release/extract-changelog-section.mjs +125 -0
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +22 -6
- package/skills/docs/acceptance-criteria-verification.md +6 -2
- package/skills/docs/anti-patterns.md +2 -1
- package/skills/docs/artifact-authority-contract.md +22 -4
- package/skills/docs/copilot-loop-operations.md +1 -1
- package/skills/docs/cross-harness-regression-contract.md +60 -0
- package/skills/docs/issue-intake-procedure.md +1 -0
- package/skills/docs/local-planning-flow.md +1 -1
- package/skills/docs/local-planning-worked-example.md +1 -1
- package/skills/docs/merge-preconditions.md +17 -1
- package/skills/docs/plan-file-contract.md +1 -1
- package/skills/docs/public-dev-loop-contract.md +1 -1
- package/skills/docs/release-runbook.md +45 -0
- package/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +26 -15
- package/skills/loop-grill/SKILL.md +165 -0
- package/scripts/loop/conductor.mjs +0 -233
- package/scripts/loop/detect-stale-runner.mjs +0 -265
- package/scripts/loop/pre-push-main-guard.mjs +0 -117
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
// Inlined (was scripts/_core-helpers.mjs -> @dev-loops/core): this script runs
|
|
6
|
+
// in release.yml with no `npm ci`, so it must import only node: builtins.
|
|
7
|
+
// A workspace import here ERR_MODULE_NOT_FOUNDs and the Release never gets made.
|
|
8
|
+
function isDirectCliRun(importMetaUrl, argv1 = process.argv[1]) {
|
|
9
|
+
if (typeof argv1 !== "string" || argv1.length === 0) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(importMetaUrl));
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const USAGE = `Usage: extract-changelog-section.mjs --version <v> [--changelog <path>]
|
|
20
|
+
|
|
21
|
+
Prints the CHANGELOG.md section for <version> (the block from "## <version>"
|
|
22
|
+
up to the next "## " heading). Exits 1 if no such section exists or the section
|
|
23
|
+
is empty, so a release is never created for an undocumented version. Exits 2 on
|
|
24
|
+
usage/argument errors (missing --version, missing flag value, unreadable file).`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract the changelog block for a single version.
|
|
28
|
+
*
|
|
29
|
+
* Matches a heading line of the form "## <version>" optionally followed by
|
|
30
|
+
* " - <date>" (or any trailing text), and returns everything up to but not
|
|
31
|
+
* including the next "## " heading. Returns null when no matching section
|
|
32
|
+
* exists (fail closed — caller must not synthesize notes).
|
|
33
|
+
*
|
|
34
|
+
* @param {string} changelog - Full CHANGELOG.md contents.
|
|
35
|
+
* @param {string} version - Version without a leading "v" (e.g. "0.5.0").
|
|
36
|
+
* @returns {string|null} Trimmed section body (without the heading), or null.
|
|
37
|
+
*/
|
|
38
|
+
export function extractChangelogSection(changelog, version) {
|
|
39
|
+
const lines = changelog.split("\n");
|
|
40
|
+
// A heading is "## " followed by the exact version token, then either end of
|
|
41
|
+
// line or a non-version-character (space / "-"). This avoids "0.5.0" matching
|
|
42
|
+
// "0.5.0-rc1" or "0.5.01".
|
|
43
|
+
const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44
|
+
const headingRe = new RegExp(`^##\\s+v?${escaped}(?=\\s|$)`);
|
|
45
|
+
|
|
46
|
+
let start = -1;
|
|
47
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
48
|
+
if (headingRe.test(lines[i])) {
|
|
49
|
+
start = i;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (start === -1) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const body = [];
|
|
59
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
60
|
+
if (/^##\s/.test(lines[i])) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
body.push(lines[i]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return body.join("\n").trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
70
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
71
|
+
process.stdout.write(`${USAGE}\n`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let version;
|
|
76
|
+
let changelogPath = "CHANGELOG.md";
|
|
77
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
78
|
+
if (argv[i] === "--version" || argv[i] === "--changelog") {
|
|
79
|
+
const flag = argv[i];
|
|
80
|
+
const value = argv[i + 1];
|
|
81
|
+
if (value === undefined) {
|
|
82
|
+
process.stderr.write(`error: ${flag} requires a value\n\n${USAGE}\n`);
|
|
83
|
+
return 2;
|
|
84
|
+
}
|
|
85
|
+
if (flag === "--version") {
|
|
86
|
+
version = value;
|
|
87
|
+
} else {
|
|
88
|
+
changelogPath = value;
|
|
89
|
+
}
|
|
90
|
+
i += 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!version) {
|
|
95
|
+
process.stderr.write(`error: --version is required\n\n${USAGE}\n`);
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const normalized = version.replace(/^v/, "");
|
|
100
|
+
|
|
101
|
+
let changelog;
|
|
102
|
+
try {
|
|
103
|
+
changelog = await readFile(changelogPath, "utf8");
|
|
104
|
+
} catch (error) {
|
|
105
|
+
process.stderr.write(`error: cannot read ${changelogPath}: ${error.message}\n`);
|
|
106
|
+
return 2;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const section = extractChangelogSection(changelog, normalized);
|
|
110
|
+
if (section === null || section === "") {
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
`error: no section found for version ${normalized} in ${changelogPath}. ` +
|
|
113
|
+
`Refusing to create a release for an undocumented version.\n`,
|
|
114
|
+
);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
process.stdout.write(`${section}\n`);
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isDirectCliRun(import.meta.url)) {
|
|
123
|
+
const exitCode = await main();
|
|
124
|
+
process.exitCode = exitCode;
|
|
125
|
+
}
|
|
@@ -243,7 +243,8 @@ When unresolved feedback exists, use a narrow follow-up loop:
|
|
|
243
243
|
- resolve the review-round cap from config via `resolveRefinementConfig(config, "maxCopilotRounds")` from `@dev-loops/core/config`; default config ships `maxCopilotRounds: 5`
|
|
244
244
|
- **Opt out entirely:** `maxCopilotRounds: 0` disables the external Copilot review gate for the repo — the loop runs `draft_gate → pre_approval_gate` with the local harness only, never requesting or waiting on Copilot. Use this when the repo has no Copilot reviewer configured or prefers local-harness-only review.
|
|
245
245
|
- use the completed Copilot review-round count from `detect-copilot-loop-state.mjs` / `copilot-pr-handoff.mjs` as the current PR's review-round count
|
|
246
|
-
- if completed review rounds have reached the maximum (default: 5), do **not** re-request Copilot review
|
|
246
|
+
- if completed review rounds have reached the maximum (default: 5), do **not** re-request Copilot review within that concluded cycle
|
|
247
|
+
- if the loop already converged and then significant post-convergence changes land on a newer head (new/changed product or test logic, not doc/message/comment-only edits), treat that as a NEW cycle and re-request Copilot review when regular rounds are already > 0 (the prior cycle's cap does not suppress this new-cycle request)
|
|
247
248
|
- when the round limit is reached **and** the refreshed thread snapshot proves zero unresolved threads **and** current-head CI is green or credibly green, treat that clean state as eligible for `pre_approval_gate` fallback instead of deadlocking on another Copilot rerequest
|
|
248
249
|
- when using that fallback, add a short round-exhaustion note to the visible `pre_approval_gate` gate evidence so the PR records why no further Copilot rerequest occurred
|
|
249
250
|
- if the round cap is reached before the PR is thread-clean or before CI is green/credibly green, reply-resolve any remaining intentionally deferred threads with a short `deferred to follow-up` note, then stop and report that the Copilot round limit was reached
|
|
@@ -305,7 +306,7 @@ Do NOT use `gh pr comment`, `gh api`, or `gh pr review` for gate comments.
|
|
|
305
306
|
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).
|
|
306
307
|
|
|
307
308
|
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.
|
|
308
|
-
2. **Fan-out (Phase 2) — seed each reviewer with the bundle verbatim:** plan batches with `planFanoutBatches(angles,
|
|
309
|
+
2. **Fan-out (Phase 2) — seed each reviewer with the bundle verbatim:** plan batches with `planFanoutBatches(angles, cap)` using the `gates.maxFanoutReviewers` cap (default 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.
|
|
309
310
|
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.
|
|
310
311
|
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.
|
|
311
312
|
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.
|
|
@@ -322,11 +323,7 @@ The canonical checkpoint verdict comment contract is [Gate Review Comment Contra
|
|
|
322
323
|
- **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.
|
|
323
324
|
- **Pass criteria:** all configured draft gate angles pass; all findings at severities in `blockCleanOnFindingSeverities` are addressed; validation passes; no unrelated files are included.
|
|
324
325
|
- **Next step after passing:** mark the PR ready for review.
|
|
325
|
-
- **Board status sync (
|
|
326
|
-
```sh
|
|
327
|
-
node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "In Progress"
|
|
328
|
-
```
|
|
329
|
-
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.
|
|
326
|
+
- **Board status sync (built-in, after ready-for-review):** the In-Progress board move is now performed automatically as a deterministic tail of `ready-for-review.mjs` — marking the PR ready couples the board move to the ready transition (#1069), so no separate `sync-item-status` step is needed. It stays 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.
|
|
330
327
|
- **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.
|
|
331
328
|
- **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`.
|
|
332
329
|
|
|
@@ -340,7 +337,7 @@ This is the default pre-approval gate for this workflow boundary. The canonical
|
|
|
340
337
|
- **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`.
|
|
341
338
|
- **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.
|
|
342
339
|
- **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.
|
|
343
|
-
- **Acceptance criteria verification:** follow the canonical procedure in [Acceptance Criteria Verification](../docs/acceptance-criteria-verification.md) before posting the `pre_approval_gate` comment.
|
|
340
|
+
- **Acceptance criteria verification:** follow the canonical procedure in [Acceptance Criteria Verification](../docs/acceptance-criteria-verification.md) before posting the `pre_approval_gate` comment. After a clean verification this also ticks the verified PR-body checkboxes via `scripts/github/tick-verified-checkboxes.mjs`, so the merged PR shows checked AC.
|
|
344
341
|
- **Next step after passing:** continue the Step 7 flow and then proceed to the human approval checkpoint below.
|
|
345
342
|
- **Non-substitution rule:** a clean `pre_approval_gate` comment is separate from `draft_gate` evidence. It governs final-approval readiness for that head SHA; it does **not** replace the required `draft_gate` evidence for leaving draft.
|
|
346
343
|
- **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. Do not declare final-approval readiness unless a visible `clean` `pre_approval_gate` checkpoint verdict comment exists for the current head SHA. Final-approval readiness must not rely only on local or hidden artifacts; the visible PR comment is the required auditable evidence. If the checkpoint verdict comment cannot be posted, fail closed and do not declare final-approval readiness. A checkpoint verdict comment for an older head SHA does not satisfy this requirement for the current head. If findings exist, follow-up fixes are required before final approval. The `pre_approval_gate` procedure must be entered and completed (visible comment posted) before any merge-ready or approval-ready declaration. Skipping the gate is not recoverable by asserting convergence. If fixes advance the head SHA, post a new checkpoint verdict comment for the new head.
|
|
@@ -350,14 +347,14 @@ This is the default pre-approval gate for this workflow boundary. The canonical
|
|
|
350
347
|
Before any merge-ready or final-approval claim, run `detect-pr-gate-coordination-state.mjs` for the current PR. If it reports `gateBoundary=conflict_resolution` or `mergeStateStatus` is conflicted, stop the normal gate path immediately and use this recovery flow:
|
|
351
348
|
|
|
352
349
|
1. fetch fresh `origin/main`, confirm the current PR head SHA, and summarize the conflict scope from `mergeStateStatus` plus any reported `conflictFiles`
|
|
353
|
-
2. ask for explicit authorization before any
|
|
354
|
-
3. after authorization, reconcile locally on the PR branch; default to
|
|
350
|
+
2. ask for explicit authorization before any merge commit or other branch-state-changing reconciliation command
|
|
351
|
+
3. after authorization, reconcile locally on the PR branch; default to a merge commit (`git merge origin/main`) per the behind-branch integration policy in [Local Implementation Skill](../local-implementation/SKILL.md#branch--review--merge-policy), unless the operator explicitly chooses another conflict-resolution command
|
|
355
352
|
4. auto-resolve simple conflicts when the correct fix is mechanical and clearly in scope; report complex conflicts explicitly and fix them manually only for in-scope files
|
|
356
353
|
5. rerun the smallest honest local validation for the touched conflict slice
|
|
357
354
|
6. rerun `detect-pr-gate-coordination-state.mjs` for the new head
|
|
358
355
|
7. because the head changed, rerun `pre_approval_gate` for the new head before any approval-ready or merge-ready claim
|
|
359
356
|
8. wait for current-head CI again before retrying merge evaluation
|
|
360
|
-
9. if the chosen reconciliation rewrote branch history (
|
|
357
|
+
9. if the chosen reconciliation rewrote branch history (rebase only — merge commits push as a normal fast-forward), ask for explicit authorization before `git push --force-with-lease` (`--force-with-lease` only, never bare `--force`), then continue the loop on the updated head
|
|
361
358
|
|
|
362
359
|
`mergeStateStatus: CLEAN` alone is not enough to resume approval or merge claims. The existing merge-ready preconditions still apply: zero unresolved review threads, a clean current-head `pre_approval_gate`, and green current-head CI.
|
|
363
360
|
|
|
@@ -384,6 +381,21 @@ node <resolved-skill-scripts>/github/detect-checkpoint-evidence.mjs \
|
|
|
384
381
|
|
|
385
382
|
This helper is always-on: it uses `gh api` to fetch visible PR issue comments and fails closed unless both required gate comments exist: a clean `draft_gate` comment for the one-time draft boundary and a clean current-head `pre_approval_gate` comment. Do not run `gh pr merge` if this command exits non-zero. There is no opt-out flag. Resolved threads, green CI, clean Copilot rereview, or local notes do not substitute for this successful helper output. If a final approval or merge boundary sees `gh pr merge` without a same-boundary successful check, treat that as a workflow violation and stop.
|
|
386
383
|
|
|
384
|
+
### Stale runner-coordination lock held by a completed run
|
|
385
|
+
|
|
386
|
+
The pre-merge gate evidence check fails closed on the PR's runner-coordination claim (`.pi/runner-coordination/<owner>/<name>/pr-<n>.json`): a fresh merge re-dispatch (new run id) is refused with `ownership_lost`, or `stale_runner` once the claim ages past the max-age window.
|
|
387
|
+
|
|
388
|
+
The auto-loop now releases its claim best-effort when a run reaches a terminal stop (clean-converged, blocked, or done — including the stop at the human approval checkpoint), so a merge-authorized re-dispatch normally inherits a cleared claim and proceeds. The release is non-fatal: it never blocks the stop, and it never clears a claim owned by a genuinely active competing run.
|
|
389
|
+
|
|
390
|
+
If a stale claim still blocks the merge because the completing run could not release (crash, killed process, or a pre-#1109 run), the sanctioned recovery for a lock held by a COMPLETED run is an explicit takeover by the merge run:
|
|
391
|
+
|
|
392
|
+
```sh
|
|
393
|
+
node <resolved-skill-scripts>/loop/pr-runner-coordination.mjs takeover \
|
|
394
|
+
--repo <owner/name> --pr <number>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`takeover` seizes ownership for the current run id and records the displaced run under `previousRun`. Only take over when the prior owner is genuinely completed/dead. A genuinely active (non-stale) run must still be allowed to block — do not take over to race a live run.
|
|
398
|
+
|
|
387
399
|
### Mandatory post-merge retrospective checkpoint write
|
|
388
400
|
|
|
389
401
|
After a merge succeeds (or an explicit retrospective skip is authorized), write the durable retrospective checkpoint before exiting the subagent session:
|
|
@@ -410,7 +422,7 @@ node <resolved-skill-scripts>/projects/archive-done-items.mjs --repo <owner/name
|
|
|
410
422
|
|
|
411
423
|
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.
|
|
412
424
|
|
|
413
|
-
|
|
425
|
+
The deterministic mechanism that converges merged→Done is `dev-loops queue reconcile` — idempotent, run best-effort at loop startup — so the merge lands on the "Done" column without a remembered manual step. The manual sync below is now an optional fallback (e.g. to converge immediately without waiting for the next startup reconcile):
|
|
414
426
|
|
|
415
427
|
```sh
|
|
416
428
|
node <resolved-skill-scripts>/projects/sync-item-status.mjs --repo <owner/name> --item <linked-issue> --to-column "Done"
|
package/skills/dev-loop/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ allowed-tools: read bash edit write subagent review_loop
|
|
|
10
10
|
|
|
11
11
|
**No-implicit-start rule:** Never start implementation without explicit instruction.
|
|
12
12
|
|
|
13
|
-
**Work-origin rule:** All work must originate from a tracked artifact: a GitHub issue (tracker-first)
|
|
13
|
+
**Work-origin rule:** All work must originate from a tracked artifact: a GitHub issue (tracker-first), a persisted markdown plan file (local-planning), or — on the sanctioned lightweight path — the PR description itself as the spec-of-record (`--lightweight`, `canonicalSpecSource: pr_body`; no committed plan artifact). See [Artifact Authority Contract](../docs/artifact-authority-contract.md) for canonical mode definitions and settings. No work may originate from a PR (other than the sanctioned lightweight PR-body-as-spec path) or a direct local change unless explicitly requested.
|
|
14
14
|
|
|
15
15
|
# Unified Dev Loop
|
|
16
16
|
|
|
@@ -35,7 +35,14 @@ For async-required routes (config `workflow.asyncStartMode`, default `required`)
|
|
|
35
35
|
> 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).
|
|
36
36
|
|
|
37
37
|
<!-- pi-only -->
|
|
38
|
-
**CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands below are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>`
|
|
38
|
+
**CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands below are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>` via the first of these **bounded** candidates whose `cli/index.mjs` exists — never assume a single fixed layout (under a Pi user-level install the package lives at `~/.pi/agent/npm/node_modules/dev-loops/`, so the old `../../..` package-relative guess from `skills/dev-loop/SKILL.md` overshoots the package root):
|
|
39
|
+
|
|
40
|
+
1. **Node module resolution** (best-effort first try): `node -e "try{const p=require('node:path');console.log(p.resolve(p.dirname(require.resolve('dev-loops/cli/index.mjs')),'..'))}catch{process.exit(1)}"` — resolves the package root when `dev-loops` is reachable from Node's module search path (notably under `~/.pi/agent/npm`); this is cwd-dependent and commonly misses from a target-repo cwd, so the probe is wrapped in try/catch (no stack trace, exits non-zero on miss) — treat a non-zero exit as "probe missed, try the next candidate", not a hard failure.
|
|
41
|
+
2. **Pi user-agent npm root** (reliable for user-level installs): `~/.pi/agent/npm/node_modules/dev-loops`.
|
|
42
|
+
3. **Package-relative (legacy):** `../../..` from this skill's own directory (the original package-local install layout).
|
|
43
|
+
4. **Global npm root:** `$(npm root -g)/dev-loops`.
|
|
44
|
+
|
|
45
|
+
NEVER fall back to `find /` or any unbounded filesystem walk to locate the CLI — it stalls and trips the needs-attention timeout. If every bounded candidate fails, stop and ask the orchestrator/operator for the dev-loops package root rather than searching. (The `dev-loop` agent resolves it analogously.)
|
|
39
46
|
<!-- /pi-only -->
|
|
40
47
|
|
|
41
48
|
Resolve authoritative state via the startup resolver (`node <dev-loops-package-root>/cli/index.mjs loop startup --issue <n>` for issues, `node <dev-loops-package-root>/cli/index.mjs loop startup --pr <n>` for PRs), then immediately build the handoff envelope via `node <dev-loops-package-root>/cli/index.mjs 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.
|
|
@@ -124,8 +131,16 @@ When you need a fact from a dev-loops JSON-emitting script, climb this ladder an
|
|
|
124
131
|
|
|
125
132
|
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.
|
|
126
133
|
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`.
|
|
127
|
-
3. **`--jq <filter>` to extract a single field.**
|
|
128
|
-
4.
|
|
134
|
+
3. **`--jq <filter>` to extract a single field.** `--jq`/`--silent` are a BASE-CLI GUARANTEE across every operator-facing JSON-result dev-loops command — each accepts a gh-style `--jq` filter (jq subset: field access, `.[]`/`.[N]`, `|`, `select(...)`, `==`/`!=`/`<`/`<=`/`>`/`>=`, `length`, `keys`), not just a named subset. (A few scripts are out of scope — e.g. build/smoke tooling, dashboard servers, dormant/unwired adapters, and scripts that write JSON to a file rather than stdout — each carried with its reason in the contract test's exclusion list.) It prints only the filtered value. An invalid filter fails closed (stderr + exit `2`), distinct from a clean predicate-false (silent exit `1`). This is enforced by a contract test (`test/contracts/jq-output-base-guarantee-contract.test.mjs`) that fails the build if a new JSON-emitting command ships without wiring the shared `scripts/lib/jq-output.mjs` emit path.
|
|
135
|
+
4. **Use a dev-loops wrapper for `gh` reads — never an agent-level raw `gh`.** A raw `gh` call is a recorded advisory retro violation (issue #1077: reported, never blocking). If no script covers the read you need, treat it as a tooling gap: file/build a thin wrapper (reuse `scripts/lib/jq-output.mjs`, like the ones below), don't shell out. The reads/edits that already have wrappers:
|
|
136
|
+
- CI run-log tail (a failing PR's job log) → `scripts/github/fetch-ci-logs.mjs --repo <o/r> --pr <n> [--failed-only] [--tail <n>]`, **never raw `gh run view --log`/`--log-failed`**. (`probe-ci-status.mjs` names the failed checks; this returns the LOG.)
|
|
137
|
+
- Issue list/filter → `scripts/github/list-issues.mjs --repo <o/r> [--state <open|closed|all>] [--label <l>] [--limit <n>]`, **never raw `gh issue list`**. (The queue tool lists the project board; this is for arbitrary issue queries.)
|
|
138
|
+
- Issue comment → `scripts/github/comment-issue.mjs --repo <o/r> --issue <n> (--body <text> | --body-file <path>)`, **never raw `gh issue comment`**. Returns `{ ok, commentUrl }`.
|
|
139
|
+
- PR facts read (branch/state/mergeStateStatus/head SHA/etc.) → `scripts/github/view-pr.mjs --repo <o/r> --pr <n> [--json <fields>]`, **never raw `gh pr view`**. (For composite loop-routing/CI facts prefer `loop info --pr`; this is the thin field-read counterpart.)
|
|
140
|
+
- PR edit (title/body/assignee/milestone) → `scripts/github/edit-pr.mjs --repo <o/r> --pr <n> [--title <t>] [--body <b> | --body-file <path>] [--add-assignee <u>] [--remove-assignee <u>] [--milestone <m>]`, **never raw `gh pr edit`**. Returns `{ ok, repo, pr, edited }`.
|
|
141
|
+
- PR checks/status → `scripts/github/probe-ci-status.mjs --repo <o/r> --pr <n> --timeout-ms 0` for a single live combined-CI check, **never raw `gh pr checks`**. (Provider-agnostic; drop the `--timeout-ms 0` to block-wait.)
|
|
142
|
+
|
|
143
|
+
These accept the same `--jq`/`--silent` output flags as every other JSON-emitting script (base-CLI guarantee) — including `probe-ci-status.mjs`, watch-shaped as it is.
|
|
129
144
|
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.
|
|
130
145
|
|
|
131
146
|
## Guard rules
|
|
@@ -136,9 +151,9 @@ When you need a fact from a dev-loops JSON-emitting script, climb this ladder an
|
|
|
136
151
|
|
|
137
152
|
**Inline-first rule:** Prefer inline commands over nested async delegation when managing a single PR. Use nested delegation only for parallel fan-out or when the parent needs to continue other work.
|
|
138
153
|
|
|
139
|
-
**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`.
|
|
154
|
+
**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`. For fan-out reviewer waits (gate sub-loops or any Agent-tool fan-out), await completion via the harness completion notification or the reviewer's findings artifact at its deterministic path, then join via `consolidateFanin` — never tail/parse a subagent transcript, never `node -e`/`python3`-parse tool JSON, never `sleep`-poll (see the [fan-in wait anti-pattern](../docs/anti-patterns.md) and [Checkpoint Review Chain Contract](../../docs/gate-review-sub-loop-contract.md) Phase 2).
|
|
140
155
|
|
|
141
|
-
**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
|
|
156
|
+
**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 **within that review cycle**. Exception: if the loop already converged and then significant post-convergence changes land on a newer head (new/changed product or test logic, not doc/message/comment-only edits), open a new Copilot review cycle and re-request review when regular rounds are already > 0, even if the previous cycle hit 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`.
|
|
142
157
|
|
|
143
158
|
## Shorthand issue-based auto trigger contract
|
|
144
159
|
|
|
@@ -157,3 +172,4 @@ All PRs must pass the full gate pipeline before merge. No scope is exempt: docs-
|
|
|
157
172
|
- Before any state-changing action, get explicit confirmation unless already authorized.
|
|
158
173
|
- A question requires an answer, not an action.
|
|
159
174
|
- Stop and ask rather than guessing when facts don't agree.
|
|
175
|
+
- Cross-harness regression contract: [Cross-Harness Regression Contract](../docs/cross-harness-regression-contract.md).
|
|
@@ -4,7 +4,9 @@ Extracted procedure for verifying issue acceptance criteria during the `pre_appr
|
|
|
4
4
|
|
|
5
5
|
## Procedure
|
|
6
6
|
|
|
7
|
-
Before posting the `pre_approval_gate` comment, verify every acceptance criteria checklist item in the issue linked to this PR:
|
|
7
|
+
Before posting the `pre_approval_gate` comment, verify every acceptance criteria checklist item in the issue linked to this PR, and reflect the verified items back into both the linked issue body and the PR body (exception: under the lightweight path — `specSource: pr_body` — the step 5 issue-body mirroring is skipped, since the PR body, not the issue body, is the canonical spec surface; see the lightweight fork below):
|
|
8
|
+
|
|
9
|
+
> **Lightweight (PR-body-as-spec) fork:** when the session is lightweight — the resolver output / handoff envelope carries `specSource: pr_body` (`canonicalSpecSource: pr_body`) — a linked issue still exists (`--lightweight` runs on the `--issue` path, so keep the `Closes #N` linkage and all issue-level tracking), but the **PR body**, not the issue body, is the canonical spec-of-record read for AC/DoD. In that case, still resolve the linked issue for tracking, but read the AC/DoD/invariants from the PR body rather than the issue body (skip step 2's issue-body read for spec purposes). Run `node scripts/loop/validate-pr-body-spec.mjs --repo <owner/name> --pr <pr-number> --expected-issue <issue-number>` (reuses `@dev-loops/core/loop/issue-refinement-artifact`) to confirm the body carries the required invariants — it fails closed with a per-section `missing_*` reason if any is absent, in which case post the gate comment with verdict `blocked`. The PR body MUST carry the `Closes #N` linkage (or GitHub's other closing-keyword forms): `validate-pr-body-spec` fails closed with `missing_closing_issue_reference` without it, and with `closes_wrong_issue` when `--expected-issue` doesn't match. Then continue at step 3, extracting the **Acceptance criteria** checklist items from the PR body itself; step 5's issue-body AC-tick update is unnecessary for lightweight since the issue body is not the spec surface (the PR body is ticked in step 6 as usual). The default issue-backed procedure below is unchanged.
|
|
8
10
|
|
|
9
11
|
1. **Resolve the linked issue number deterministically:** use `gh pr view <pr-number> --repo <owner/name> --json closingIssuesReferences,body` and apply this decision tree: if there is exactly one closing issue reference, use it; else if there is exactly one PR-body `Closes #N` / `Fixes #N` pattern, use it; otherwise (zero or multiple candidates), post the gate comment with verdict `blocked` (gate cannot complete deterministically) rather than guessing.
|
|
10
12
|
|
|
@@ -16,6 +18,8 @@ Before posting the `pre_approval_gate` comment, verify every acceptance criteria
|
|
|
16
18
|
|
|
17
19
|
5. **Update the issue body once:** compute the fully-updated issue body by replacing each verified item's `- [ ]` with `- [x]`, write it to a temporary file, and perform a single `gh issue edit <issue-number> --body-file <tmp-file> --repo <owner/name>`. Do not issue one edit per item; prefer `--body-file` over inline `--body` to avoid shell quoting/escaping hazards.
|
|
18
20
|
|
|
19
|
-
6. **
|
|
21
|
+
6. **Tick the PR body once:** after the verification is clean, flip each verified item's `- [ ]` to `- [x]` in the PR body via a single `gh pr edit --body-file` update, so the merged PR shows checked AC/DoD. Run `node scripts/github/tick-verified-checkboxes.mjs --repo <owner/name> --pr <pr-number> --verified <exact label>...` (one edit; exact-label match; only items actually verified are ticked; unverified or deferred items stay `- [ ]`; fail closed). This runs automatically as part of the `pre_approval_gate`, not by memory.
|
|
22
|
+
|
|
23
|
+
7. **Post the gate comment:** always post a `pre_approval_gate` comment (the checkpoint verdict comment contract requires a visible comment even for non-`clean` verdicts). Use verdict `clean` only when all AC items are verified; use verdict `findings_present` when any AC item is not satisfied and requires follow-up fixes; use verdict `blocked` when the gate cannot complete deterministically (for example no linked issue, ambiguous issue linkage, or the issue body is unavailable). In all cases include a note on AC verification status.
|
|
20
24
|
|
|
21
25
|
When the issue body has no AC checklist items, post the gate comment with verdict `findings_present` and note that fact explicitly rather than assuming satisfaction.
|
|
@@ -11,7 +11,8 @@ 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 queue add ... --
|
|
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 ... --next-up` to land directly in the normative `Next Up` pickup queue; default lands in Backlog, which is unprioritized intake and never auto-picked), 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
|
+
9. **Hand-rolling a fan-in wait by polling a subagent transcript or parsing tool JSON with `node -e`/`python3`**: When awaiting fan-out reviewers (gate `draft_gate`/`pre_approval_gate` sub-loops, or any Agent-tool fan-out), NEVER tail/parse another agent's JSONL transcript, NEVER use `node -e`/`python3` to parse tool/subagent JSON, and NEVER `sleep`-poll in a shell loop for completion. These improvisations breach the [internal-tooling-only rule (issue #982)](retrospective-checkpoint-contract.md#internal-tooling-only-rule-issue-982--now-advisory) (no `node -e`/`python3` parsing of tool JSON) and the no-shell-polling rule, and a single improvised wait adds a non-empty entry to the advisory `retrospectiveFindings.rawCallViolations` array (issue #1077: reported to the conductor, **never blocking**). **Sanctioned wait:** rely on the harness completion notification, or read each reviewer's findings artifact at its deterministic output path (see [Checkpoint Review Chain Contract](../../docs/gate-review-sub-loop-contract.md) Phase 2), then join via `consolidateFanin` from `@dev-loops/core/loop/gate-fanin` (Phase 3). See also the dev-loop SKILL "Bounded async task contract".
|
|
15
16
|
|
|
16
17
|
## Light mode exception
|
|
17
18
|
|
|
@@ -10,7 +10,7 @@ Other repo docs may summarize or link this contract, but they should not redefin
|
|
|
10
10
|
|
|
11
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
|
-
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
|
|
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 local-first code-level default in `BUILT_IN_DEFAULTS`.
|
|
14
14
|
|
|
15
15
|
### Tracker-first
|
|
16
16
|
|
|
@@ -25,6 +25,7 @@ Key contract:
|
|
|
25
25
|
- GitHub issue state is authoritative — not local notes or chat context
|
|
26
26
|
- A linked PR is the single canonical follow-up artifact for the issue
|
|
27
27
|
- When an open linked PR exists, reuse it rather than opening another
|
|
28
|
+
- Follow-ups discovered while working a PR/loop are noted on the originating issue or PR body by default; a standalone issue is filed only when the follow-up is genuinely independent of the PR and outlives it (see [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md))
|
|
28
29
|
- 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`)
|
|
29
30
|
|
|
30
31
|
### Local-planning
|
|
@@ -41,12 +42,29 @@ Key contract:
|
|
|
41
42
|
- GitHub issues may still be used for tracking or linking, but the plan file is authoritative for scope and acceptance criteria
|
|
42
43
|
- A tracker-backed local implementation session (GitHub issue as canonical spec) must not also maintain a duplicate `docs/phases/phase-<n>.md` — see [Public Dev Loop Contract](public-dev-loop-contract.md) "Tracker-backed local implementation input-source contract"
|
|
43
44
|
|
|
45
|
+
### Lightweight (PR-body-as-spec)
|
|
46
|
+
|
|
47
|
+
**The PR description itself is the authoritative artifact store — no committed plan artifact.** This is a lightweight modifier on the local `--issue` path (`resolve-dev-loop-startup.mjs --issue <n> --lightweight`, `canonicalSpecSource: pr_body`), not a settings-level mode. No phase/plan doc is minted or committed; the PR body carries the spec-of-record invariants directly. The gate sequence is identical to the phase-doc path (draft → pre-approval fanout → detect-evidence → human merge); only the backing artifact differs (PR body vs phase doc).
|
|
48
|
+
|
|
49
|
+
Artifacts:
|
|
50
|
+
- **Planning + execution artifact:** the GitHub PR — its description is the spec, its diff is the execution
|
|
51
|
+
- **No committed plan doc:** no `docs/phases/*.md` is created for the session
|
|
52
|
+
|
|
53
|
+
Key contract:
|
|
54
|
+
- The PR body must carry the same invariants a durable spec would: **Objective/why, in-scope + explicit non-goals, testable acceptance criteria, definition of done, open questions/risks**. `scripts/loop/validate-pr-body-spec.mjs` (reusing the generic markdown logic of `@dev-loops/core/loop/issue-refinement-artifact`, `validatePrBodySpec`) validates these and **fails closed** with a distinct `missing_*` reason per absent invariant.
|
|
55
|
+
- The PR body MUST also carry the `Closes #N` linkage (GitHub's other closing keywords count too); `validate-pr-body-spec` fails closed with `missing_closing_issue_reference` without it, and with `closes_wrong_issue` when an `--expected-issue` is given and doesn't match — the lightweight path's issue-tracking state must never silently diverge from PR state (issue #1181).
|
|
56
|
+
- This flips the promotion invariant below (P4, "the PR body carries the committed plan-doc **path**"): under lightweight there is no committed plan doc — the PR body **is** the spec, not a pointer to one.
|
|
57
|
+
- The explicit `--lightweight` flag is the primary, deterministic trigger. The secondary heuristic (chore/fix commit type + no `--plan-file` + small change) is a documented manual signal for when to reach for the flag; it is not an automatic selector.
|
|
58
|
+
- `--lightweight` is rejected when combined with `--plan-file` (they are opposites: `--plan-file` commits a durable plan doc as the spec, `--lightweight` makes the PR body the spec) and only composes with `--issue`.
|
|
59
|
+
- Pre-approval acceptance-criteria verification reads the AC/DoD/invariants directly from the PR body rather than a linked issue body; see [Acceptance Criteria Verification](acceptance-criteria-verification.md).
|
|
60
|
+
|
|
44
61
|
### Mode selection table
|
|
45
62
|
|
|
46
63
|
| Mode | Canonical artifact | GitHub issue required | Settings value |
|
|
47
64
|
|---|---|---|---|
|
|
48
65
|
| Tracker-first | GitHub issue | Yes | `strategy.default: github-first` |
|
|
49
66
|
| Local-planning (shipped default) | Markdown plan file | No | `strategy.default: local-first` |
|
|
67
|
+
| Lightweight (PR-body-as-spec) | GitHub PR description | Yes (`--issue`) | modifier: `--lightweight` (`canonicalSpecSource: pr_body`) |
|
|
50
68
|
|
|
51
69
|
`inputSource.default` further disambiguates local-first startup:
|
|
52
70
|
| inputSource | Meaning |
|
|
@@ -81,7 +99,7 @@ The `inputSource.default` key disambiguates local-first startup:
|
|
|
81
99
|
|
|
82
100
|
The effective default for a consumer comes from the config-merge layering in `packages/core/src/config/config.mjs`. Precedence, low to high:
|
|
83
101
|
|
|
84
|
-
1. `BUILT_IN_DEFAULTS` (frozen in `config.mjs`) — `strategy.default:
|
|
102
|
+
1. `BUILT_IN_DEFAULTS` (frozen in `config.mjs`) — `strategy.default: local-first`. This is the code-level fallback when no other layer sets the key.
|
|
85
103
|
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
104
|
3. Repo-local `.pi/dev-loop/defaults.*` (legacy) — applied when present.
|
|
87
105
|
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.
|
|
@@ -95,7 +113,7 @@ Two legacy repo-local layers also exist under `.pi/dev-loop/` (the package no lo
|
|
|
95
113
|
These are not valid artifact authority mode selectors:
|
|
96
114
|
- `strategy.default: copilot` — not a valid mode; the enum accepts only `github-first` or `local-first` (`packages/core/src/config/config.mjs`)
|
|
97
115
|
- Free-form string values — fail closed
|
|
98
|
-
- Omitting `strategy.default` from every layer — resolves to `
|
|
116
|
+
- Omitting `strategy.default` from every layer — resolves to `local-first` from `BUILT_IN_DEFAULTS`
|
|
99
117
|
|
|
100
118
|
## Local-first plan-file flow end to end
|
|
101
119
|
|
|
@@ -103,7 +121,7 @@ Under local-planning, one plan file moves through four stages. Each stage has a
|
|
|
103
121
|
|
|
104
122
|
### P1 — Plan-file artifact + config (#949)
|
|
105
123
|
|
|
106
|
-
The plan file is a phase-doc-format markdown document.
|
|
124
|
+
The plan file is a phase-doc-format markdown document. It lives under `docs/phases/`, the existing phase-docs directory. 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
125
|
|
|
108
126
|
### P2 — Intake (#950)
|
|
109
127
|
|
|
@@ -188,7 +188,7 @@ Preferred defaults for this repo:
|
|
|
188
188
|
- parent/subagent no-activity threshold for watcher-style runs: at least **15 minutes**
|
|
189
189
|
- active-long-running notice threshold for watcher-style runs: about **30 minutes**
|
|
190
190
|
|
|
191
|
-
These are the defaults built into `probe-copilot-review.mjs`, `run-watch-cycle.mjs`, and the `watchArgs` emitted by `copilot-pr-handoff.mjs`. Do not pass removed CLI policy flags (`--poll-interval-ms`, `--
|
|
191
|
+
These are the defaults built into `probe-copilot-review.mjs`, `run-watch-cycle.mjs`, and the `watchArgs` emitted by `copilot-pr-handoff.mjs`. Do not pass removed CLI policy flags (`--poll-interval-ms`, `--probe-only`): the scripts reject them as usage errors, but do not rely on that as a guaranteed hard stop in the `dev-loops gate …` path — the CLI retry-wrapper (`buildCorrectedArgs`) may strip an unrecognized/removed flag and silently retry with defaults, so passing one can quietly fall back to default behavior rather than failing. `probe-copilot-review.mjs` does accept `--timeout-ms` (watch budget in ms; `0` = single immediate idle check, no watch); timeouts and intervals are otherwise derived from `packages/core/src/loop/policy-constants.mjs`.
|
|
192
192
|
|
|
193
193
|
### Outer-loop checkpoint: canonical re-attachment artifact
|
|
194
194
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Cross-Harness Regression Contract
|
|
2
|
+
|
|
3
|
+
Canonical owner for harness-agnostic non-regression across Pi and Claude Code.
|
|
4
|
+
|
|
5
|
+
## What counts as a harness-specific change
|
|
6
|
+
|
|
7
|
+
A change is harness-specific when it touches a seam that branches by runtime harness (Pi vs Claude Code), including:
|
|
8
|
+
|
|
9
|
+
- Runner coordination (`scripts/loop/pr-runner-coordination.mjs`, `scripts/loop/_pr-runner-coordination.mjs`)
|
|
10
|
+
- Run-id injection / async-start semantics (`packages/core/src/loop/async-start-contract.mjs`, `packages/core/src/loop/run-context.mjs`)
|
|
11
|
+
- Subagent / Task fan-out tool provisioning
|
|
12
|
+
- Harness-specific environment variables (`PI_SUBAGENT_RUN_ID`, `DEVLOOPS_RUN_ID`)
|
|
13
|
+
- Session/intercom wiring
|
|
14
|
+
- TUI behavior
|
|
15
|
+
- Any capability detection or `process.env` branch whose outcome differs per harness
|
|
16
|
+
|
|
17
|
+
## Required cross-harness test coverage
|
|
18
|
+
|
|
19
|
+
Every harness-specific change MUST add or keep a test that exercises the path of the harness it does not directly target. A pull request that alters behavior on one harness without any test evidence for the other harness fails this contract.
|
|
20
|
+
|
|
21
|
+
## The additive/no-op bar
|
|
22
|
+
|
|
23
|
+
State the bar symmetrically, in both directions:
|
|
24
|
+
|
|
25
|
+
- A change additive on Pi MUST be a no-op on Claude Code, or its effect on Claude Code MUST be explicitly validated by a test.
|
|
26
|
+
- A change additive on Claude Code MUST be a no-op on Pi, or its effect on Pi MUST be explicitly validated by a test.
|
|
27
|
+
|
|
28
|
+
Definitions:
|
|
29
|
+
|
|
30
|
+
- **No-op**: the existing test suite for the other harness passes unmodified — no assertion needed to change, no new test required, because the seam does not fire for that harness.
|
|
31
|
+
- **Explicitly validated**: a new or updated test directly asserts the other harness's resulting behavior (not merely "did not throw"), covering both the case where the seam is inert and the case where it participates.
|
|
32
|
+
|
|
33
|
+
## Claude-Code-path test inventory
|
|
34
|
+
|
|
35
|
+
When a change targets a Claude-Code-specific seam, the following suites are the baseline that MUST stay green, and are the first place to add coverage for a Pi-originated change that must not regress Claude Code:
|
|
36
|
+
|
|
37
|
+
- `npm run test:assets` — runs `test/contracts/*.test.mjs`, including `test/contracts/claude-assets-reproducible.test.mjs`, `test/contracts/claude-headless-smoke.test.mjs`, `test/contracts/claude-plugin-manifest.test.mjs`, `test/contracts/claude-plugin-marketplace.test.mjs`, `test/contracts/claude-hooks-settings.test.mjs`, and `test/contracts/cli-harness-agnostic.test.mjs`
|
|
38
|
+
- `npm run smoke:headless` — runs `scripts/claude/headless-info-smoke.mjs`; this is a manual/local-only check, not part of `npm test` or `npm run verify` and not run by CI — run it directly when a change touches Claude Code headless-info behavior
|
|
39
|
+
- `npm run test:core` — runs `packages/core/test/*.test.mjs`, including `packages/core/test/claude-headless-entry.test.mjs`, `packages/core/test/claude-hook-decisions.test.mjs`, `packages/core/test/run-context.test.mjs`, `packages/core/test/async-start-contract.test.mjs`
|
|
40
|
+
|
|
41
|
+
Symmetrically, for a change targeting the Claude-Code-specific seam that must not regress Pi, `npm run test:extension` (which runs `test/extension-*.test.mjs`, including `test/extension-pi-adapter.test.mjs`, the Pi-side adapter inventory) is the baseline suite to check and extend.
|
|
42
|
+
|
|
43
|
+
## How to add coverage
|
|
44
|
+
|
|
45
|
+
1. Identify the harness-specific seam the change touches (see the surface list above).
|
|
46
|
+
2. Locate the existing test file that already covers that seam for the harness being changed, and its counterpart for the other harness (from the inventory above, or the nearest analogous file).
|
|
47
|
+
3. Add or extend a test asserting the other harness's behavior directly — do not rely on an untested assumption that the change is inert there.
|
|
48
|
+
4. Run the relevant suite locally (`npm run test:assets`, `npm run test:core`, `npm run test:extension`, or `npm run smoke:headless` as applicable) before opening the pull request.
|
|
49
|
+
|
|
50
|
+
## Non-goals
|
|
51
|
+
|
|
52
|
+
- No new or expanded CI matrix: `npm run verify` (which CI runs on every pull request) already covers `test:assets`, `test:extension`, `test:scripts`, `test:core`, `test:docs`, and `test:dev-loop` — every suite named above except `npm run smoke:headless`, which is manual/local-only by design (see the test inventory above).
|
|
53
|
+
- No bespoke cross-harness enforcement script: naming the required suites here, combined with CI already running them on every pull request, satisfies the coverage bar.
|
|
54
|
+
- No rewriting of existing harness-specific code as part of adopting this contract.
|
|
55
|
+
|
|
56
|
+
## Cross-references
|
|
57
|
+
|
|
58
|
+
- [AGENTS.md](../../AGENTS.md)
|
|
59
|
+
- [Dev Loop Skill](../dev-loop/SKILL.md)
|
|
60
|
+
- [Public Dev Loop Contract](public-dev-loop-contract.md)
|
|
@@ -111,6 +111,7 @@ Preflight verdicts:
|
|
|
111
111
|
- do not rely only on PR title/body containing a literal issue number
|
|
112
112
|
- treat an open linked PR as the active implementation for this issue
|
|
113
113
|
- once an open linked PR exists, that PR is the only canonical follow-up artifact for the issue; attach follow-up work to it and do not open another PR unless the prior PR was explicitly superseded and reconciled first
|
|
114
|
+
- **follow-up-capture rule:** when a follow-up is discovered while working a PR/loop, note it on the originating issue (or the PR body); file a standalone issue only if the follow-up is genuinely independent of the PR **and** outlives it (a real separate bug/feature that would be lost as a note on a soon-closed issue). Prefer noting PR-scoped follow-ups on the originating artifact over spinning up standalone tracker items for work that is part of the same effort or will be resolved imminently. See [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md).
|
|
114
115
|
- if a PR already exists, classify bootstrap-wait versus follow-up:
|
|
115
116
|
`node <resolved-skill-scripts>/loop/detect-initial-copilot-pr-state.mjs --repo <resolved-repo> --issue <number>`
|
|
116
117
|
- `waiting_for_initial_copilot_implementation`: keep waiting; in durable-auto mode use:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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
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 `
|
|
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 `docs/phases/`.
|
|
6
6
|
|
|
7
7
|
## Stages
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
One plan file, `docs/phases/phase-42.md`, carried through every stage of the [Local-Planning Flow](local-planning-flow.md): authored, validated, refined in place, held at the local human-review checkpoint, then promoted to a draft PR. Each stage below shows the same file's content as it evolves. The contract details live in the [Artifact Authority Contract](artifact-authority-contract.md); this is a concrete trace of one file.
|
|
4
4
|
|
|
5
|
-
The repo runs local-planning by default (`strategy.default: local-first`), and the plan lives under `
|
|
5
|
+
The repo runs local-planning by default (`strategy.default: local-first`), and the plan lives under `docs/phases/`.
|
|
6
6
|
|
|
7
7
|
## Stage 1 — Authored
|
|
8
8
|
|
|
@@ -47,6 +47,22 @@ doing manually; it is not a general conflict-resolution engine.
|
|
|
47
47
|
6. ✅ PR body contains `Closes #N` or `Fixes #N`
|
|
48
48
|
7. ✅ PR **title** free of merge-blocking markers — `WIP`, `[WIP]`, `DRAFT`, `DO NOT MERGE`, `🚧` (case-insensitive)
|
|
49
49
|
|
|
50
|
+
> Runner-coordination lock: the pre-merge evidence check fails closed on a stale/foreign runner claim for the PR. A completing run releases its claim best-effort at every terminal stop (including the human approval checkpoint), so a merge re-dispatch normally proceeds. If a lock held by a completed/dead run still blocks the merge, take it over explicitly with `node <resolved-skill-scripts>/loop/pr-runner-coordination.mjs takeover --repo <owner/name> --pr <number>`. Never take over a genuinely active (non-stale) run — that fail-closed block is intentional.
|
|
51
|
+
|
|
52
|
+
### Evidence writes and `gh pr merge` MUST be separate tool calls (#1172)
|
|
53
|
+
|
|
54
|
+
The PreToolUse Bash gate evaluates `gh pr merge` **before** the Bash tool call executes. A compound
|
|
55
|
+
command that both writes gate evidence (the findings-log ledger, `upsert-checkpoint-verdict`) and
|
|
56
|
+
merges in the same call is blocked at hook-evaluation time — the write never runs. This can look
|
|
57
|
+
like the ledger "vanished" between writing and merging; it was never written. The block message
|
|
58
|
+
names this when it detects an evidence-writing invocation in the same command string.
|
|
59
|
+
|
|
60
|
+
Documented pattern — **write, verify, then merge alone**:
|
|
61
|
+
|
|
62
|
+
1. Write the gate evidence (findings-log ledger / checkpoint verdict) in its own Bash call.
|
|
63
|
+
2. Verify it landed (e.g. `ls tmp/gate-findings/<slug>/pr-<n>/`) in a separate call.
|
|
64
|
+
3. Run `gh pr merge` alone, with no other command chained via `&&`/`;`/newline.
|
|
65
|
+
|
|
50
66
|
## Title markers
|
|
51
67
|
|
|
52
68
|
The PR title is a contract surface, so a merge-blocking marker in the title is enforced
|
|
@@ -89,7 +105,7 @@ explicit instruction can unlock.
|
|
|
89
105
|
When a repo sets `approval.humanHandoff.enabled: true` in `.devloops`, the loop
|
|
90
106
|
does not just park silently at the human-merge stop — at the
|
|
91
107
|
`pre_approval_gate` / `waiting_for_merge_authorization` boundary it **offers**
|
|
92
|
-
to route the PR to a
|
|
108
|
+
to route the PR to a contributor (pairs with `autonomy.humanMergeOnly`: when
|
|
93
109
|
human-merge is enforced, the handoff names who should take it). Disabled by
|
|
94
110
|
default; no candidate sourcing when disabled.
|
|
95
111
|
|
|
@@ -4,7 +4,7 @@ A plan file is the persisted markdown artifact that drives work in local-plannin
|
|
|
4
4
|
|
|
5
5
|
## Format
|
|
6
6
|
|
|
7
|
-
A plan file is a markdown document whose sections are level-2 (`##`) headings. It follows the same shape as the phase docs already committed under `docs/phases
|
|
7
|
+
A plan file is a markdown document whose sections are level-2 (`##`) headings. It follows the same shape as the phase docs already committed under `docs/phases/`, which is also the directory that holds plan files.
|
|
8
8
|
|
|
9
9
|
## Base authoring sections
|
|
10
10
|
|
|
@@ -437,7 +437,7 @@ Any conflict that would materially change artifact identity, ownership truth, or
|
|
|
437
437
|
| `mode` | `bounded_handoff` (default) \| `durable_auto` | Steers execution mode; `durable_auto` uses the same durable-auto execution-mode semantics as `auto_continue_current`, without replacing the selected intent |
|
|
438
438
|
| `watch` | boolean | Explicitly request wait/watch semantics; fails closed for otherwise-successful non-wait routed results, while preserving authoritative `stop` and `needs_reconcile` outcomes |
|
|
439
439
|
| `intent` | any existing public `dev-loop` intent | Disambiguates the supported public intent; maps to existing contract values |
|
|
440
|
-
| `targetPreference` | `
|
|
440
|
+
| `targetPreference` | `prefer_local` (default) \| `prefer_github_first` | Steers routing preference; must not override authoritative linked-PR or active-artifact truth |
|
|
441
441
|
|
|
442
442
|
The bounded allow-list is exported from `packages/core/src/loop/public-dev-loop-routing.mjs` as `DEV_LOOP_VARIATION_PARAMETER_CONTRACT`.
|
|
443
443
|
|