@deftai/directive-core 0.98.1 → 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 (144) 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 +401 -75
  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/consumer-check-contract/evaluate.d.ts +40 -0
  13. package/dist/consumer-check-contract/evaluate.js +188 -3
  14. package/dist/consumer-check-contract/index.d.ts +1 -1
  15. package/dist/consumer-check-contract/index.js +1 -1
  16. package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
  17. package/dist/content-contracts/skills/greptile-detector.js +202 -4
  18. package/dist/decision/index.d.ts +17 -0
  19. package/dist/decision/index.js +35 -0
  20. package/dist/decision/list.d.ts +47 -0
  21. package/dist/decision/list.js +250 -0
  22. package/dist/decision/schema.d.ts +88 -0
  23. package/dist/decision/schema.js +293 -0
  24. package/dist/decision/write.d.ts +82 -0
  25. package/dist/decision/write.js +427 -0
  26. package/dist/doctor/checks.d.ts +44 -0
  27. package/dist/doctor/checks.js +428 -2
  28. package/dist/doctor/main.d.ts +6 -7
  29. package/dist/doctor/main.js +72 -79
  30. package/dist/doctor/which.d.ts +20 -1
  31. package/dist/doctor/which.js +67 -1
  32. package/dist/eval/report.d.ts +29 -0
  33. package/dist/eval/report.js +69 -0
  34. package/dist/eval/run.d.ts +9 -0
  35. package/dist/eval/run.js +40 -4
  36. package/dist/eval/version-pin.d.ts +99 -0
  37. package/dist/eval/version-pin.js +181 -0
  38. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  39. package/dist/finish-loop/pr-finish-loop.js +116 -5
  40. package/dist/finish-loop/types.d.ts +1 -1
  41. package/dist/hooks/dispatcher.d.ts +9 -5
  42. package/dist/hooks/dispatcher.js +23 -15
  43. package/dist/hooks/readonly.d.ts +6 -3
  44. package/dist/hooks/readonly.js +44 -25
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +2 -0
  47. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  48. package/dist/init-deposit/agent-hooks.js +51 -31
  49. package/dist/intake/issue-ingest.js +29 -0
  50. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  51. package/dist/lifecycle/completed-consistency.js +443 -0
  52. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  53. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  54. package/dist/lifecycle/index.d.ts +2 -0
  55. package/dist/lifecycle/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +52 -0
  57. package/dist/literal-acceptance/capture.js +658 -0
  58. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  59. package/dist/literal-acceptance/evaluate.js +193 -0
  60. package/dist/literal-acceptance/index.d.ts +12 -0
  61. package/dist/literal-acceptance/index.js +12 -0
  62. package/dist/literal-acceptance/run.d.ts +38 -0
  63. package/dist/literal-acceptance/run.js +216 -0
  64. package/dist/literal-acceptance/safety.d.ts +24 -0
  65. package/dist/literal-acceptance/safety.js +251 -0
  66. package/dist/literal-acceptance/types.d.ts +82 -0
  67. package/dist/literal-acceptance/types.js +26 -0
  68. package/dist/platform/host-content-surface.d.ts +74 -0
  69. package/dist/platform/host-content-surface.js +214 -0
  70. package/dist/platform/index.d.ts +1 -0
  71. package/dist/platform/index.js +1 -0
  72. package/dist/policy/ceremony-dial.d.ts +289 -0
  73. package/dist/policy/ceremony-dial.js +980 -0
  74. package/dist/policy/deft-directive-disable.js +12 -2
  75. package/dist/policy/index.d.ts +2 -0
  76. package/dist/policy/index.js +16 -1
  77. package/dist/policy/merge-approval-head.d.ts +129 -0
  78. package/dist/policy/merge-approval-head.js +461 -0
  79. package/dist/policy/require-human-merge.d.ts +5 -0
  80. package/dist/policy/require-human-merge.js +5 -0
  81. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  82. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  83. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  84. package/dist/pr-merge-readiness/compute.js +99 -14
  85. package/dist/pr-merge-readiness/evaluate.js +10 -0
  86. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  87. package/dist/pr-merge-readiness/gh.js +268 -0
  88. package/dist/pr-merge-readiness/index.d.ts +2 -2
  89. package/dist/pr-merge-readiness/index.js +2 -2
  90. package/dist/pr-merge-readiness/mergeability.js +5 -0
  91. package/dist/pr-merge-readiness/output.js +2 -0
  92. package/dist/pr-merge-readiness/parse.js +4 -0
  93. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  94. package/dist/pr-merge-readiness/types.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  96. package/dist/pr-wait-mergeable/cascade.js +115 -1
  97. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  98. package/dist/pr-wait-mergeable/main.js +4 -0
  99. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  100. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  101. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  102. package/dist/preflight/evaluate.d.ts +15 -1
  103. package/dist/preflight/evaluate.js +33 -2
  104. package/dist/render/framework-commands.js +4 -0
  105. package/dist/scope/acceptance-evidence.d.ts +76 -0
  106. package/dist/scope/acceptance-evidence.js +348 -0
  107. package/dist/scope/coverage-map.d.ts +98 -0
  108. package/dist/scope/coverage-map.js +558 -0
  109. package/dist/scope/decompose.js +125 -11
  110. package/dist/scope/effort-activate-gate.d.ts +28 -0
  111. package/dist/scope/effort-activate-gate.js +64 -0
  112. package/dist/scope/index.d.ts +4 -0
  113. package/dist/scope/index.js +4 -0
  114. package/dist/scope/parent-lineage.d.ts +125 -0
  115. package/dist/scope/parent-lineage.js +626 -0
  116. package/dist/scope/transition.d.ts +8 -0
  117. package/dist/scope/transition.js +69 -2
  118. package/dist/session/active-cli.d.ts +79 -0
  119. package/dist/session/active-cli.js +382 -0
  120. package/dist/session/effort-budget.d.ts +130 -0
  121. package/dist/session/effort-budget.js +334 -0
  122. package/dist/session/index.d.ts +2 -0
  123. package/dist/session/index.js +2 -0
  124. package/dist/session/session-start.d.ts +30 -1
  125. package/dist/session/session-start.js +234 -26
  126. package/dist/session/verify-session-ritual.d.ts +14 -0
  127. package/dist/session/verify-session-ritual.js +33 -0
  128. package/dist/story-ready/evaluate.d.ts +11 -0
  129. package/dist/story-ready/evaluate.js +41 -3
  130. package/dist/swarm/index.d.ts +2 -0
  131. package/dist/swarm/index.js +2 -0
  132. package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
  133. package/dist/swarm/pre-dispatch-cli.js +143 -0
  134. package/dist/swarm/pre-dispatch.d.ts +87 -0
  135. package/dist/swarm/pre-dispatch.js +373 -0
  136. package/dist/triage/actions/candidates-log.d.ts +1 -2
  137. package/dist/triage/actions/candidates-log.js +37 -6
  138. package/dist/vbrief-activate/activate.js +6 -0
  139. package/dist/vbrief-validate/constants.d.ts +2 -0
  140. package/dist/vbrief-validate/constants.js +2 -0
  141. package/dist/vbrief-validate/schema.js +4 -1
  142. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  143. package/dist/xbrief-migrate/migrate-project.js +92 -3
  144. package/package.json +19 -3
@@ -0,0 +1,427 @@
1
+ /**
2
+ * decision:write — create a lightweight structured decision record (#1396).
3
+ */
4
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
5
+ import { dirname, join, relative, resolve } from "node:path";
6
+ import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
7
+ import { ProjectionContainmentError } from "../fs/projection-containment.js";
8
+ import { resolveProjectRoot } from "../scope/project-context.js";
9
+ import { DECISIONS_DIR_REL, decisionFilename, formatDecisionValidationErrors, normalizeTimestamp, slugifyDecision, validateDecisionRecord, } from "./schema.js";
10
+ function loadBodyFile(bodyFile, projectRoot) {
11
+ const abs = resolve(bodyFile.startsWith(".") ? join(projectRoot, bodyFile) : bodyFile);
12
+ const text = readFileSync(abs, "utf8");
13
+ const parsed = JSON.parse(text);
14
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15
+ throw new Error("body file must contain a JSON object");
16
+ }
17
+ return parsed;
18
+ }
19
+ function mergeInput(options, projectRoot) {
20
+ let base = {};
21
+ if (options.bodyFile !== undefined && options.bodyFile.trim().length > 0) {
22
+ base = loadBodyFile(options.bodyFile.trim(), projectRoot);
23
+ }
24
+ const out = { ...base };
25
+ if (options.decision !== undefined)
26
+ out.decision = options.decision;
27
+ if (options.governingRule !== undefined)
28
+ out.governingRule = options.governingRule;
29
+ if (options.alternatives !== undefined)
30
+ out.alternativesConsidered = options.alternatives;
31
+ if (options.whyWinner !== undefined)
32
+ out.whyWinner = options.whyWinner;
33
+ if (options.confidence !== undefined)
34
+ out.confidence = options.confidence;
35
+ if (options.revisitTrigger !== undefined)
36
+ out.revisitTrigger = options.revisitTrigger;
37
+ if (options.id !== undefined)
38
+ out.id = options.id;
39
+ if (options.timestamp !== undefined)
40
+ out.timestamp = options.timestamp;
41
+ if (options.tags !== undefined)
42
+ out.tags = options.tags;
43
+ if (options.relatedIssues !== undefined)
44
+ out.relatedIssues = options.relatedIssues;
45
+ if (options.scope !== undefined) {
46
+ const scopes = Array.isArray(options.scope) ? options.scope : [options.scope];
47
+ out.activeScopeRefs = scopes.map((s) => s.replace(/\\/g, "/"));
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * Append a pointer line to plan.narratives.Decisions on a scope xBRIEF without
53
+ * removing existing narratives (validators accept unknown narrative keys as strings).
54
+ *
55
+ * Concurrent writers: read-modify-replace with post-write verify + retry so a
56
+ * later attach does not silently drop an earlier decision pointer.
57
+ */
58
+ export function appendScopeDecisionPointer(projectRoot, scopeRelPath, decisionRelPath, decisionSummary) {
59
+ const scopeAbs = resolve(projectRoot, scopeRelPath);
60
+ if (!existsSync(scopeAbs)) {
61
+ throw new Error(`scope xBRIEF not found: ${scopeRelPath}`);
62
+ }
63
+ const pointerNeedle = decisionRelPath;
64
+ const maxAttempts = 4;
65
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
66
+ const raw = readFileSync(scopeAbs, "utf8");
67
+ const doc = JSON.parse(raw);
68
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
69
+ throw new Error(`invalid scope xBRIEF JSON: ${scopeRelPath}`);
70
+ }
71
+ const plan = doc.plan;
72
+ if (plan === null || typeof plan !== "object" || Array.isArray(plan)) {
73
+ throw new Error(`scope xBRIEF missing plan: ${scopeRelPath}`);
74
+ }
75
+ const narrativesRaw = plan.narratives;
76
+ const narratives = narrativesRaw !== null && typeof narrativesRaw === "object" && !Array.isArray(narrativesRaw)
77
+ ? { ...narrativesRaw }
78
+ : {};
79
+ const pointer = `- ${decisionRelPath} — ${decisionSummary}`;
80
+ const existing = typeof narratives.Decisions === "string" ? narratives.Decisions.trim() : "";
81
+ if (existing.includes(pointerNeedle)) {
82
+ return;
83
+ }
84
+ narratives.Decisions = existing.length > 0 ? `${existing}\n${pointer}` : pointer;
85
+ plan.narratives = narratives;
86
+ const data = `${JSON.stringify(doc, null, 2)}\n`;
87
+ containedWrite({
88
+ root: resolve(projectRoot),
89
+ target: scopeAbs,
90
+ data,
91
+ mode: "replace",
92
+ });
93
+ // Verify pointer survived concurrent replace.
94
+ try {
95
+ const verifyRaw = readFileSync(scopeAbs, "utf8");
96
+ const verifyDoc = JSON.parse(verifyRaw);
97
+ const vPlan = verifyDoc?.plan;
98
+ const vNarr = vPlan?.narratives;
99
+ const vDec = typeof vNarr?.Decisions === "string" ? vNarr.Decisions : "";
100
+ if (vDec.includes(pointerNeedle)) {
101
+ return;
102
+ }
103
+ }
104
+ catch {
105
+ // retry
106
+ }
107
+ }
108
+ throw new Error(`failed to attach decision pointer to ${scopeRelPath} after ${maxAttempts} attempts ` +
109
+ `(concurrent writers may be contending on plan.narratives.Decisions)`);
110
+ }
111
+ /** Write a validated decision record to disk (standalone + optional scope pointer). */
112
+ export function runDecisionWrite(options) {
113
+ const projectRootRaw = resolveProjectRoot(options.projectRoot ?? undefined);
114
+ if (projectRootRaw === null) {
115
+ return {
116
+ outcome: "error-config",
117
+ exitCode: 2,
118
+ path: null,
119
+ scopePath: null,
120
+ record: null,
121
+ message: "Error: could not resolve project root. Pass --project-root or run from a directive repo.\n",
122
+ };
123
+ }
124
+ const projectRoot = resolve(projectRootRaw);
125
+ let merged;
126
+ try {
127
+ merged = mergeInput(options, projectRoot);
128
+ }
129
+ catch (err) {
130
+ return {
131
+ outcome: "error-bad-args",
132
+ exitCode: 2,
133
+ path: null,
134
+ scopePath: null,
135
+ record: null,
136
+ message: `Error: failed to load body file: ${err instanceof Error ? err.message : String(err)}\n`,
137
+ };
138
+ }
139
+ const validated = validateDecisionRecord(merged);
140
+ if (!validated.ok || validated.record === undefined) {
141
+ return {
142
+ outcome: "error-bad-args",
143
+ exitCode: 2,
144
+ path: null,
145
+ scopePath: null,
146
+ record: null,
147
+ message: `Error: invalid decision record:\n${formatDecisionValidationErrors(validated.errors)}\n`,
148
+ };
149
+ }
150
+ const record = validated.record;
151
+ const filename = decisionFilename(record.id, record.timestamp);
152
+ const relPath = join(DECISIONS_DIR_REL, filename).replace(/\\/g, "/");
153
+ const absPath = resolve(projectRoot, relPath);
154
+ const recordOut = {
155
+ ...record,
156
+ path: relPath,
157
+ };
158
+ if (options.dryRun) {
159
+ return {
160
+ outcome: "written",
161
+ exitCode: 0,
162
+ path: relPath,
163
+ scopePath: record.activeScopeRefs[0] ?? null,
164
+ record: recordOut,
165
+ message: `[dry-run] would write ${relPath}\n`,
166
+ };
167
+ }
168
+ try {
169
+ mkdirSync(dirname(absPath), { recursive: true });
170
+ if (existsSync(absPath) && options.force !== true) {
171
+ return {
172
+ outcome: "error-io",
173
+ exitCode: 2,
174
+ path: relPath,
175
+ scopePath: null,
176
+ record: recordOut,
177
+ message: `Error: decision file already exists: ${relPath}\n` +
178
+ " Refusing to overwrite (would destroy prior rationale). " +
179
+ "Pass --force to replace, or use a distinct --id / decision text.\n",
180
+ };
181
+ }
182
+ const data = `${JSON.stringify({
183
+ schemaVersion: recordOut.schemaVersion,
184
+ id: recordOut.id,
185
+ decision: recordOut.decision,
186
+ governingRule: recordOut.governingRule,
187
+ alternativesConsidered: recordOut.alternativesConsidered,
188
+ whyWinner: recordOut.whyWinner,
189
+ confidence: recordOut.confidence,
190
+ activeScopeRefs: recordOut.activeScopeRefs,
191
+ timestamp: recordOut.timestamp,
192
+ revisitTrigger: recordOut.revisitTrigger,
193
+ ...(recordOut.tags !== undefined ? { tags: recordOut.tags } : {}),
194
+ ...(recordOut.relatedIssues !== undefined
195
+ ? { relatedIssues: recordOut.relatedIssues }
196
+ : {}),
197
+ }, null, 2)}\n`;
198
+ // Prefer exclusive create unless --force. Concurrent writers that both
199
+ // pass the pre-check still fail closed via create/EXISTS instead of replace.
200
+ containedWrite({
201
+ root: projectRoot,
202
+ target: absPath,
203
+ data,
204
+ mode: options.force === true ? "replace" : "create",
205
+ });
206
+ let scopePath = null;
207
+ if (record.activeScopeRefs.length > 0 && options.standalone !== true) {
208
+ const scopeRel = record.activeScopeRefs[0];
209
+ try {
210
+ appendScopeDecisionPointer(projectRoot, scopeRel, relPath, record.decision);
211
+ scopePath = scopeRel;
212
+ }
213
+ catch (err) {
214
+ return {
215
+ outcome: "error-io",
216
+ exitCode: 2,
217
+ path: relPath,
218
+ scopePath: null,
219
+ record: recordOut,
220
+ message: `[deft decision] Wrote ${relPath} but failed to attach scope pointer: ` +
221
+ `${err instanceof Error ? err.message : String(err)}\n`,
222
+ };
223
+ }
224
+ }
225
+ const scopeNote = scopePath !== null ? ` (linked from ${scopePath})` : "";
226
+ return {
227
+ outcome: "written",
228
+ exitCode: 0,
229
+ path: relPath,
230
+ scopePath,
231
+ record: recordOut,
232
+ message: `[deft decision] Wrote ${relPath}${scopeNote}\n`,
233
+ };
234
+ }
235
+ catch (err) {
236
+ if (err instanceof ContainedWriteError || err instanceof ProjectionContainmentError) {
237
+ return {
238
+ outcome: "error-io",
239
+ exitCode: 2,
240
+ path: null,
241
+ scopePath: null,
242
+ record: recordOut,
243
+ message: `Error: contained write refused: ${err.message}\n`,
244
+ };
245
+ }
246
+ return {
247
+ outcome: "error-io",
248
+ exitCode: 2,
249
+ path: null,
250
+ scopePath: null,
251
+ record: recordOut,
252
+ message: `Error: write failed: ${err instanceof Error ? err.message : String(err)}\n`,
253
+ };
254
+ }
255
+ }
256
+ /** Parse argv for decision:write. */
257
+ export function parseDecisionWriteArgs(argv) {
258
+ const out = {};
259
+ const positionals = [];
260
+ const takeValue = (i, flag, eqPrefix) => {
261
+ const arg = argv[i];
262
+ if (arg === flag) {
263
+ return [argv[i + 1], i + 1];
264
+ }
265
+ if (arg.startsWith(eqPrefix)) {
266
+ return [arg.slice(eqPrefix.length), i];
267
+ }
268
+ return [undefined, i];
269
+ };
270
+ for (let i = 0; i < argv.length; i += 1) {
271
+ const arg = argv[i];
272
+ if (arg === "--dry-run")
273
+ out.dryRun = true;
274
+ else if (arg === "--json")
275
+ out.json = true;
276
+ else if (arg === "--standalone")
277
+ out.standalone = true;
278
+ else if (arg === "--force")
279
+ out.force = true;
280
+ else if (arg === "--decision" || arg.startsWith("--decision=")) {
281
+ const [v, ni] = takeValue(i, "--decision", "--decision=");
282
+ out.decision = v;
283
+ i = ni;
284
+ }
285
+ else if (arg === "--governing-rule" || arg.startsWith("--governing-rule=")) {
286
+ const [v, ni] = takeValue(i, "--governing-rule", "--governing-rule=");
287
+ out.governingRule = v;
288
+ i = ni;
289
+ }
290
+ else if (arg === "--governing-path" || arg.startsWith("--governing-path=")) {
291
+ const [v, ni] = takeValue(i, "--governing-path", "--governing-path=");
292
+ out.governingPath = v;
293
+ i = ni;
294
+ }
295
+ else if (arg === "--governing-rfc" || arg.startsWith("--governing-rfc=")) {
296
+ const [v, ni] = takeValue(i, "--governing-rfc", "--governing-rfc=");
297
+ out.governingRfc = v;
298
+ i = ni;
299
+ }
300
+ else if (arg === "--alternative" || arg.startsWith("--alternative=")) {
301
+ const [v, ni] = takeValue(i, "--alternative", "--alternative=");
302
+ if (v !== undefined) {
303
+ out.alternatives = [...(out.alternatives ?? []), v];
304
+ }
305
+ i = ni;
306
+ }
307
+ else if (arg === "--why-winner" || arg.startsWith("--why-winner=")) {
308
+ const [v, ni] = takeValue(i, "--why-winner", "--why-winner=");
309
+ out.whyWinner = v;
310
+ i = ni;
311
+ }
312
+ else if (arg === "--confidence" || arg.startsWith("--confidence=")) {
313
+ const [v, ni] = takeValue(i, "--confidence", "--confidence=");
314
+ out.confidence = v;
315
+ i = ni;
316
+ }
317
+ else if (arg === "--scope" || arg.startsWith("--scope=")) {
318
+ const [v, ni] = takeValue(i, "--scope", "--scope=");
319
+ if (v !== undefined) {
320
+ out.scope = [...(out.scope ?? []), v];
321
+ }
322
+ i = ni;
323
+ }
324
+ else if (arg === "--revisit-trigger" || arg.startsWith("--revisit-trigger=")) {
325
+ const [v, ni] = takeValue(i, "--revisit-trigger", "--revisit-trigger=");
326
+ out.revisitTrigger = v;
327
+ i = ni;
328
+ }
329
+ else if (arg === "--id" || arg.startsWith("--id=")) {
330
+ const [v, ni] = takeValue(i, "--id", "--id=");
331
+ out.id = v;
332
+ i = ni;
333
+ }
334
+ else if (arg === "--timestamp" || arg.startsWith("--timestamp=")) {
335
+ const [v, ni] = takeValue(i, "--timestamp", "--timestamp=");
336
+ out.timestamp = v;
337
+ i = ni;
338
+ }
339
+ else if (arg === "--tag" || arg.startsWith("--tag=")) {
340
+ const [v, ni] = takeValue(i, "--tag", "--tag=");
341
+ if (v !== undefined)
342
+ out.tags = [...(out.tags ?? []), v];
343
+ i = ni;
344
+ }
345
+ else if (arg === "--related-issue" || arg.startsWith("--related-issue=")) {
346
+ const [v, ni] = takeValue(i, "--related-issue", "--related-issue=");
347
+ if (v !== undefined && /^\d+$/.test(v.trim())) {
348
+ out.relatedIssues = [...(out.relatedIssues ?? []), Number(v.trim())];
349
+ }
350
+ i = ni;
351
+ }
352
+ else if (arg === "--body-file" || arg.startsWith("--body-file=")) {
353
+ const [v, ni] = takeValue(i, "--body-file", "--body-file=");
354
+ out.bodyFile = v;
355
+ i = ni;
356
+ }
357
+ else if (arg === "--project-root" || arg.startsWith("--project-root=")) {
358
+ const [v, ni] = takeValue(i, "--project-root", "--project-root=");
359
+ out.projectRoot = v;
360
+ i = ni;
361
+ }
362
+ else if (arg.startsWith("-")) {
363
+ return { ...out, error: `unrecognized argument: ${arg}` };
364
+ }
365
+ else {
366
+ positionals.push(arg);
367
+ }
368
+ }
369
+ if ((out.decision === undefined || out.decision.trim().length === 0) && positionals.length > 0) {
370
+ out.decision = positionals.join(" ");
371
+ }
372
+ return out;
373
+ }
374
+ /** CLI entry for decision:write. */
375
+ export function decisionWriteMain(argv) {
376
+ const args = parseDecisionWriteArgs(argv);
377
+ if (args.error !== undefined) {
378
+ process.stderr.write(`decision:write: ${args.error}\n`);
379
+ return 2;
380
+ }
381
+ const governingRule = args.governingRule !== undefined
382
+ ? {
383
+ description: args.governingRule,
384
+ path: args.governingPath,
385
+ rfc2119: args.governingRfc,
386
+ }
387
+ : undefined;
388
+ const result = runDecisionWrite({
389
+ decision: args.decision,
390
+ governingRule,
391
+ alternatives: args.alternatives,
392
+ whyWinner: args.whyWinner,
393
+ confidence: args.confidence,
394
+ scope: args.scope,
395
+ revisitTrigger: args.revisitTrigger,
396
+ id: args.id,
397
+ timestamp: args.timestamp ?? normalizeTimestamp(),
398
+ tags: args.tags,
399
+ relatedIssues: args.relatedIssues,
400
+ bodyFile: args.bodyFile,
401
+ standalone: args.standalone,
402
+ force: args.force,
403
+ dryRun: args.dryRun,
404
+ json: args.json,
405
+ projectRoot: args.projectRoot,
406
+ });
407
+ if (args.json) {
408
+ process.stdout.write(`${JSON.stringify({
409
+ outcome: result.outcome,
410
+ exit_code: result.exitCode,
411
+ path: result.path,
412
+ scope_path: result.scopePath,
413
+ record: result.record,
414
+ message: result.message.trim(),
415
+ }, null, 2)}\n`);
416
+ }
417
+ else {
418
+ process.stdout.write(result.message);
419
+ }
420
+ return result.exitCode;
421
+ }
422
+ /** Relative path helper for tests. */
423
+ export function relativeToProject(projectRoot, absPath) {
424
+ return relative(projectRoot, absPath).replace(/\\/g, "/");
425
+ }
426
+ export { slugifyDecision };
427
+ //# sourceMappingURL=write.js.map
@@ -1,9 +1,42 @@
1
+ import type { XbriefSchemaDistance } from "../staleness-tickler/types.js";
1
2
  import type { CheckResult } from "./types.js";
3
+ /** Remediation verb for project envelope behind-major (#2971 / #3243 / #3236). */
4
+ export declare const XBRIEF_ENVELOPE_MIGRATE_COMMAND: "deft migrate:xbrief";
5
+ /** Doctor check name for envelope major mismatch (Q5 Option 2, #3243). */
6
+ export declare const XBRIEF_ENVELOPE_MAJOR_CHECK: "xbrief-envelope-version";
2
7
  export interface CheckSeams {
3
8
  readonly readText?: (path: string) => string | null;
4
9
  readonly isFile?: (path: string) => boolean;
5
10
  readonly isDir?: (path: string) => boolean;
11
+ /** List directory entries; throws on enum failure (fail-closed for live lifecycle dirs). */
12
+ readonly readdir?: (path: string) => string[];
6
13
  }
14
+ /** One scanned project envelope and its schema distance vs the framework target. */
15
+ export interface XbriefEnvelopeScanEntry {
16
+ readonly relativePath: string;
17
+ readonly declaredVersion: string | null;
18
+ readonly distance: XbriefSchemaDistance;
19
+ }
20
+ /**
21
+ * Scan live project xbrief envelopes (PROJECT-DEFINITION + pending/active) for
22
+ * schema distance vs the installed framework envelope major (#3243). Complements
23
+ * #2971 PROJECT-DEFINITION-only probe; remediation aligns with migrate hybrid
24
+ * rewrite (#3236).
25
+ */
26
+ export declare function scanXbriefEnvelopeVersions(projectRoot: string, seams?: CheckSeams, targetVersion?: string): {
27
+ readonly targetVersion: string;
28
+ readonly entries: readonly XbriefEnvelopeScanEntry[];
29
+ readonly worstDistance: XbriefSchemaDistance | null;
30
+ readonly behindMajor: readonly XbriefEnvelopeScanEntry[];
31
+ };
32
+ /**
33
+ * Fail closed when any scanned project xBRIEF envelope declares a major behind
34
+ * the framework target (#3243 / epic #3237 Q5 Option 2). Behind-minor does not
35
+ * fail this check. Remediation: `deft migrate:xbrief` (rewrites hybrid 0.6 on
36
+ * already-xbrief trees — #3236). Distinct from `stale-xbrief-schema-deposit`
37
+ * which routes schema-only drift to `directive update`.
38
+ */
39
+ export declare function checkXbriefEnvelopeMajorVersion(projectRoot: string, seams?: CheckSeams): CheckResult;
7
40
  export declare function checkQuickStartResolves(projectRoot: string, installRoot: string | null, seams?: CheckSeams): CheckResult;
8
41
  export declare function checkSkillPathsResolve(projectRoot: string, agentsMdText: string, seams?: CheckSeams): CheckResult;
9
42
  export declare function checkManifestAgreement(projectRoot: string, installRoot: string | null, seams?: CheckSeams): CheckResult;
@@ -60,6 +93,17 @@ export declare function checkGitignoreCoverage(projectRoot: string, seams?: Chec
60
93
  * Decided-off is quiet; dismiss-with-reason is pass with reason in detail.
61
94
  * Invalid typed blocks resolve fail-closed and surface via source=default-on-error.
62
95
  */
96
+ /**
97
+ * Fail closed when completed/ plan.status disagrees with the folder (#3242 AC1 / epic #3237 Q4).
98
+ * Open plan.items under completed/ are reported by `checkCompletedOpenItems` (exit-exempt
99
+ * until historical corpora are backfilled; scope:complete enforces items hard).
100
+ */
101
+ export declare function checkCompletedLifecycleConsistency(projectRoot: string): CheckResult;
102
+ /**
103
+ * Surface non-terminal plan.items under completed/ (#3242). Exit-exempt so
104
+ * historical pre-#2862 corpora do not red-light doctor; scope:complete fails closed.
105
+ */
106
+ export declare function checkCompletedOpenItems(projectRoot: string): CheckResult;
63
107
  export declare function checkCoverageCheckResumePolicy(projectRoot: string): CheckResult;
64
108
  export declare function deriveExitCode(checks: readonly CheckResult[], errors: readonly string[]): number;
65
109
  export declare function runChecksImpl(projectRoot: string, seams?: CheckSeams & {