@deftai/directive-core 0.88.0 → 0.89.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.
- package/dist/authz/actions.d.ts +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +160 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +21 -18
- package/dist/hooks/dispatcher.js +212 -163
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.js +27 -8
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +10 -3
- package/dist/init-deposit/scaffold.js +35 -26
- package/dist/init-deposit/xbrief-projections.js +11 -4
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.js +19 -6
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.js +31 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.js +10 -3
- package/dist/policy/org-force-on-migration.js +9 -5
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +15 -2
- package/dist/policy/runtime-authority.js +23 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start.js +7 -0
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +14 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +15 -3
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI entry for pr:finish-loop and directive:finish-loop (#871).
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { runDirectiveFinishLoop } from "./directive-finish-loop.js";
|
|
7
|
+
import { runPrFinishLoop } from "./pr-finish-loop.js";
|
|
8
|
+
import { EXIT_BLOCKED, EXIT_OK } from "./types.js";
|
|
9
|
+
const PR_HELP = "usage: task pr:finish-loop -- <pr_number> [options]\n" +
|
|
10
|
+
"\n" +
|
|
11
|
+
"Walk-away PR finish loop (#871 Wave 5). Requires a finish-loop human-origin\n" +
|
|
12
|
+
"grant (`deft authz:grant -- --template finish-loop`) or DEFT_ALLOW_FINISH_LOOP=1.\n" +
|
|
13
|
+
"Polls via pr:watch until CLEAN. NEW_P0_P1 → exit 1 (agent address path).\n" +
|
|
14
|
+
"Respects plan.policy.requireHumanMerge (never force bot merge).\n" +
|
|
15
|
+
"\n" +
|
|
16
|
+
"options:\n" +
|
|
17
|
+
" -h, --help Show this help\n" +
|
|
18
|
+
" --json Emit structured JSON\n" +
|
|
19
|
+
" --one-shot Single pr:watch probe\n" +
|
|
20
|
+
" --merge Attempt merge when CLEAN and policy allows\n" +
|
|
21
|
+
" --max-wait-minutes N pr:watch cap (default 30)\n" +
|
|
22
|
+
" --poll-seconds N pr:watch interval (default 90)\n" +
|
|
23
|
+
" --repo OWNER/REPO Override repository\n" +
|
|
24
|
+
" --project-root PATH Project root (default cwd)\n" +
|
|
25
|
+
"\n" +
|
|
26
|
+
"exit codes:\n" +
|
|
27
|
+
" 0 CLEAN / MERGED\n" +
|
|
28
|
+
" 1 ACTION_REQUIRED (NEW_P0_P1 address, or require-human-merge)\n" +
|
|
29
|
+
" 2 BLOCKED (no grant) / watch error / timeout\n";
|
|
30
|
+
const DIRECTIVE_HELP = "usage: task directive:finish-loop -- [options]\n" +
|
|
31
|
+
"\n" +
|
|
32
|
+
"Outer walk-away cascade (#871 Wave 5). Gates on finish-loop grant, scans\n" +
|
|
33
|
+
"xbrief active/pending queue, logs `.deft-cache/finish-loop-progress.jsonl`,\n" +
|
|
34
|
+
"optionally shepherds a PR via pr:finish-loop. Implementation steps are\n" +
|
|
35
|
+
"agent-orchestrated (exit 1 AGENT_STEP) — full autonomous coding is not\n" +
|
|
36
|
+
"inlined in the CLI.\n" +
|
|
37
|
+
"\n" +
|
|
38
|
+
"Mint once, walk away:\n" +
|
|
39
|
+
" deft authz:grant -- --template finish-loop\n" +
|
|
40
|
+
" task directive:finish-loop --\n" +
|
|
41
|
+
"\n" +
|
|
42
|
+
"options:\n" +
|
|
43
|
+
" -h, --help Show this help\n" +
|
|
44
|
+
" --json Emit structured JSON\n" +
|
|
45
|
+
" --pr N Shepherd PR N via pr:finish-loop this run\n" +
|
|
46
|
+
" --merge Pass --merge to pr:finish-loop\n" +
|
|
47
|
+
" --one-shot Single pr:watch probe\n" +
|
|
48
|
+
" --max-iterations N Outer loop cap (default 20)\n" +
|
|
49
|
+
" --max-wait-minutes N pr:watch cap\n" +
|
|
50
|
+
" --poll-seconds N pr:watch interval\n" +
|
|
51
|
+
" --repo OWNER/REPO Override repository\n" +
|
|
52
|
+
" --project-root PATH Project root (default cwd)\n" +
|
|
53
|
+
"\n" +
|
|
54
|
+
"exit codes:\n" +
|
|
55
|
+
" 0 empty queue / clean complete\n" +
|
|
56
|
+
" 1 AGENT_STEP / address findings / require-human-merge\n" +
|
|
57
|
+
" 2 BLOCKED (grant/gate/max-iterations/error)\n";
|
|
58
|
+
function takePositive(label, raw) {
|
|
59
|
+
if (raw === undefined)
|
|
60
|
+
return { error: `argument ${label}: expected one argument` };
|
|
61
|
+
const parsed = Number(raw);
|
|
62
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
63
|
+
return { error: `invalid ${label} value: ${raw}` };
|
|
64
|
+
}
|
|
65
|
+
return { value: parsed };
|
|
66
|
+
}
|
|
67
|
+
export function parsePrFinishArgs(argv) {
|
|
68
|
+
const acc = {
|
|
69
|
+
prNumber: null,
|
|
70
|
+
repo: null,
|
|
71
|
+
projectRoot: process.cwd(),
|
|
72
|
+
maxWaitMinutes: null,
|
|
73
|
+
pollSeconds: null,
|
|
74
|
+
oneShot: false,
|
|
75
|
+
merge: false,
|
|
76
|
+
emitJson: false,
|
|
77
|
+
help: false,
|
|
78
|
+
};
|
|
79
|
+
let prNumber = null;
|
|
80
|
+
let repo = null;
|
|
81
|
+
let projectRoot = process.cwd();
|
|
82
|
+
let maxWaitMinutes = null;
|
|
83
|
+
let pollSeconds = null;
|
|
84
|
+
let oneShot = false;
|
|
85
|
+
let merge = false;
|
|
86
|
+
let emitJson = false;
|
|
87
|
+
let help = false;
|
|
88
|
+
const args = [...argv];
|
|
89
|
+
while (args[0] === "--")
|
|
90
|
+
args.shift();
|
|
91
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
92
|
+
const arg = args[i];
|
|
93
|
+
if (arg === undefined)
|
|
94
|
+
break;
|
|
95
|
+
if (arg === "--help" || arg === "-h") {
|
|
96
|
+
help = true;
|
|
97
|
+
}
|
|
98
|
+
else if (arg === "--json") {
|
|
99
|
+
emitJson = true;
|
|
100
|
+
}
|
|
101
|
+
else if (arg === "--one-shot") {
|
|
102
|
+
oneShot = true;
|
|
103
|
+
}
|
|
104
|
+
else if (arg === "--merge") {
|
|
105
|
+
merge = true;
|
|
106
|
+
}
|
|
107
|
+
else if (arg === "--repo") {
|
|
108
|
+
const value = args[++i];
|
|
109
|
+
if (value === undefined)
|
|
110
|
+
return { ...acc, error: "argument --repo: expected one argument" };
|
|
111
|
+
repo = value;
|
|
112
|
+
}
|
|
113
|
+
else if (arg.startsWith("--repo=")) {
|
|
114
|
+
repo = arg.slice("--repo=".length);
|
|
115
|
+
}
|
|
116
|
+
else if (arg === "--project-root") {
|
|
117
|
+
const value = args[++i];
|
|
118
|
+
if (value === undefined) {
|
|
119
|
+
return { ...acc, error: "argument --project-root: expected one argument" };
|
|
120
|
+
}
|
|
121
|
+
projectRoot = value;
|
|
122
|
+
}
|
|
123
|
+
else if (arg.startsWith("--project-root=")) {
|
|
124
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
125
|
+
}
|
|
126
|
+
else if (arg === "--max-wait-minutes") {
|
|
127
|
+
const r = takePositive("--max-wait-minutes", args[++i]);
|
|
128
|
+
if ("error" in r)
|
|
129
|
+
return { ...acc, error: r.error };
|
|
130
|
+
maxWaitMinutes = r.value;
|
|
131
|
+
}
|
|
132
|
+
else if (arg === "--poll-seconds") {
|
|
133
|
+
const r = takePositive("--poll-seconds", args[++i]);
|
|
134
|
+
if ("error" in r)
|
|
135
|
+
return { ...acc, error: r.error };
|
|
136
|
+
pollSeconds = r.value;
|
|
137
|
+
}
|
|
138
|
+
else if (arg.startsWith("-")) {
|
|
139
|
+
return { ...acc, error: `unrecognized arguments: ${arg}` };
|
|
140
|
+
}
|
|
141
|
+
else if (prNumber === null) {
|
|
142
|
+
const n = Number(arg);
|
|
143
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
144
|
+
return { ...acc, error: `invalid PR number: ${arg}` };
|
|
145
|
+
}
|
|
146
|
+
prNumber = n;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return { ...acc, error: `unrecognized arguments: ${arg}` };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (help) {
|
|
153
|
+
return {
|
|
154
|
+
prNumber,
|
|
155
|
+
repo,
|
|
156
|
+
projectRoot,
|
|
157
|
+
maxWaitMinutes,
|
|
158
|
+
pollSeconds,
|
|
159
|
+
oneShot,
|
|
160
|
+
merge,
|
|
161
|
+
emitJson,
|
|
162
|
+
help,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (prNumber === null) {
|
|
166
|
+
return { ...acc, error: "the following arguments are required: pr_number" };
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
prNumber,
|
|
170
|
+
repo,
|
|
171
|
+
projectRoot,
|
|
172
|
+
maxWaitMinutes,
|
|
173
|
+
pollSeconds,
|
|
174
|
+
oneShot,
|
|
175
|
+
merge,
|
|
176
|
+
emitJson,
|
|
177
|
+
help,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export function parseDirectiveFinishArgs(argv) {
|
|
181
|
+
const acc = {
|
|
182
|
+
prNumber: null,
|
|
183
|
+
repo: null,
|
|
184
|
+
projectRoot: process.cwd(),
|
|
185
|
+
maxIterations: null,
|
|
186
|
+
maxWaitMinutes: null,
|
|
187
|
+
pollSeconds: null,
|
|
188
|
+
oneShot: false,
|
|
189
|
+
merge: false,
|
|
190
|
+
emitJson: false,
|
|
191
|
+
help: false,
|
|
192
|
+
};
|
|
193
|
+
let prNumber = null;
|
|
194
|
+
let repo = null;
|
|
195
|
+
let projectRoot = process.cwd();
|
|
196
|
+
let maxIterations = null;
|
|
197
|
+
let maxWaitMinutes = null;
|
|
198
|
+
let pollSeconds = null;
|
|
199
|
+
let oneShot = false;
|
|
200
|
+
let merge = false;
|
|
201
|
+
let emitJson = false;
|
|
202
|
+
let help = false;
|
|
203
|
+
const args = [...argv];
|
|
204
|
+
while (args[0] === "--")
|
|
205
|
+
args.shift();
|
|
206
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
207
|
+
const arg = args[i];
|
|
208
|
+
if (arg === undefined)
|
|
209
|
+
break;
|
|
210
|
+
if (arg === "--help" || arg === "-h") {
|
|
211
|
+
help = true;
|
|
212
|
+
}
|
|
213
|
+
else if (arg === "--json") {
|
|
214
|
+
emitJson = true;
|
|
215
|
+
}
|
|
216
|
+
else if (arg === "--one-shot") {
|
|
217
|
+
oneShot = true;
|
|
218
|
+
}
|
|
219
|
+
else if (arg === "--merge") {
|
|
220
|
+
merge = true;
|
|
221
|
+
}
|
|
222
|
+
else if (arg === "--pr") {
|
|
223
|
+
const r = takePositive("--pr", args[++i]);
|
|
224
|
+
if ("error" in r)
|
|
225
|
+
return { ...acc, error: r.error };
|
|
226
|
+
prNumber = r.value;
|
|
227
|
+
}
|
|
228
|
+
else if (arg.startsWith("--pr=")) {
|
|
229
|
+
const r = takePositive("--pr", arg.slice("--pr=".length));
|
|
230
|
+
if ("error" in r)
|
|
231
|
+
return { ...acc, error: r.error };
|
|
232
|
+
prNumber = r.value;
|
|
233
|
+
}
|
|
234
|
+
else if (arg === "--repo") {
|
|
235
|
+
const value = args[++i];
|
|
236
|
+
if (value === undefined)
|
|
237
|
+
return { ...acc, error: "argument --repo: expected one argument" };
|
|
238
|
+
repo = value;
|
|
239
|
+
}
|
|
240
|
+
else if (arg.startsWith("--repo=")) {
|
|
241
|
+
repo = arg.slice("--repo=".length);
|
|
242
|
+
}
|
|
243
|
+
else if (arg === "--project-root") {
|
|
244
|
+
const value = args[++i];
|
|
245
|
+
if (value === undefined) {
|
|
246
|
+
return { ...acc, error: "argument --project-root: expected one argument" };
|
|
247
|
+
}
|
|
248
|
+
projectRoot = value;
|
|
249
|
+
}
|
|
250
|
+
else if (arg.startsWith("--project-root=")) {
|
|
251
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
252
|
+
}
|
|
253
|
+
else if (arg === "--max-iterations") {
|
|
254
|
+
const r = takePositive("--max-iterations", args[++i]);
|
|
255
|
+
if ("error" in r)
|
|
256
|
+
return { ...acc, error: r.error };
|
|
257
|
+
maxIterations = r.value;
|
|
258
|
+
}
|
|
259
|
+
else if (arg === "--max-wait-minutes") {
|
|
260
|
+
const r = takePositive("--max-wait-minutes", args[++i]);
|
|
261
|
+
if ("error" in r)
|
|
262
|
+
return { ...acc, error: r.error };
|
|
263
|
+
maxWaitMinutes = r.value;
|
|
264
|
+
}
|
|
265
|
+
else if (arg === "--poll-seconds") {
|
|
266
|
+
const r = takePositive("--poll-seconds", args[++i]);
|
|
267
|
+
if ("error" in r)
|
|
268
|
+
return { ...acc, error: r.error };
|
|
269
|
+
pollSeconds = r.value;
|
|
270
|
+
}
|
|
271
|
+
else if (arg.startsWith("-")) {
|
|
272
|
+
return { ...acc, error: `unrecognized arguments: ${arg}` };
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
return { ...acc, error: `unrecognized arguments: ${arg}` };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
prNumber,
|
|
280
|
+
repo,
|
|
281
|
+
projectRoot,
|
|
282
|
+
maxIterations,
|
|
283
|
+
maxWaitMinutes,
|
|
284
|
+
pollSeconds,
|
|
285
|
+
oneShot,
|
|
286
|
+
merge,
|
|
287
|
+
emitJson,
|
|
288
|
+
help,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
export function formatPrFinishHelp() {
|
|
292
|
+
return PR_HELP;
|
|
293
|
+
}
|
|
294
|
+
export function formatDirectiveFinishHelp() {
|
|
295
|
+
return DIRECTIVE_HELP;
|
|
296
|
+
}
|
|
297
|
+
export function cmdPrFinishLoop(argv = process.argv.slice(2)) {
|
|
298
|
+
const args = parsePrFinishArgs(argv);
|
|
299
|
+
if (args.help) {
|
|
300
|
+
process.stdout.write(formatPrFinishHelp());
|
|
301
|
+
return EXIT_OK;
|
|
302
|
+
}
|
|
303
|
+
if (args.error !== undefined) {
|
|
304
|
+
process.stderr.write(`pr:finish-loop: ${args.error}\n`);
|
|
305
|
+
process.stderr.write("Try: task pr:finish-loop -- --help\n");
|
|
306
|
+
return EXIT_BLOCKED;
|
|
307
|
+
}
|
|
308
|
+
const root = resolve(args.projectRoot);
|
|
309
|
+
if (!existsSync(root)) {
|
|
310
|
+
process.stderr.write(`pr:finish-loop: --project-root does not exist: ${root}\n`);
|
|
311
|
+
return EXIT_BLOCKED;
|
|
312
|
+
}
|
|
313
|
+
const result = runPrFinishLoop({
|
|
314
|
+
projectRoot: root,
|
|
315
|
+
prNumber: args.prNumber,
|
|
316
|
+
repo: args.repo,
|
|
317
|
+
maxWaitMinutes: args.maxWaitMinutes ?? undefined,
|
|
318
|
+
pollSeconds: args.pollSeconds ?? undefined,
|
|
319
|
+
oneShot: args.oneShot,
|
|
320
|
+
merge: args.merge,
|
|
321
|
+
});
|
|
322
|
+
if (args.emitJson) {
|
|
323
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
process.stdout.write(`${result.message}\n`);
|
|
327
|
+
}
|
|
328
|
+
return result.exitCode;
|
|
329
|
+
}
|
|
330
|
+
export function cmdDirectiveFinishLoop(argv = process.argv.slice(2)) {
|
|
331
|
+
const args = parseDirectiveFinishArgs(argv);
|
|
332
|
+
if (args.help) {
|
|
333
|
+
process.stdout.write(formatDirectiveFinishHelp());
|
|
334
|
+
return EXIT_OK;
|
|
335
|
+
}
|
|
336
|
+
if (args.error !== undefined) {
|
|
337
|
+
process.stderr.write(`directive:finish-loop: ${args.error}\n`);
|
|
338
|
+
process.stderr.write("Try: task directive:finish-loop -- --help\n");
|
|
339
|
+
return EXIT_BLOCKED;
|
|
340
|
+
}
|
|
341
|
+
const root = resolve(args.projectRoot);
|
|
342
|
+
if (!existsSync(root)) {
|
|
343
|
+
process.stderr.write(`directive:finish-loop: --project-root does not exist: ${root}\n`);
|
|
344
|
+
return EXIT_BLOCKED;
|
|
345
|
+
}
|
|
346
|
+
const result = runDirectiveFinishLoop({
|
|
347
|
+
projectRoot: root,
|
|
348
|
+
prNumber: args.prNumber,
|
|
349
|
+
repo: args.repo,
|
|
350
|
+
maxIterations: args.maxIterations ?? undefined,
|
|
351
|
+
maxWaitMinutes: args.maxWaitMinutes ?? undefined,
|
|
352
|
+
pollSeconds: args.pollSeconds ?? undefined,
|
|
353
|
+
oneShot: args.oneShot,
|
|
354
|
+
merge: args.merge,
|
|
355
|
+
});
|
|
356
|
+
if (args.emitJson) {
|
|
357
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
process.stdout.write(`${result.message}\n`);
|
|
361
|
+
process.stdout.write(` progress: ${result.progressPath}\n`);
|
|
362
|
+
}
|
|
363
|
+
return result.exitCode;
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pr:finish-loop — grant-gated PR watch until CLEAN (#871 Wave 5).
|
|
3
|
+
*
|
|
4
|
+
* - Polls via pr:watch until CLEAN / NEW_P0_P1 / terminal error
|
|
5
|
+
* - NEW_P0_P1 → exit ACTION_REQUIRED (address path is agent-orchestrated)
|
|
6
|
+
* - CLEAN + requireHumanMerge → halt with require-human-merge (do not force bot merge)
|
|
7
|
+
* - CLEAN + bot merge allowed + --merge → optional wait-mergeable-and-merge
|
|
8
|
+
* - Missing grants → fail closed BLOCKED
|
|
9
|
+
*/
|
|
10
|
+
import { evaluateAgentMerge } from "../policy/require-human-merge.js";
|
|
11
|
+
import type { WatchOptions, WatchResult } from "../pr-watch/types.js";
|
|
12
|
+
import { type PrFinishLoopResult } from "./types.js";
|
|
13
|
+
export interface PrFinishLoopOptions {
|
|
14
|
+
readonly projectRoot: string;
|
|
15
|
+
readonly prNumber: number;
|
|
16
|
+
readonly repo?: string | null;
|
|
17
|
+
readonly maxWaitMinutes?: number;
|
|
18
|
+
readonly pollSeconds?: number;
|
|
19
|
+
readonly oneShot?: boolean;
|
|
20
|
+
/** Attempt merge when CLEAN and policy allows (default false). */
|
|
21
|
+
readonly merge?: boolean;
|
|
22
|
+
readonly skipGrantGate?: boolean;
|
|
23
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
24
|
+
readonly now?: Date;
|
|
25
|
+
/** Inject watch for tests. */
|
|
26
|
+
readonly watchFn?: (prNumber: number, repo: string | null, options?: WatchOptions) => WatchResult;
|
|
27
|
+
/** Inject merge cascade for tests; returns exit code. */
|
|
28
|
+
readonly mergeFn?: (prNumber: number, repo: string | null) => number;
|
|
29
|
+
readonly agentMergeFn?: typeof evaluateAgentMerge;
|
|
30
|
+
readonly writeProgress?: boolean;
|
|
31
|
+
readonly iteration?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function runPrFinishLoop(options: PrFinishLoopOptions): PrFinishLoopResult;
|
|
34
|
+
//# sourceMappingURL=pr-finish-loop.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pr:finish-loop — grant-gated PR watch until CLEAN (#871 Wave 5).
|
|
3
|
+
*
|
|
4
|
+
* - Polls via pr:watch until CLEAN / NEW_P0_P1 / terminal error
|
|
5
|
+
* - NEW_P0_P1 → exit ACTION_REQUIRED (address path is agent-orchestrated)
|
|
6
|
+
* - CLEAN + requireHumanMerge → halt with require-human-merge (do not force bot merge)
|
|
7
|
+
* - CLEAN + bot merge allowed + --merge → optional wait-mergeable-and-merge
|
|
8
|
+
* - Missing grants → fail closed BLOCKED
|
|
9
|
+
*/
|
|
10
|
+
import { evaluateAgentMerge } from "../policy/require-human-merge.js";
|
|
11
|
+
import { EXIT_CLEAN, EXIT_NEW_P0_P1, EXIT_TERMINAL_ERROR, VERDICT_CLEAN, VERDICT_NEW_P0_P1, } from "../pr-watch/constants.js";
|
|
12
|
+
import { watch } from "../pr-watch/watch.js";
|
|
13
|
+
import { evaluateFinishLoopGrant } from "./grant-gate.js";
|
|
14
|
+
import { appendFinishLoopProgress, makeProgressLine } from "./progress.js";
|
|
15
|
+
import { EXIT_ACTION_REQUIRED, EXIT_BLOCKED, EXIT_OK } from "./types.js";
|
|
16
|
+
export function runPrFinishLoop(options) {
|
|
17
|
+
const projectRoot = options.projectRoot;
|
|
18
|
+
const prNumber = options.prNumber;
|
|
19
|
+
const iteration = options.iteration ?? 0;
|
|
20
|
+
const writeProgress = options.writeProgress !== false;
|
|
21
|
+
const log = (phase, haltReason, message, extra) => {
|
|
22
|
+
if (!writeProgress)
|
|
23
|
+
return;
|
|
24
|
+
appendFinishLoopProgress(projectRoot, makeProgressLine({
|
|
25
|
+
phase,
|
|
26
|
+
iteration,
|
|
27
|
+
haltReason,
|
|
28
|
+
message,
|
|
29
|
+
prNumber,
|
|
30
|
+
grantId: null,
|
|
31
|
+
queueCount: null,
|
|
32
|
+
exitCode: null,
|
|
33
|
+
extra,
|
|
34
|
+
}));
|
|
35
|
+
};
|
|
36
|
+
// --- Grant gate ---
|
|
37
|
+
if (options.skipGrantGate !== true) {
|
|
38
|
+
const gate = evaluateFinishLoopGrant({
|
|
39
|
+
projectRoot,
|
|
40
|
+
env: options.env,
|
|
41
|
+
now: options.now,
|
|
42
|
+
});
|
|
43
|
+
if (!gate.allowed) {
|
|
44
|
+
const message = `BLOCKED pr:finish-loop: ${gate.reason}`;
|
|
45
|
+
log("gate", "grant-deny", message, { code: gate.code });
|
|
46
|
+
if (writeProgress) {
|
|
47
|
+
appendFinishLoopProgress(projectRoot, makeProgressLine({
|
|
48
|
+
phase: "halt",
|
|
49
|
+
iteration,
|
|
50
|
+
haltReason: gate.code === "authz-grant-expired" ? "grant-expired" : "grant-missing",
|
|
51
|
+
message,
|
|
52
|
+
prNumber,
|
|
53
|
+
grantId: gate.grantId,
|
|
54
|
+
queueCount: null,
|
|
55
|
+
exitCode: EXIT_BLOCKED,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
exitCode: EXIT_BLOCKED,
|
|
60
|
+
haltReason: gate.code === "authz-grant-expired" ? "grant-expired" : "grant-missing",
|
|
61
|
+
message,
|
|
62
|
+
prNumber,
|
|
63
|
+
watchVerdict: null,
|
|
64
|
+
mergeAttempted: false,
|
|
65
|
+
mergeSkippedReason: null,
|
|
66
|
+
grantId: gate.grantId,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
log("gate", null, `grant ok: ${gate.reason}`, { grantId: gate.grantId });
|
|
70
|
+
}
|
|
71
|
+
// --- pr:watch ---
|
|
72
|
+
const watchFn = options.watchFn ?? watch;
|
|
73
|
+
const repo = options.repo ?? process.env.GH_REPO ?? null;
|
|
74
|
+
let watchResult;
|
|
75
|
+
try {
|
|
76
|
+
watchResult = watchFn(prNumber, repo, {
|
|
77
|
+
maxWaitMinutes: options.maxWaitMinutes,
|
|
78
|
+
pollSeconds: options.pollSeconds,
|
|
79
|
+
oneShot: options.oneShot === true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const message = `pr:finish-loop watch error: ${err instanceof Error ? err.message : String(err)}`;
|
|
84
|
+
log("pr-watch", "error", message);
|
|
85
|
+
return {
|
|
86
|
+
exitCode: EXIT_BLOCKED,
|
|
87
|
+
haltReason: "error",
|
|
88
|
+
message,
|
|
89
|
+
prNumber,
|
|
90
|
+
watchVerdict: null,
|
|
91
|
+
mergeAttempted: false,
|
|
92
|
+
mergeSkippedReason: null,
|
|
93
|
+
grantId: null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const verdict = watchResult.verdict;
|
|
97
|
+
log("pr-watch", null, `pr:watch verdict=${verdict}`, {
|
|
98
|
+
exitCode: watchResult.exitCode,
|
|
99
|
+
});
|
|
100
|
+
if (watchResult.exitCode === EXIT_NEW_P0_P1 || verdict === VERDICT_NEW_P0_P1) {
|
|
101
|
+
const message = `pr:finish-loop ACTION_REQUIRED: NEW_P0_P1 on PR #${prNumber}. ` +
|
|
102
|
+
"Address path is agent-orchestrated: fix findings, push, re-run " +
|
|
103
|
+
"`task pr:finish-loop -- <N>` (or review-cycle skill).";
|
|
104
|
+
log("address", "address-findings", message);
|
|
105
|
+
return {
|
|
106
|
+
exitCode: EXIT_ACTION_REQUIRED,
|
|
107
|
+
haltReason: "address-findings",
|
|
108
|
+
message,
|
|
109
|
+
prNumber,
|
|
110
|
+
watchVerdict: verdict,
|
|
111
|
+
mergeAttempted: false,
|
|
112
|
+
mergeSkippedReason: null,
|
|
113
|
+
grantId: null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (watchResult.exitCode !== EXIT_CLEAN && verdict !== VERDICT_CLEAN) {
|
|
117
|
+
const message = `pr:finish-loop BLOCKED: watch exit=${watchResult.exitCode} verdict=${verdict}` +
|
|
118
|
+
(watchResult.probe.error !== null ? ` error=${watchResult.probe.error}` : "");
|
|
119
|
+
log("halt", "error", message);
|
|
120
|
+
return {
|
|
121
|
+
exitCode: EXIT_BLOCKED,
|
|
122
|
+
haltReason: "error",
|
|
123
|
+
message,
|
|
124
|
+
prNumber,
|
|
125
|
+
watchVerdict: verdict,
|
|
126
|
+
mergeAttempted: false,
|
|
127
|
+
mergeSkippedReason: null,
|
|
128
|
+
grantId: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// CLEAN path
|
|
132
|
+
if (options.merge !== true) {
|
|
133
|
+
const message = `pr:finish-loop CLEAN on PR #${prNumber}. Merge not requested ` +
|
|
134
|
+
"(pass --merge to attempt when policy allows). " +
|
|
135
|
+
"When plan.policy.requireHumanMerge is true, a human must merge.";
|
|
136
|
+
log("halt", "clean", message);
|
|
137
|
+
return {
|
|
138
|
+
exitCode: EXIT_OK,
|
|
139
|
+
haltReason: "clean",
|
|
140
|
+
message,
|
|
141
|
+
prNumber,
|
|
142
|
+
watchVerdict: VERDICT_CLEAN,
|
|
143
|
+
mergeAttempted: false,
|
|
144
|
+
mergeSkippedReason: "merge-not-requested",
|
|
145
|
+
grantId: null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Respect requireHumanMerge — never force bot merge
|
|
149
|
+
const agentMergeFn = options.agentMergeFn ?? evaluateAgentMerge;
|
|
150
|
+
const hm = agentMergeFn(projectRoot);
|
|
151
|
+
if (!hm.allowed) {
|
|
152
|
+
const message = `pr:finish-loop CLEAN on PR #${prNumber} but merge blocked by human-merge gate: ` +
|
|
153
|
+
`${hm.message} Human action required: merge in GitHub UI or ` +
|
|
154
|
+
"`deft policy:allow-bot-merge -- --confirm` / DEFT_ALLOW_BOT_MERGE=1.";
|
|
155
|
+
log("merge", "require-human-merge", message);
|
|
156
|
+
return {
|
|
157
|
+
exitCode: EXIT_ACTION_REQUIRED,
|
|
158
|
+
haltReason: "require-human-merge",
|
|
159
|
+
message,
|
|
160
|
+
prNumber,
|
|
161
|
+
watchVerdict: VERDICT_CLEAN,
|
|
162
|
+
mergeAttempted: false,
|
|
163
|
+
mergeSkippedReason: "require-human-merge",
|
|
164
|
+
grantId: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Optional merge when policy allows
|
|
168
|
+
if (options.mergeFn !== undefined) {
|
|
169
|
+
const rc = options.mergeFn(prNumber, repo);
|
|
170
|
+
if (rc === 0) {
|
|
171
|
+
const message = `pr:finish-loop MERGED PR #${prNumber}`;
|
|
172
|
+
log("merge", "merged", message);
|
|
173
|
+
return {
|
|
174
|
+
exitCode: EXIT_OK,
|
|
175
|
+
haltReason: "merged",
|
|
176
|
+
message,
|
|
177
|
+
prNumber,
|
|
178
|
+
watchVerdict: VERDICT_CLEAN,
|
|
179
|
+
mergeAttempted: true,
|
|
180
|
+
mergeSkippedReason: null,
|
|
181
|
+
grantId: null,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const message = `pr:finish-loop merge attempt failed exit=${rc} for PR #${prNumber}`;
|
|
185
|
+
log("merge", "error", message);
|
|
186
|
+
return {
|
|
187
|
+
exitCode: EXIT_BLOCKED,
|
|
188
|
+
haltReason: "error",
|
|
189
|
+
message,
|
|
190
|
+
prNumber,
|
|
191
|
+
watchVerdict: VERDICT_CLEAN,
|
|
192
|
+
mergeAttempted: true,
|
|
193
|
+
mergeSkippedReason: null,
|
|
194
|
+
grantId: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Documented agent/orchestration path when mergeFn not injected:
|
|
198
|
+
// real cascade is task pr:wait-mergeable-and-merge (not inlined here to
|
|
199
|
+
// keep unit tests free of gh).
|
|
200
|
+
const message = `pr:finish-loop CLEAN on PR #${prNumber}; bot merge allowed by policy. ` +
|
|
201
|
+
"Run `task pr:wait-mergeable-and-merge -- <N>` to complete merge cascade, " +
|
|
202
|
+
"or pass an injected mergeFn in automation.";
|
|
203
|
+
log("merge", "clean", message);
|
|
204
|
+
return {
|
|
205
|
+
exitCode: EXIT_OK,
|
|
206
|
+
haltReason: "clean",
|
|
207
|
+
message,
|
|
208
|
+
prNumber,
|
|
209
|
+
watchVerdict: VERDICT_CLEAN,
|
|
210
|
+
mergeAttempted: false,
|
|
211
|
+
mergeSkippedReason: "merge-cascade-documented",
|
|
212
|
+
grantId: null,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// silence unused EXIT_TERMINAL_ERROR import if tree-shaken — keep for docs parity
|
|
216
|
+
void EXIT_TERMINAL_ERROR;
|
|
217
|
+
//# sourceMappingURL=pr-finish-loop.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heartbeat surface for walk-away finish-loop (#871).
|
|
3
|
+
* Append-only JSONL at `.deft-cache/finish-loop-progress.jsonl`.
|
|
4
|
+
*/
|
|
5
|
+
import type { FinishLoopProgressLine } from "./types.js";
|
|
6
|
+
export declare const FINISH_LOOP_PROGRESS_REL: string;
|
|
7
|
+
export declare function finishLoopProgressPath(projectRoot: string): string;
|
|
8
|
+
export declare function utcIso(now?: Date): string;
|
|
9
|
+
export declare function makeProgressLine(partial: Omit<FinishLoopProgressLine, "schemaVersion" | "ts"> & {
|
|
10
|
+
readonly ts?: string;
|
|
11
|
+
}): FinishLoopProgressLine;
|
|
12
|
+
/**
|
|
13
|
+
* Append one progress line. Creates parent dirs via containedWrite.
|
|
14
|
+
* #2951 Phase 2: product write sink routes through containedWrite.
|
|
15
|
+
*/
|
|
16
|
+
export declare function appendFinishLoopProgress(projectRoot: string, line: FinishLoopProgressLine): string;
|
|
17
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heartbeat surface for walk-away finish-loop (#871).
|
|
3
|
+
* Append-only JSONL at `.deft-cache/finish-loop-progress.jsonl`.
|
|
4
|
+
*/
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
7
|
+
export const FINISH_LOOP_PROGRESS_REL = join(".deft-cache", "finish-loop-progress.jsonl");
|
|
8
|
+
export function finishLoopProgressPath(projectRoot) {
|
|
9
|
+
return resolve(projectRoot, FINISH_LOOP_PROGRESS_REL);
|
|
10
|
+
}
|
|
11
|
+
export function utcIso(now) {
|
|
12
|
+
return (now ?? new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13
|
+
}
|
|
14
|
+
export function makeProgressLine(partial) {
|
|
15
|
+
return {
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
ts: partial.ts ?? utcIso(),
|
|
18
|
+
phase: partial.phase,
|
|
19
|
+
iteration: partial.iteration,
|
|
20
|
+
haltReason: partial.haltReason,
|
|
21
|
+
message: partial.message.replace(/[\r\n]+/g, " "),
|
|
22
|
+
prNumber: partial.prNumber,
|
|
23
|
+
grantId: partial.grantId,
|
|
24
|
+
queueCount: partial.queueCount,
|
|
25
|
+
exitCode: partial.exitCode,
|
|
26
|
+
extra: partial.extra,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Append one progress line. Creates parent dirs via containedWrite.
|
|
31
|
+
* #2951 Phase 2: product write sink routes through containedWrite.
|
|
32
|
+
*/
|
|
33
|
+
export function appendFinishLoopProgress(projectRoot, line) {
|
|
34
|
+
const root = resolve(projectRoot);
|
|
35
|
+
const path = finishLoopProgressPath(root);
|
|
36
|
+
const payload = { ...line };
|
|
37
|
+
containedWrite({
|
|
38
|
+
root,
|
|
39
|
+
target: path,
|
|
40
|
+
data: `${JSON.stringify(payload)}\n`,
|
|
41
|
+
mode: "append",
|
|
42
|
+
});
|
|
43
|
+
return path;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope queue scan for directive:finish-loop (#871).
|
|
3
|
+
* Lists xbrief/active + xbrief/pending (and legacy vbrief/) story files.
|
|
4
|
+
* Implementation work is agent-owned; this is the gate's empty-queue detector.
|
|
5
|
+
*/
|
|
6
|
+
export interface QueueEntry {
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly lifecycle: "active" | "pending";
|
|
9
|
+
readonly name: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Scan project root for in-flight / pending story xBRIEFs.
|
|
13
|
+
* Does not invent work — empty means the outer loop may halt cleanly.
|
|
14
|
+
*/
|
|
15
|
+
export declare function scanFinishLoopQueue(projectRoot: string): QueueEntry[];
|
|
16
|
+
//# sourceMappingURL=queue.d.ts.map
|