@deftai/directive-core 0.99.0 → 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/authz/actions.d.ts +6 -0
  2. package/dist/authz/actions.js +3 -0
  3. package/dist/authz/classify.js +157 -23
  4. package/dist/authz/decompose-apply.d.ts +67 -0
  5. package/dist/authz/decompose-apply.js +302 -0
  6. package/dist/authz/index.d.ts +3 -1
  7. package/dist/authz/index.js +3 -1
  8. package/dist/authz/store.d.ts +53 -0
  9. package/dist/authz/store.js +219 -1
  10. package/dist/authz/types.d.ts +19 -2
  11. package/dist/authz/types.js +5 -1
  12. package/dist/doctor/checks.d.ts +44 -0
  13. package/dist/doctor/checks.js +428 -2
  14. package/dist/doctor/main.d.ts +6 -7
  15. package/dist/doctor/main.js +72 -79
  16. package/dist/doctor/which.d.ts +20 -1
  17. package/dist/doctor/which.js +67 -1
  18. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  19. package/dist/finish-loop/pr-finish-loop.js +116 -5
  20. package/dist/finish-loop/types.d.ts +1 -1
  21. package/dist/hooks/dispatcher.d.ts +9 -5
  22. package/dist/hooks/dispatcher.js +23 -15
  23. package/dist/hooks/readonly.d.ts +6 -3
  24. package/dist/hooks/readonly.js +44 -25
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  28. package/dist/init-deposit/agent-hooks.js +51 -31
  29. package/dist/intake/issue-ingest.js +29 -0
  30. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  31. package/dist/lifecycle/completed-consistency.js +443 -0
  32. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  33. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  34. package/dist/lifecycle/index.d.ts +2 -0
  35. package/dist/lifecycle/index.js +2 -0
  36. package/dist/literal-acceptance/capture.d.ts +52 -0
  37. package/dist/literal-acceptance/capture.js +658 -0
  38. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  39. package/dist/literal-acceptance/evaluate.js +193 -0
  40. package/dist/literal-acceptance/index.d.ts +12 -0
  41. package/dist/literal-acceptance/index.js +12 -0
  42. package/dist/literal-acceptance/run.d.ts +38 -0
  43. package/dist/literal-acceptance/run.js +216 -0
  44. package/dist/literal-acceptance/safety.d.ts +24 -0
  45. package/dist/literal-acceptance/safety.js +251 -0
  46. package/dist/literal-acceptance/types.d.ts +82 -0
  47. package/dist/literal-acceptance/types.js +26 -0
  48. package/dist/policy/ceremony-dial.d.ts +67 -11
  49. package/dist/policy/ceremony-dial.js +174 -23
  50. package/dist/policy/index.d.ts +1 -0
  51. package/dist/policy/index.js +1 -0
  52. package/dist/policy/merge-approval-head.d.ts +129 -0
  53. package/dist/policy/merge-approval-head.js +461 -0
  54. package/dist/policy/require-human-merge.d.ts +5 -0
  55. package/dist/policy/require-human-merge.js +5 -0
  56. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  57. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  58. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  59. package/dist/pr-merge-readiness/compute.js +99 -14
  60. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  61. package/dist/pr-merge-readiness/gh.js +268 -0
  62. package/dist/pr-merge-readiness/index.d.ts +2 -2
  63. package/dist/pr-merge-readiness/index.js +2 -2
  64. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  65. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  66. package/dist/pr-wait-mergeable/cascade.js +115 -1
  67. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  68. package/dist/pr-wait-mergeable/main.js +4 -0
  69. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  70. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  71. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  72. package/dist/preflight/evaluate.d.ts +15 -1
  73. package/dist/preflight/evaluate.js +33 -2
  74. package/dist/render/framework-commands.js +4 -0
  75. package/dist/scope/acceptance-evidence.d.ts +76 -0
  76. package/dist/scope/acceptance-evidence.js +348 -0
  77. package/dist/scope/coverage-map.d.ts +98 -0
  78. package/dist/scope/coverage-map.js +558 -0
  79. package/dist/scope/decompose.js +125 -11
  80. package/dist/scope/index.d.ts +3 -0
  81. package/dist/scope/index.js +3 -0
  82. package/dist/scope/parent-lineage.d.ts +125 -0
  83. package/dist/scope/parent-lineage.js +626 -0
  84. package/dist/scope/transition.d.ts +8 -0
  85. package/dist/scope/transition.js +61 -2
  86. package/dist/session/active-cli.d.ts +79 -0
  87. package/dist/session/active-cli.js +382 -0
  88. package/dist/session/effort-budget.d.ts +130 -0
  89. package/dist/session/effort-budget.js +334 -0
  90. package/dist/session/index.d.ts +2 -0
  91. package/dist/session/index.js +2 -0
  92. package/dist/session/session-start.d.ts +6 -0
  93. package/dist/session/session-start.js +53 -2
  94. package/dist/session/verify-session-ritual.d.ts +14 -0
  95. package/dist/session/verify-session-ritual.js +33 -0
  96. package/dist/story-ready/evaluate.d.ts +11 -0
  97. package/dist/story-ready/evaluate.js +41 -3
  98. package/dist/triage/actions/candidates-log.d.ts +1 -2
  99. package/dist/triage/actions/candidates-log.js +37 -6
  100. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  101. package/dist/xbrief-migrate/migrate-project.js +92 -3
  102. package/package.json +7 -3
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Completed lifecycle consistency verifier (#3242 / epic #3237 Q4).
3
+ *
4
+ * Simple fail-closed rule for stories:
5
+ * 1. File in `completed/` must have plan.status matching a completed-folder
6
+ * terminal status (`completed` or `failed` per D2/D13).
7
+ * 2. Every plan.items entry (and nested items/subItems) must be terminal
8
+ * (not pending/proposed/running).
9
+ *
10
+ * Used by doctor (corpus scan) and scope:complete (post-reconcile assert).
11
+ * Does not couple history/changes ledgers.
12
+ */
13
+ import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
14
+ import { join, resolve } from "node:path";
15
+ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
16
+ import { FOLDER_ALLOWED_STATUSES, VALID_ITEM_STATUSES } from "../vbrief-validate/constants.js";
17
+ /** Item statuses that still represent unfinished checklist work (#2862 / #3240 / #3242). */
18
+ export const NON_TERMINAL_ITEM_STATUSES = new Set(["pending", "proposed", "running"]);
19
+ /**
20
+ * Terminal checklist statuses for completed-folder fail-closed (#3242).
21
+ * Align with #3240 non-terminal set: any valid item status that is not
22
+ * pending|proposed|running is terminal; unknown/invalid statuses fail closed.
23
+ */
24
+ export const TERMINAL_ITEM_STATUSES = new Set([...VALID_ITEM_STATUSES].filter((s) => !NON_TERMINAL_ITEM_STATUSES.has(s)));
25
+ /** Statuses allowed under completed/ (D2/D13). */
26
+ export const COMPLETED_FOLDER_PLAN_STATUSES = FOLDER_ALLOWED_STATUSES.completed;
27
+ function asRecord(value) {
28
+ /* v8 ignore next 3 -- non-object / array inputs */
29
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
30
+ return null;
31
+ }
32
+ return value;
33
+ }
34
+ function itemLabel(item, path) {
35
+ if (typeof item.title === "string" && item.title.trim().length > 0) {
36
+ return item.title.trim();
37
+ }
38
+ if (typeof item.id === "string" && item.id.trim().length > 0) {
39
+ return item.id.trim();
40
+ }
41
+ return path;
42
+ }
43
+ /**
44
+ * Collect non-terminal plan.items / subItems / nested items.
45
+ * Fail-closed on malformed slots: non-object entries and present non-array
46
+ * nested collections are reported so completed corpus cannot green-skip them.
47
+ */
48
+ export function collectOpenPlanItems(items, pathPrefix = "plan.items") {
49
+ const hits = [];
50
+ if (!Array.isArray(items)) {
51
+ return hits;
52
+ }
53
+ items.forEach((item, index) => {
54
+ const path = `${pathPrefix}[${index}]`;
55
+ const rec = asRecord(item);
56
+ if (rec === null) {
57
+ hits.push({
58
+ path,
59
+ status: "(non-object)",
60
+ title: path,
61
+ });
62
+ return;
63
+ }
64
+ const status = String(rec.status ?? "").trim();
65
+ if (status.length === 0) {
66
+ hits.push({
67
+ path,
68
+ status: "(empty)",
69
+ title: itemLabel(rec, path),
70
+ });
71
+ }
72
+ else if (NON_TERMINAL_ITEM_STATUSES.has(status)) {
73
+ hits.push({
74
+ path,
75
+ status,
76
+ title: itemLabel(rec, path),
77
+ });
78
+ }
79
+ else if (!TERMINAL_ITEM_STATUSES.has(status)) {
80
+ // Unknown/invalid status is not verified terminal — fail closed (#3242).
81
+ hits.push({
82
+ path,
83
+ status: `(unknown:${status})`,
84
+ title: itemLabel(rec, path),
85
+ });
86
+ }
87
+ for (const nestedKey of ["subItems", "items"]) {
88
+ const nested = rec[nestedKey];
89
+ const nestedPath = `${path}.${nestedKey}`;
90
+ if (nested === undefined)
91
+ continue;
92
+ if (!Array.isArray(nested)) {
93
+ hits.push({
94
+ path: nestedPath,
95
+ status: "(non-array)",
96
+ title: nestedPath,
97
+ });
98
+ continue;
99
+ }
100
+ hits.push(...collectOpenPlanItems(nested, nestedPath));
101
+ }
102
+ });
103
+ return hits;
104
+ }
105
+ /**
106
+ * Evaluate Q4 consistency for a single plan that claims completed lifecycle.
107
+ *
108
+ * @param plan - plan object
109
+ * @param options.relPath - artifact path for messages (default `plan`)
110
+ * @param options.requireStatus - when set, plan.status must equal this exactly
111
+ * (scope:complete uses `completed`). When omitted, any completed-folder
112
+ * terminal status (completed|failed) is accepted.
113
+ */
114
+ export function evaluateCompletedPlanConsistency(plan, options = {}) {
115
+ const relPath = options.relPath ?? "plan";
116
+ const rawStatus = plan.status;
117
+ const planStatus = typeof rawStatus === "string" && rawStatus.trim().length > 0 ? rawStatus.trim() : "(empty)";
118
+ const findings = [];
119
+ const statusOk = options.requireStatus !== undefined
120
+ ? planStatus === options.requireStatus
121
+ : COMPLETED_FOLDER_PLAN_STATUSES.has(planStatus);
122
+ if (!statusOk) {
123
+ const expected = options.requireStatus !== undefined
124
+ ? options.requireStatus
125
+ : [...COMPLETED_FOLDER_PLAN_STATUSES].sort().join("|");
126
+ findings.push({
127
+ relPath,
128
+ planStatus,
129
+ folder: "completed",
130
+ kind: "status_mismatch",
131
+ detail: `${relPath}: folder=completed plan.status=${planStatus} ` + `expected=${expected} (#3242)`,
132
+ });
133
+ }
134
+ // Fail closed when plan.items is present but not a checklist array (incl. null).
135
+ // Omitted (undefined) items = empty terminal checklist; null is malformed shape.
136
+ if (plan.items !== undefined && !Array.isArray(plan.items)) {
137
+ findings.push({
138
+ relPath,
139
+ planStatus,
140
+ folder: "completed",
141
+ kind: "unreadable",
142
+ detail: `${relPath}: folder=completed plan.status=${planStatus} ` +
143
+ `plan.items is non-array (cannot verify terminal checklist) (#3242)`,
144
+ });
145
+ }
146
+ else {
147
+ const openItems = collectOpenPlanItems(plan.items);
148
+ // Structural malformations + unknown statuses are hard unreadable findings —
149
+ // not advisory open_items — so doctor exit fails closed.
150
+ const structural = openItems.filter((h) => h.status === "(non-object)" ||
151
+ h.status === "(non-array)" ||
152
+ h.status.startsWith("(unknown:"));
153
+ const checklistOpen = openItems.filter((h) => h.status !== "(non-object)" &&
154
+ h.status !== "(non-array)" &&
155
+ !h.status.startsWith("(unknown:"));
156
+ if (structural.length > 0) {
157
+ const itemLines = structural
158
+ .map((h) => `${h.path} "${h.title}" status=${h.status}`)
159
+ .join("; ");
160
+ findings.push({
161
+ relPath,
162
+ planStatus,
163
+ folder: "completed",
164
+ kind: "unreadable",
165
+ detail: `${relPath}: folder=completed plan.status=${planStatus} has ` +
166
+ `${structural.length} malformed plan.items shape(s): ${itemLines} (#3242)`,
167
+ openItems: structural,
168
+ });
169
+ }
170
+ if (checklistOpen.length > 0) {
171
+ const itemLines = checklistOpen
172
+ .map((h) => `${h.path} "${h.title}" status=${h.status}`)
173
+ .join("; ");
174
+ findings.push({
175
+ relPath,
176
+ planStatus,
177
+ folder: "completed",
178
+ kind: "open_items",
179
+ detail: `${relPath}: folder=completed plan.status=${planStatus} has ` +
180
+ `${checklistOpen.length} non-terminal plan.items: ${itemLines} (#3242)`,
181
+ openItems: checklistOpen,
182
+ });
183
+ }
184
+ }
185
+ if (findings.length === 0) {
186
+ return {
187
+ ok: true,
188
+ findings: [],
189
+ message: `Completed lifecycle consistency OK (#3242): ${relPath} ` +
190
+ `folder=completed plan.status=${planStatus} items=terminal`,
191
+ };
192
+ }
193
+ return {
194
+ ok: false,
195
+ findings,
196
+ message: formatCompletedConsistencyFailure(findings),
197
+ };
198
+ }
199
+ /** Format fail-closed doctor/complete message with exact paths and statuses. */
200
+ export function formatCompletedConsistencyFailure(findings) {
201
+ const lines = findings.map((f) => ` - ${f.detail}`);
202
+ return (`Completed lifecycle consistency failed (#3242). ` +
203
+ `${findings.length} finding(s):\n${lines.join("\n")}\n` +
204
+ `Rule: completed/ requires plan.status in ` +
205
+ `[${[...COMPLETED_FOLDER_PLAN_STATUSES].sort().join("|")}] and every ` +
206
+ `plan.items entry terminal (not pending|proposed|running).`);
207
+ }
208
+ /**
209
+ * Lifecycle inventory roots that may host completed/ artifacts.
210
+ * Scans both canonical xbrief/ and read-accepted legacy vbrief/ when present
211
+ * (mixed roots must not green-skip retained legacy completed corpus).
212
+ * Existing roots that cannot be inspected produce unreadable findings (fail closed).
213
+ */
214
+ function listConsistencyLifecycleRoots(projectRoot) {
215
+ const root = resolve(projectRoot);
216
+ const ordered = [];
217
+ const seen = new Set();
218
+ const consider = (absRoot, dirName) => {
219
+ const key = absRoot.toLowerCase();
220
+ if (seen.has(key))
221
+ return;
222
+ seen.add(key);
223
+ let exists;
224
+ try {
225
+ exists = existsSync(absRoot);
226
+ }
227
+ catch (err) {
228
+ const msg = err instanceof Error ? err.message : String(err);
229
+ ordered.push({
230
+ kind: "unreadable",
231
+ dirName,
232
+ detail: `lifecycle root existsSync failed: ${msg}`,
233
+ });
234
+ return;
235
+ }
236
+ if (!exists)
237
+ return;
238
+ try {
239
+ const st = lstatSync(absRoot);
240
+ if (st.isSymbolicLink()) {
241
+ ordered.push({
242
+ kind: "unreadable",
243
+ dirName,
244
+ detail: `lifecycle root is a symlink (refusing to follow; cannot inventory completed/)`,
245
+ });
246
+ return;
247
+ }
248
+ if (!st.isDirectory()) {
249
+ ordered.push({
250
+ kind: "unreadable",
251
+ dirName,
252
+ detail: `lifecycle root exists but is not a directory (cannot inventory completed/)`,
253
+ });
254
+ return;
255
+ }
256
+ }
257
+ catch (err) {
258
+ const msg = err instanceof Error ? err.message : String(err);
259
+ ordered.push({
260
+ kind: "unreadable",
261
+ dirName,
262
+ detail: `lifecycle root unreadable (lstat failed): ${msg}`,
263
+ });
264
+ return;
265
+ }
266
+ ordered.push({ kind: "dir", absRoot, dirName });
267
+ };
268
+ // Always probe both layout dirs so mixed roots cannot green-skip legacy completed/.
269
+ consider(join(root, MIGRATED_ARTIFACT_DIR), MIGRATED_ARTIFACT_DIR);
270
+ consider(join(root, LEGACY_ARTIFACT_DIR), LEGACY_ARTIFACT_DIR);
271
+ return ordered;
272
+ }
273
+ function unreadableFinding(relPath, detail) {
274
+ return {
275
+ relPath,
276
+ planStatus: "(unreadable)",
277
+ folder: "completed",
278
+ kind: "unreadable",
279
+ detail: `${relPath}: ${detail} (#3242)`,
280
+ };
281
+ }
282
+ function scanCompletedDir(completedDir, pathPrefix) {
283
+ let completedPresent = false;
284
+ try {
285
+ if (!existsSync(completedDir)) {
286
+ return { findings: [], scanned: 0, completedPresent: false };
287
+ }
288
+ }
289
+ catch (err) {
290
+ const msg = err instanceof Error ? err.message : String(err);
291
+ return {
292
+ findings: [
293
+ unreadableFinding(`${pathPrefix}/completed`, `completed/ existsSync failed (cannot inventory): ${msg}`),
294
+ ],
295
+ scanned: 0,
296
+ completedPresent: true,
297
+ };
298
+ }
299
+ try {
300
+ const st = lstatSync(completedDir);
301
+ if (st.isSymbolicLink()) {
302
+ return {
303
+ findings: [
304
+ unreadableFinding(`${pathPrefix}/completed`, "completed/ is a symlink (refusing to follow; cannot inventory)"),
305
+ ],
306
+ scanned: 0,
307
+ completedPresent: true,
308
+ };
309
+ }
310
+ if (!st.isDirectory()) {
311
+ return {
312
+ findings: [
313
+ unreadableFinding(`${pathPrefix}/completed`, "completed path exists but is not a directory (cannot inventory)"),
314
+ ],
315
+ scanned: 0,
316
+ completedPresent: true,
317
+ };
318
+ }
319
+ }
320
+ catch (err) {
321
+ const msg = err instanceof Error ? err.message : String(err);
322
+ return {
323
+ findings: [
324
+ unreadableFinding(`${pathPrefix}/completed`, `completed/ unreadable (lstat failed): ${msg}`),
325
+ ],
326
+ scanned: 0,
327
+ completedPresent: true,
328
+ };
329
+ }
330
+ completedPresent = true;
331
+ let names;
332
+ try {
333
+ names = readdirSync(completedDir)
334
+ .filter((n) => hasArtifactSuffix(n))
335
+ .sort();
336
+ }
337
+ catch (err) {
338
+ const msg = err instanceof Error ? err.message : String(err);
339
+ return {
340
+ findings: [
341
+ unreadableFinding(`${pathPrefix}/completed`, `completed/ readdir failed (cannot inventory): ${msg}`),
342
+ ],
343
+ scanned: 0,
344
+ completedPresent: true,
345
+ };
346
+ }
347
+ const findings = [];
348
+ for (const name of names) {
349
+ const relPath = `${pathPrefix}/completed/${name}`;
350
+ const abs = join(completedDir, name);
351
+ try {
352
+ const st = lstatSync(abs);
353
+ if (st.isSymbolicLink()) {
354
+ findings.push(unreadableFinding(relPath, "symlink under completed/ (refusing to follow; cannot verify status/items)"));
355
+ continue;
356
+ }
357
+ if (!st.isFile()) {
358
+ findings.push(unreadableFinding(relPath, "non-file entry under completed/ (cannot verify status/items)"));
359
+ continue;
360
+ }
361
+ }
362
+ catch (err) {
363
+ const msg = err instanceof Error ? err.message : String(err);
364
+ findings.push(unreadableFinding(relPath, `lstat failed: ${msg}`));
365
+ continue;
366
+ }
367
+ let data;
368
+ try {
369
+ data = JSON.parse(readFileSync(abs, "utf8"));
370
+ }
371
+ catch {
372
+ findings.push(unreadableFinding(relPath, "malformed JSON under completed/ (cannot verify status/items)"));
373
+ continue;
374
+ }
375
+ const root = asRecord(data);
376
+ if (root === null) {
377
+ findings.push(unreadableFinding(relPath, "non-object root under completed/ (cannot verify status/items)"));
378
+ continue;
379
+ }
380
+ const plan = asRecord(root.plan);
381
+ if (plan === null) {
382
+ findings.push(unreadableFinding(relPath, "missing or non-object plan under completed/"));
383
+ continue;
384
+ }
385
+ const result = evaluateCompletedPlanConsistency(plan, { relPath });
386
+ findings.push(...result.findings);
387
+ }
388
+ return { findings, scanned: names.length, completedPresent };
389
+ }
390
+ /**
391
+ * Scan `xbrief/completed/` and/or legacy `vbrief/completed/` for Q4 mismatches.
392
+ * Offline / filesystem only. Missing completed/ is green (nothing to check).
393
+ * Malformed completed artifacts and unreadable roots fail closed (no silent skip).
394
+ * Mixed roots scan both.
395
+ */
396
+ export function scanCompletedLifecycleConsistency(projectRoot) {
397
+ const roots = listConsistencyLifecycleRoots(projectRoot);
398
+ if (roots.length === 0) {
399
+ return {
400
+ ok: true,
401
+ findings: [],
402
+ message: "Completed lifecycle consistency OK (#3242): no lifecycle root",
403
+ };
404
+ }
405
+ const findings = [];
406
+ let scanned = 0;
407
+ let anyCompletedPresent = false;
408
+ for (const probe of roots) {
409
+ if (probe.kind === "unreadable") {
410
+ findings.push(unreadableFinding(probe.dirName, probe.detail));
411
+ continue;
412
+ }
413
+ const completedDir = join(probe.absRoot, "completed");
414
+ // Always prefix with layout dir so mixed roots and exact paths are unambiguous.
415
+ const part = scanCompletedDir(completedDir, probe.dirName);
416
+ if (part.completedPresent) {
417
+ anyCompletedPresent = true;
418
+ }
419
+ findings.push(...part.findings);
420
+ scanned += part.scanned;
421
+ }
422
+ if (!anyCompletedPresent && findings.length === 0) {
423
+ return {
424
+ ok: true,
425
+ findings: [],
426
+ message: "Completed lifecycle consistency OK (#3242): completed/ absent",
427
+ };
428
+ }
429
+ if (findings.length === 0) {
430
+ return {
431
+ ok: true,
432
+ findings: [],
433
+ message: `Completed lifecycle consistency OK (#3242): scanned ${scanned} ` +
434
+ `completed/ artifact(s)`,
435
+ };
436
+ }
437
+ return {
438
+ ok: false,
439
+ findings,
440
+ message: formatCompletedConsistencyFailure(findings),
441
+ };
442
+ }
443
+ //# sourceMappingURL=completed-consistency.js.map
@@ -0,0 +1,47 @@
1
+ import { type IssueRef } from "../orphan-active/refs.js";
2
+ import type { RunGhFn } from "../pr-protected-issues/types.js";
3
+ import { type GitRunner } from "../session/git.js";
4
+ export type OutputStream = "stdout" | "stderr" | "none";
5
+ /** Lifecycle folders scanned for local "known origin" xBRIEFs. */
6
+ export declare const LOCAL_ORIGIN_FOLDERS: readonly ["proposed", "pending", "active", "completed", "cancelled"];
7
+ /** Non-terminal tip folders: closed issues still here need completed land. */
8
+ export declare const TIP_NONTERMINAL_FOLDERS: readonly ["proposed", "pending", "active"];
9
+ /** Terminal tip folders that satisfy the land rule. */
10
+ export declare const TIP_TERMINAL_FOLDERS: readonly ["completed", "cancelled"];
11
+ export interface MissingCompletedLand {
12
+ readonly issue: IssueRef;
13
+ /** Where the detector learned about this scoped issue (local and/or tip paths). */
14
+ readonly origins: readonly string[];
15
+ }
16
+ export interface EvaluateCompletedTrackedResult {
17
+ readonly code: 0 | 1 | 2;
18
+ readonly message: string;
19
+ readonly stream: OutputStream;
20
+ readonly missing: readonly MissingCompletedLand[];
21
+ readonly tip: string | null;
22
+ }
23
+ export interface EvaluateCompletedTrackedOptions {
24
+ readonly quiet?: boolean;
25
+ readonly repo?: string | null;
26
+ /** Explicit delivery tip ref (e.g. origin/master). Overrides policy/git default. */
27
+ readonly tip?: string | null;
28
+ readonly runGh?: RunGhFn;
29
+ readonly skipGh?: boolean;
30
+ readonly runGit?: GitRunner;
31
+ /** Test seam: override issue state resolution. */
32
+ readonly resolveIssueState?: (ref: IssueRef) => "open" | "closed" | null;
33
+ }
34
+ /**
35
+ * Resolve the git tip to inspect for tracked completed/cancelled land.
36
+ * Prefers origin/<deliveryBranch> when present.
37
+ */
38
+ export declare function resolveDeliveryTip(projectRoot: string, tipOverride: string | null | undefined, runGit: GitRunner): {
39
+ tip: string | null;
40
+ error: string | null;
41
+ };
42
+ /**
43
+ * Pure evaluator: closed scoped issues must have tracked completed/cancelled
44
+ * on the delivery tip (#3264).
45
+ */
46
+ export declare function evaluateCompletedTracked(projectRoot: string, options?: EvaluateCompletedTrackedOptions): EvaluateCompletedTrackedResult;
47
+ //# sourceMappingURL=completed-tracked-on-delivery.d.ts.map