@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.
- package/dist/authz/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +401 -75
- package/dist/authz/decompose-apply.d.ts +67 -0
- package/dist/authz/decompose-apply.js +302 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/consumer-check-contract/evaluate.d.ts +40 -0
- package/dist/consumer-check-contract/evaluate.js +188 -3
- package/dist/consumer-check-contract/index.d.ts +1 -1
- package/dist/consumer-check-contract/index.js +1 -1
- package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
- package/dist/content-contracts/skills/greptile-detector.js +202 -4
- package/dist/decision/index.d.ts +17 -0
- package/dist/decision/index.js +35 -0
- package/dist/decision/list.d.ts +47 -0
- package/dist/decision/list.js +250 -0
- package/dist/decision/schema.d.ts +88 -0
- package/dist/decision/schema.js +293 -0
- package/dist/decision/write.d.ts +82 -0
- package/dist/decision/write.js +427 -0
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +72 -79
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/eval/report.d.ts +29 -0
- package/dist/eval/report.js +69 -0
- package/dist/eval/run.d.ts +9 -0
- package/dist/eval/run.js +40 -4
- package/dist/eval/version-pin.d.ts +99 -0
- package/dist/eval/version-pin.js +181 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/intake/issue-ingest.js +29 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/platform/host-content-surface.d.ts +74 -0
- package/dist/platform/host-content-surface.js +214 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/ceremony-dial.d.ts +289 -0
- package/dist/policy/ceremony-dial.js +980 -0
- package/dist/policy/deft-directive-disable.js +12 -2
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/evaluate.js +10 -0
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/mergeability.js +5 -0
- package/dist/pr-merge-readiness/output.js +2 -0
- package/dist/pr-merge-readiness/parse.js +4 -0
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-merge-readiness/types.d.ts +6 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/render/framework-commands.js +4 -0
- package/dist/scope/acceptance-evidence.d.ts +76 -0
- package/dist/scope/acceptance-evidence.js +348 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.js +125 -11
- package/dist/scope/effort-activate-gate.d.ts +28 -0
- package/dist/scope/effort-activate-gate.js +64 -0
- package/dist/scope/index.d.ts +4 -0
- package/dist/scope/index.js +4 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +69 -2
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/effort-budget.d.ts +130 -0
- package/dist/session/effort-budget.js +334 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/session-start.d.ts +30 -1
- package/dist/session/session-start.js +234 -26
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
- package/dist/swarm/pre-dispatch-cli.js +143 -0
- package/dist/swarm/pre-dispatch.d.ts +87 -0
- package/dist/swarm/pre-dispatch.js +373 -0
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/vbrief-activate/activate.js +6 -0
- package/dist/vbrief-validate/constants.d.ts +2 -0
- package/dist/vbrief-validate/constants.js +2 -0
- package/dist/vbrief-validate/schema.js +4 -1
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- 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
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -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 & {
|