@deftai/directive-core 0.100.0 → 0.101.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/classify.js +100 -23
- package/dist/authz/decompose-apply.d.ts +14 -0
- package/dist/authz/decompose-apply.js +93 -18
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.js +58 -0
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +7 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +29 -0
- package/dist/policy/ceremony-dial.js +76 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +15 -1
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +34 -0
- package/dist/scope/acceptance-evidence.js +84 -10
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +11 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +6 -0
- package/dist/session/effort-budget.js +21 -3
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +25 -0
- package/dist/session/session-start.js +175 -0
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/package.json +11 -3
|
@@ -1,30 +1,60 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { join, resolve } from "node:path";
|
|
3
4
|
import { lintShippedRegistry, resolveTaskContract, runWithCache, } from "../cache/task-cache/index.js";
|
|
4
5
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
6
|
+
import { applyProductFirstGateMode, isHygieneGate, isProductAcGate, resolveProductFirstCheckMode, } from "../product-first-done-gate/index.js";
|
|
7
|
+
import { RunSummaryEmitter } from "../run-summary/emit.js";
|
|
8
|
+
import { runToolchainPreflight, SKIP_ALL_GATES, } from "../session/toolchain-preflight.js";
|
|
5
9
|
import { evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, } from "./consumer-gate-integrity.js";
|
|
6
10
|
import { resolveCheckTarget } from "./context.js";
|
|
7
11
|
import { checkGateId, checkGateSpawnArgs, gatesForCheckTarget, isSuiteCheckGate, } from "./gate-lists.js";
|
|
12
|
+
import { formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate } from "./named-cause.js";
|
|
8
13
|
function captureSpawn(taskBin, args, opts) {
|
|
9
14
|
const result = spawnSync(taskBin, args, {
|
|
10
15
|
cwd: opts.cwd,
|
|
11
16
|
encoding: "utf8",
|
|
12
17
|
env: opts.env ?? process.env,
|
|
13
18
|
});
|
|
19
|
+
if (result.error !== undefined) {
|
|
20
|
+
return {
|
|
21
|
+
exitCode: result.status ?? 1,
|
|
22
|
+
stdout: result.stdout ?? "",
|
|
23
|
+
stderr: result.stderr ?? "",
|
|
24
|
+
spawnError: result.error.message,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
14
27
|
return {
|
|
15
28
|
exitCode: result.status ?? 1,
|
|
16
29
|
stdout: result.stdout ?? "",
|
|
17
30
|
stderr: result.stderr ?? "",
|
|
18
31
|
};
|
|
19
32
|
}
|
|
33
|
+
function writeLines(lines, stream = "stderr") {
|
|
34
|
+
const write = stream === "stdout"
|
|
35
|
+
? process.stdout.write.bind(process.stdout)
|
|
36
|
+
: process.stderr.write.bind(process.stderr);
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
write(`${line}\n`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
20
41
|
/**
|
|
21
42
|
* Run check gates sequentially with content-hash caching (#1713).
|
|
22
43
|
* Falls back to fail-open execution for undeclared / non-cacheable gates.
|
|
23
44
|
*
|
|
24
|
-
* Gate order
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
45
|
+
* Gate order (#3284 / #3188):
|
|
46
|
+
* 1. Product AC (`verify:ac`) first — fail-fast; never skippable when commands exist
|
|
47
|
+
* 2. Hygiene preflight (may be advisory under pressure / degraded)
|
|
48
|
+
* 3. Suite last (`ts:check-lane`)
|
|
49
|
+
*
|
|
50
|
+
* Modes (env / ceremony dial / hard budget — see resolveProductFirstCheckMode):
|
|
51
|
+
* - full: AC hard → hygiene hard → suite
|
|
52
|
+
* - pressure: AC hard → hygiene advisory → suite
|
|
53
|
+
* - rapid: AC only (ceremony dial rapid/minimal positive content)
|
|
54
|
+
*
|
|
55
|
+
* #3282: toolchain preflight enables degraded skip report when go-task/pnpm
|
|
56
|
+
* are missing; gate failures print named cause + remedy; run-summary JSONL
|
|
57
|
+
* is appended when DEFT_RUN_SUMMARY_PATH is set (fail-open).
|
|
28
58
|
*/
|
|
29
59
|
export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}) {
|
|
30
60
|
const resolvedFramework = resolve(frameworkRoot);
|
|
@@ -33,8 +63,41 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
33
63
|
const taskBin = options.taskBin ?? "task";
|
|
34
64
|
const target = resolveCheckTarget(resolvedFramework, resolvedProject);
|
|
35
65
|
const cwd = target === "check:framework-source" ? resolvedFramework : resolvedProject;
|
|
36
|
-
const
|
|
66
|
+
const modeResolution = resolveProductFirstCheckMode({
|
|
67
|
+
environ: options.env ?? process.env,
|
|
68
|
+
projectRoot: resolvedProject,
|
|
69
|
+
});
|
|
70
|
+
const baseGates = gatesForCheckTarget(target);
|
|
71
|
+
const gates = applyProductFirstGateMode(baseGates, modeResolution.mode, checkGateId);
|
|
37
72
|
const codeVersion = readCorePackageVersion();
|
|
73
|
+
const sessionId = options.sessionId ?? randomUUID();
|
|
74
|
+
const gateOutcomes = [];
|
|
75
|
+
const emitSummary = (exitCode, degraded) => {
|
|
76
|
+
if (options.emitRunSummary === false)
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const emitter = new RunSummaryEmitter({
|
|
80
|
+
projectRoot: resolvedProject,
|
|
81
|
+
sessionId,
|
|
82
|
+
frameworkVersion: codeVersion,
|
|
83
|
+
env: options.env,
|
|
84
|
+
});
|
|
85
|
+
emitter.emitCheckInvocation({
|
|
86
|
+
target,
|
|
87
|
+
exit_code: exitCode,
|
|
88
|
+
degraded,
|
|
89
|
+
gates: gateOutcomes,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// fail-open
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (modeResolution.mode !== "full") {
|
|
97
|
+
process.stderr.write(`check: product-first mode=${modeResolution.mode} ` +
|
|
98
|
+
`(sources=${modeResolution.sources.join(",")}; ` +
|
|
99
|
+
`hygieneAdvisory=${modeResolution.hygieneAdvisory}; acOnly=${modeResolution.acOnly}) (#3284)\n`);
|
|
100
|
+
}
|
|
38
101
|
// #3070: fail loud with deposit-repair guidance when consumer check-graph
|
|
39
102
|
// includes (e.g. tasks/verify.yml for verify:orphan-active) are missing,
|
|
40
103
|
// instead of opaque go-task "Task does not exist" exit 200/201.
|
|
@@ -42,6 +105,7 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
42
105
|
const integrity = evaluateConsumerGateIntegrity(resolvedFramework);
|
|
43
106
|
if (!integrity.ok) {
|
|
44
107
|
process.stderr.write(formatConsumerGateIntegrityFailure(integrity));
|
|
108
|
+
emitSummary(2, false);
|
|
45
109
|
return 2;
|
|
46
110
|
}
|
|
47
111
|
}
|
|
@@ -50,22 +114,86 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
50
114
|
for (const finding of registryLint.findings.filter((f) => f.kind === "under-declared-input")) {
|
|
51
115
|
process.stderr.write(`check: task registry lint failed for ${finding.taskId}: ${finding.detail}\n`);
|
|
52
116
|
}
|
|
117
|
+
emitSummary(2, false);
|
|
53
118
|
return 2;
|
|
54
119
|
}
|
|
55
120
|
if (gates.length === 0) {
|
|
56
121
|
process.stderr.write(`check: no gate list for target ${target}\n`);
|
|
122
|
+
emitSummary(2, false);
|
|
57
123
|
return 2;
|
|
58
124
|
}
|
|
125
|
+
// #3282: toolchain preflight — degraded skip when framework tools missing.
|
|
126
|
+
const preflight = options.preflight === undefined
|
|
127
|
+
? runToolchainPreflight({
|
|
128
|
+
projectRoot: resolvedProject,
|
|
129
|
+
frameworkRoot: resolvedFramework,
|
|
130
|
+
})
|
|
131
|
+
: options.preflight;
|
|
132
|
+
// Expand SKIP_ALL_GATES sentinel to the live composition (avoids hardcode drift).
|
|
133
|
+
const skipSet = new Set();
|
|
134
|
+
for (const id of preflight?.skipGateIds ?? []) {
|
|
135
|
+
if (id === SKIP_ALL_GATES) {
|
|
136
|
+
for (const g of gates) {
|
|
137
|
+
skipSet.add(checkGateId(g));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
skipSet.add(id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const degraded = preflight?.degraded === true;
|
|
145
|
+
if (preflight?.degraded) {
|
|
146
|
+
for (const line of preflight.lines) {
|
|
147
|
+
process.stderr.write(`${line}\n`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// When task/node are missing, every gate is skipped — report named skips and
|
|
151
|
+
// exit 2 (config/environment), never exit 0 green (#3282 Greptile P1).
|
|
152
|
+
// Missing framework tooling must not look like a clean product pass.
|
|
153
|
+
if (degraded && skipSet.size > 0) {
|
|
154
|
+
const allSkipped = gates.every((g) => skipSet.has(checkGateId(g)));
|
|
155
|
+
if (allSkipped) {
|
|
156
|
+
const skipped = gates.map((g) => {
|
|
157
|
+
const id = checkGateId(g);
|
|
158
|
+
const cause = preflight?.findings.find((f) => !f.present)?.cause ?? "toolchain preflight degraded";
|
|
159
|
+
const remedy = preflight?.findings.find((f) => !f.present)?.remedy ?? remedyForGate(id, cause);
|
|
160
|
+
gateOutcomes.push({ id, status: "skipped", cause, remedy });
|
|
161
|
+
return { id, cause, remedy };
|
|
162
|
+
});
|
|
163
|
+
writeLines(formatDegradedSkipReport({
|
|
164
|
+
reason: "done-gate toolchain incomplete at check start",
|
|
165
|
+
skipped,
|
|
166
|
+
exitCode: 2,
|
|
167
|
+
}));
|
|
168
|
+
emitSummary(2, true);
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
59
172
|
for (const gateSpec of gates) {
|
|
60
173
|
const gateId = checkGateId(gateSpec);
|
|
174
|
+
// #3282: skip gates that require missing tools (e.g. pnpm-only suite).
|
|
175
|
+
if (skipSet.has(gateId)) {
|
|
176
|
+
const missing = preflight?.findings.find((f) => !f.present);
|
|
177
|
+
const cause = missing?.cause ?? "toolchain preflight marked gate skippable";
|
|
178
|
+
const remedy = missing?.remedy ?? remedyForGate(gateId, cause);
|
|
179
|
+
process.stderr.write(`check: skipping gate ${gateId} (degraded) — cause: ${cause}; remedy: ${remedy}\n`);
|
|
180
|
+
gateOutcomes.push({ id: gateId, status: "skipped", cause, remedy });
|
|
181
|
+
options.onGateStart?.(gateId);
|
|
182
|
+
options.onGateComplete?.(gateId, 0, false);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
61
185
|
// #3188: log suite entry so operators/tests can prove fast failures never
|
|
62
186
|
// reach vitest+coverage (suite gates are ordered last in gate-lists).
|
|
63
187
|
if (isSuiteCheckGate(gateSpec)) {
|
|
64
188
|
process.stderr.write(`check: starting suite gate ${gateId} after fast preflight (#3188)\n`);
|
|
65
189
|
}
|
|
190
|
+
if (isProductAcGate(gateId)) {
|
|
191
|
+
process.stderr.write(`check: product AC gate ${gateId} first (#3284)\n`);
|
|
192
|
+
}
|
|
66
193
|
options.onGateStart?.(gateId);
|
|
67
194
|
const contract = resolveTaskContract(gateId);
|
|
68
195
|
const taskArgs = checkGateSpawnArgs(gateSpec, taskfilePath);
|
|
196
|
+
let lastSpawn = { exitCode: 0, stdout: "", stderr: "" };
|
|
69
197
|
const result = runWithCache({
|
|
70
198
|
projectRoot: cwd,
|
|
71
199
|
contract,
|
|
@@ -78,6 +206,12 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
78
206
|
env: options.env,
|
|
79
207
|
})
|
|
80
208
|
: captureSpawn(taskBin, taskArgs, { cwd, env: options.env });
|
|
209
|
+
lastSpawn = {
|
|
210
|
+
exitCode: spawned.exitCode,
|
|
211
|
+
stdout: spawned.stdout,
|
|
212
|
+
stderr: spawned.stderr,
|
|
213
|
+
spawnError: "spawnError" in spawned ? spawned.spawnError : undefined,
|
|
214
|
+
};
|
|
81
215
|
if (spawned.stdout.length > 0) {
|
|
82
216
|
process.stdout.write(spawned.stdout);
|
|
83
217
|
}
|
|
@@ -88,17 +222,105 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
|
|
|
88
222
|
},
|
|
89
223
|
});
|
|
90
224
|
options.onGateComplete?.(gateId, result.exitCode, result.fromCache);
|
|
91
|
-
// Fail-fast: do not start later gates (including suite) after a failure
|
|
225
|
+
// Fail-fast: do not start later gates (including suite) after a failure —
|
|
226
|
+
// unless this is a hygiene gate under pressure mode (advisory only, #3284).
|
|
92
227
|
if (result.exitCode !== 0) {
|
|
228
|
+
if (modeResolution.hygieneAdvisory && isHygieneGate(gateId) && !isProductAcGate(gateId)) {
|
|
229
|
+
process.stderr.write(`check: hygiene gate ${gateId} failed (exit ${result.exitCode}) but is ADVISORY ` +
|
|
230
|
+
`under ${modeResolution.mode} mode — continuing (#3284)\n`);
|
|
231
|
+
const named = formatNamedCauseFailure({
|
|
232
|
+
gateId,
|
|
233
|
+
exitCode: result.exitCode,
|
|
234
|
+
stdout: lastSpawn.stdout,
|
|
235
|
+
stderr: lastSpawn.stderr,
|
|
236
|
+
spawnError: lastSpawn.spawnError,
|
|
237
|
+
});
|
|
238
|
+
writeLines(named.lines);
|
|
239
|
+
gateOutcomes.push({
|
|
240
|
+
id: gateId,
|
|
241
|
+
status: "run",
|
|
242
|
+
exit_code: result.exitCode,
|
|
243
|
+
cause: `advisory hygiene failure: ${named.cause}`,
|
|
244
|
+
remedy: named.remedy,
|
|
245
|
+
from_cache: result.fromCache,
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const named = formatNamedCauseFailure({
|
|
250
|
+
gateId,
|
|
251
|
+
exitCode: result.exitCode,
|
|
252
|
+
stdout: lastSpawn.stdout,
|
|
253
|
+
stderr: lastSpawn.stderr,
|
|
254
|
+
spawnError: lastSpawn.spawnError,
|
|
255
|
+
});
|
|
256
|
+
writeLines(named.lines);
|
|
257
|
+
gateOutcomes.push({
|
|
258
|
+
id: gateId,
|
|
259
|
+
status: "failed",
|
|
260
|
+
exit_code: result.exitCode,
|
|
261
|
+
cause: named.cause,
|
|
262
|
+
remedy: named.remedy,
|
|
263
|
+
from_cache: result.fromCache,
|
|
264
|
+
});
|
|
265
|
+
// Mark remaining as skipped for the summary (not run).
|
|
266
|
+
let sawCurrent = false;
|
|
267
|
+
for (const later of gates) {
|
|
268
|
+
const id = checkGateId(later);
|
|
269
|
+
if (!sawCurrent) {
|
|
270
|
+
if (id === gateId)
|
|
271
|
+
sawCurrent = true;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (!gateOutcomes.some((o) => o.id === id)) {
|
|
275
|
+
gateOutcomes.push({
|
|
276
|
+
id,
|
|
277
|
+
status: "skipped",
|
|
278
|
+
cause: `skipped after ${gateId} failed`,
|
|
279
|
+
remedy: "Fix the failed gate above, then re-run task check",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (isProductAcGate(gateId)) {
|
|
284
|
+
process.stderr.write(`check: product AC gate ${gateId} failed (exit ${result.exitCode}); ` +
|
|
285
|
+
`failing closed before hygiene (#3284)\n`);
|
|
286
|
+
emitSummary(result.exitCode, degraded);
|
|
287
|
+
return result.exitCode;
|
|
288
|
+
}
|
|
93
289
|
if (!isSuiteCheckGate(gateSpec)) {
|
|
94
290
|
const remaining = gates.some(isSuiteCheckGate)
|
|
95
291
|
? "skipping remaining gates including suite"
|
|
96
292
|
: "skipping remaining gates";
|
|
97
293
|
process.stderr.write(`check: fast gate ${gateId} failed (exit ${result.exitCode}); ${remaining} (#3188)\n`);
|
|
98
294
|
}
|
|
295
|
+
emitSummary(result.exitCode, degraded);
|
|
99
296
|
return result.exitCode;
|
|
100
297
|
}
|
|
298
|
+
gateOutcomes.push({
|
|
299
|
+
id: gateId,
|
|
300
|
+
status: "run",
|
|
301
|
+
exit_code: 0,
|
|
302
|
+
from_cache: result.fromCache,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// Partial degraded (some gates skipped for pnpm, others ran): never report a
|
|
306
|
+
// green pass when required suite/toolchain gates were skipped (#3282 Greptile P1).
|
|
307
|
+
const skippedOutcomes = gateOutcomes.filter((o) => o.status === "skipped");
|
|
308
|
+
if (degraded && skippedOutcomes.length > 0) {
|
|
309
|
+
const skipped = skippedOutcomes.map((o) => ({
|
|
310
|
+
id: o.id,
|
|
311
|
+
cause: o.cause ?? "degraded",
|
|
312
|
+
remedy: o.remedy ?? remedyForGate(o.id, o.cause ?? "degraded"),
|
|
313
|
+
}));
|
|
314
|
+
writeLines(formatDegradedSkipReport({
|
|
315
|
+
reason: "partial toolchain; skipped gates did not run",
|
|
316
|
+
skipped,
|
|
317
|
+
ran: gateOutcomes.filter((o) => o.status === "run").map((o) => o.id),
|
|
318
|
+
exitCode: 2,
|
|
319
|
+
}));
|
|
320
|
+
emitSummary(2, true);
|
|
321
|
+
return 2;
|
|
101
322
|
}
|
|
323
|
+
emitSummary(0, degraded);
|
|
102
324
|
return 0;
|
|
103
325
|
}
|
|
104
326
|
//# sourceMappingURL=cached-orchestrator.js.map
|
|
@@ -9,6 +9,11 @@ export type CheckGateSpec = string | {
|
|
|
9
9
|
readonly task: string;
|
|
10
10
|
readonly args?: readonly string[];
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Product-first AC gate (#3284): always first; soft-missing when no active
|
|
14
|
+
* xBRIEF so framework self-check is not deadlocked without a story.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PRODUCT_FIRST_AC_GATE: CheckGateSpec;
|
|
12
17
|
export declare function checkGateId(spec: CheckGateSpec): string;
|
|
13
18
|
/** Args for `task … --taskfile <path>` (optional `--` + public CLI flags). */
|
|
14
19
|
export declare function checkGateSpawnArgs(spec: CheckGateSpec, taskfilePath: string): string[];
|
|
@@ -26,11 +31,12 @@ export declare function isSuiteCheckGate(spec: CheckGateSpec | string): boolean;
|
|
|
26
31
|
*/
|
|
27
32
|
export declare function isFastBeforeSlowOrder(gates: readonly CheckGateSpec[]): boolean;
|
|
28
33
|
/**
|
|
29
|
-
* Framework-source check composition (#1713 / #3188).
|
|
34
|
+
* Framework-source check composition (#1713 / #3188 / #3284).
|
|
30
35
|
*
|
|
31
|
-
* Order contract:
|
|
32
|
-
*
|
|
33
|
-
*
|
|
36
|
+
* Order contract:
|
|
37
|
+
* 1. Product AC first (fail-fast; never skippable when commands exist) — #3284
|
|
38
|
+
* 2. Cheap hygiene preflight — #3188
|
|
39
|
+
* 3. `ts:check-lane` suite last
|
|
34
40
|
*/
|
|
35
41
|
export declare const FRAMEWORK_CHECK_GATES: readonly CheckGateSpec[];
|
|
36
42
|
export declare const CONSUMER_CHECK_GATES: readonly CheckGateSpec[];
|
package/dist/check/gate-lists.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/** Gate execution order mirrors Taskfile.yml check targets (#1713 dogfood). */
|
|
2
|
+
import { PRODUCT_AC_GATE_ID } from "../product-first-done-gate/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Product-first AC gate (#3284): always first; soft-missing when no active
|
|
5
|
+
* xBRIEF so framework self-check is not deadlocked without a story.
|
|
6
|
+
*/
|
|
7
|
+
export const PRODUCT_FIRST_AC_GATE = {
|
|
8
|
+
task: PRODUCT_AC_GATE_ID,
|
|
9
|
+
args: ["--soft-missing-xbrief"],
|
|
10
|
+
};
|
|
2
11
|
export function checkGateId(spec) {
|
|
3
12
|
return typeof spec === "string" ? spec : spec.task;
|
|
4
13
|
}
|
|
@@ -39,13 +48,16 @@ export function isFastBeforeSlowOrder(gates) {
|
|
|
39
48
|
return true;
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
42
|
-
* Framework-source check composition (#1713 / #3188).
|
|
51
|
+
* Framework-source check composition (#1713 / #3188 / #3284).
|
|
43
52
|
*
|
|
44
|
-
* Order contract:
|
|
45
|
-
*
|
|
46
|
-
*
|
|
53
|
+
* Order contract:
|
|
54
|
+
* 1. Product AC first (fail-fast; never skippable when commands exist) — #3284
|
|
55
|
+
* 2. Cheap hygiene preflight — #3188
|
|
56
|
+
* 3. `ts:check-lane` suite last
|
|
47
57
|
*/
|
|
48
58
|
export const FRAMEWORK_CHECK_GATES = [
|
|
59
|
+
// --- Product-first done-gate (#3284) — AC before any hygiene ---
|
|
60
|
+
PRODUCT_FIRST_AC_GATE,
|
|
49
61
|
// --- Fast preflight (seconds–few min) — #3188 ---
|
|
50
62
|
"verify:branch",
|
|
51
63
|
"verify:encoding",
|
|
@@ -89,7 +101,9 @@ export const FRAMEWORK_CHECK_GATES = [
|
|
|
89
101
|
"ts:check-lane",
|
|
90
102
|
];
|
|
91
103
|
export const CONSUMER_CHECK_GATES = [
|
|
92
|
-
//
|
|
104
|
+
// Product-first: stated AC before hygiene (#3284)
|
|
105
|
+
PRODUCT_FIRST_AC_GATE,
|
|
106
|
+
// Cheap lifecycle / policy second (no suite co-list today; keep fail-fast order)
|
|
93
107
|
"verify:branch",
|
|
94
108
|
"verify:cache-fresh",
|
|
95
109
|
"verify:wip-cap",
|
package/dist/check/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
|
|
1
|
+
export { type CachedCheckOptions, dispatchCachedTaskCheck } from "./cached-orchestrator.js";
|
|
2
2
|
export { CHECK_GRAPH_REQUIRED_NAMESPACES, CONSUMER_GATE_INTEGRITY_RECOVERY, type ConsumerGateIntegrityFinding, type ConsumerGateIntegrityResult, type ConsumerGateIntegritySeams, checkGraphOptionalIncludeViolations, evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, gateLocalName, gateNamespace, includeTaskfileRel, parseTaskfileIncludes, requiredNamespacesForGates, taskDefinedInTaskfileYaml, } from "./consumer-gate-integrity.js";
|
|
3
|
-
export { type CheckGateSpec, CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, isFastBeforeSlowOrder, isSuiteCheckGate, SUITE_CHECK_GATE_IDS, } from "./gate-lists.js";
|
|
3
|
+
export { type CheckGateSpec, CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, isFastBeforeSlowOrder, isSuiteCheckGate, PRODUCT_FIRST_AC_GATE, SUITE_CHECK_GATE_IDS, } from "./gate-lists.js";
|
|
4
|
+
export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, type NamedCauseMessage, remedyForGate, } from "./named-cause.js";
|
|
4
5
|
export type { CheckOrchestratorOptions, CheckOrchestratorSeams } from "./orchestrator.js";
|
|
5
6
|
export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
|
|
6
7
|
export { detectTestRunner, type RunnerDetectResult, runnerDetectionTable, type TestRunnerKind, } from "./runner-detect.js";
|
package/dist/check/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
|
|
2
2
|
export { CHECK_GRAPH_REQUIRED_NAMESPACES, CONSUMER_GATE_INTEGRITY_RECOVERY, checkGraphOptionalIncludeViolations, evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, gateLocalName, gateNamespace, includeTaskfileRel, parseTaskfileIncludes, requiredNamespacesForGates, taskDefinedInTaskfileYaml, } from "./consumer-gate-integrity.js";
|
|
3
|
-
export { CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, isFastBeforeSlowOrder, isSuiteCheckGate, SUITE_CHECK_GATE_IDS, } from "./gate-lists.js";
|
|
3
|
+
export { CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, isFastBeforeSlowOrder, isSuiteCheckGate, PRODUCT_FIRST_AC_GATE, SUITE_CHECK_GATE_IDS, } from "./gate-lists.js";
|
|
4
|
+
export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate, } from "./named-cause.js";
|
|
4
5
|
export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
|
|
5
6
|
export { detectTestRunner, runnerDetectionTable, } from "./runner-detect.js";
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named-cause + remedy formatting for check gate failures (#3282).
|
|
3
|
+
*
|
|
4
|
+
* Gate failures must never be bare exit 1: report gate name, cause, and remedy
|
|
5
|
+
* without embedding env values.
|
|
6
|
+
*/
|
|
7
|
+
export interface NamedCauseMessage {
|
|
8
|
+
readonly gateId: string;
|
|
9
|
+
readonly exitCode: number;
|
|
10
|
+
readonly cause: string;
|
|
11
|
+
readonly remedy: string;
|
|
12
|
+
readonly lines: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extract a short cause from gate stdout/stderr without leaking env values.
|
|
16
|
+
* Strips lines that look like KEY=value assignments.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractGateCause(stdout: string, stderr: string, exitCode: number, spawnError?: string): string;
|
|
19
|
+
export declare function remedyForGate(gateId: string, cause: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Format named-cause failure lines for a single gate.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatNamedCauseFailure(input: {
|
|
24
|
+
readonly gateId: string;
|
|
25
|
+
readonly exitCode: number;
|
|
26
|
+
readonly stdout?: string;
|
|
27
|
+
readonly stderr?: string;
|
|
28
|
+
readonly spawnError?: string;
|
|
29
|
+
}): NamedCauseMessage;
|
|
30
|
+
/**
|
|
31
|
+
* Format degraded-mode skip report (which gates skipped and why).
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatDegradedSkipReport(input: {
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
readonly skipped: readonly {
|
|
36
|
+
id: string;
|
|
37
|
+
cause: string;
|
|
38
|
+
remedy: string;
|
|
39
|
+
}[];
|
|
40
|
+
readonly ran?: readonly string[];
|
|
41
|
+
readonly failed?: readonly string[];
|
|
42
|
+
/** Default 2 = config/environment (never green-pass skipped required gates). */
|
|
43
|
+
readonly exitCode?: number;
|
|
44
|
+
}): readonly string[];
|
|
45
|
+
//# sourceMappingURL=named-cause.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named-cause + remedy formatting for check gate failures (#3282).
|
|
3
|
+
*
|
|
4
|
+
* Gate failures must never be bare exit 1: report gate name, cause, and remedy
|
|
5
|
+
* without embedding env values.
|
|
6
|
+
*/
|
|
7
|
+
/** Per-gate remedy hints (static; no env interpolation). */
|
|
8
|
+
const GATE_REMEDIES = {
|
|
9
|
+
"verify:branch": "Create a feature branch (`git switch -c feat/<name>`) or set plan.policy.allowDirectCommitsToMaster with confirmation",
|
|
10
|
+
"verify:encoding": "Fix non-ASCII / encoding issues flagged by the gate; re-run task verify:encoding",
|
|
11
|
+
"verify:cache-fresh": "Run task cache:fetch-all or task triage:bootstrap to refresh the cache",
|
|
12
|
+
"verify:orphan-active": "Complete or cancel active xBRIEFs whose issues are closed / PRs merged (task scope:complete / scope:cancel)",
|
|
13
|
+
"verify:wip-cap": "Demote stale pending scopes (task scope:demote) or raise plan.policy.wipCap deliberately",
|
|
14
|
+
doctor: "Run task doctor and follow the named recovery steps",
|
|
15
|
+
"toolchain:check": "Install missing maintainer tools reported by the gate (go, uv, git, gh, node, pnpm)",
|
|
16
|
+
"toolchain:check-consumer": "Install missing consumer tools: go-task, git, gh, node, pnpm (corepack enable && corepack prepare pnpm@latest --activate)",
|
|
17
|
+
"ts:check-lane": "Fix lint/type/test failures; re-run task ts:check-lane",
|
|
18
|
+
"vbrief:validate": "Fix xBRIEF/vBRIEF schema errors reported by the gate",
|
|
19
|
+
"verify-strategy-output": "Re-run strategy output or fix non-conformant scope filenames / PROJECT-DEFINITION",
|
|
20
|
+
"verify:test-boundary": "Move tests to the allowed placement or update plan.policy test-boundary allowlist",
|
|
21
|
+
"verify:scope-provenance": "Record approved scope provenance for the failing paths",
|
|
22
|
+
"verify:consumer-check-contract": "Align consumer Taskfile includes with the required gate graph",
|
|
23
|
+
"verify:forward-coverage": "Add tests covering new source files (task verify:forward-coverage)",
|
|
24
|
+
"verify:scm-boundary": "Move SCM mutations off GraphQL-heavy paths or wait for rate-limit reset",
|
|
25
|
+
"verify:license-sync": "Sync LICENSE / package license fields",
|
|
26
|
+
"verify:agents-md-budget": "Trim AGENTS.md managed section or raise plan.policy.agentsMdBudget deliberately",
|
|
27
|
+
};
|
|
28
|
+
const SPAWN_ERROR_REMEDY = "Install go-task (https://taskfile.dev/installation/) and ensure `task` is on PATH; then re-run task check";
|
|
29
|
+
/**
|
|
30
|
+
* Extract a short cause from gate stdout/stderr without leaking env values.
|
|
31
|
+
* Strips lines that look like KEY=value assignments.
|
|
32
|
+
*/
|
|
33
|
+
export function extractGateCause(stdout, stderr, exitCode, spawnError) {
|
|
34
|
+
if (spawnError !== undefined && spawnError.length > 0) {
|
|
35
|
+
// Normalize common missing-binary messages without path dumps.
|
|
36
|
+
if (/ENOENT|not found|not recognized/i.test(spawnError)) {
|
|
37
|
+
return "task binary not found on PATH (cannot spawn go-task)";
|
|
38
|
+
}
|
|
39
|
+
return sanitizeCauseLine(spawnError);
|
|
40
|
+
}
|
|
41
|
+
const combined = `${stderr}\n${stdout}`
|
|
42
|
+
.split(/\r?\n/)
|
|
43
|
+
.map((l) => l.trim())
|
|
44
|
+
.filter((l) => l.length > 0);
|
|
45
|
+
for (const line of combined) {
|
|
46
|
+
if (looksLikeEnvLeak(line))
|
|
47
|
+
continue;
|
|
48
|
+
if (line.startsWith("check:"))
|
|
49
|
+
continue;
|
|
50
|
+
return sanitizeCauseLine(line);
|
|
51
|
+
}
|
|
52
|
+
return `gate exited ${exitCode} without a diagnostic message`;
|
|
53
|
+
}
|
|
54
|
+
function looksLikeEnvLeak(line) {
|
|
55
|
+
// Avoid echoing DEFT_*= or generic env dumps.
|
|
56
|
+
if (/^[A-Z][A-Z0-9_]*=/.test(line))
|
|
57
|
+
return true;
|
|
58
|
+
if (/\bDEFT_[A-Z0-9_]+\b/.test(line) && line.includes("="))
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function sanitizeCauseLine(line) {
|
|
63
|
+
// Cap length; collapse absolute home paths lightly.
|
|
64
|
+
let out = line.replace(/\r/g, "").trim();
|
|
65
|
+
if (out.length > 240) {
|
|
66
|
+
out = `${out.slice(0, 237)}...`;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
export function remedyForGate(gateId, cause) {
|
|
71
|
+
if (/task binary not found|cannot spawn go-task/i.test(cause)) {
|
|
72
|
+
return SPAWN_ERROR_REMEDY;
|
|
73
|
+
}
|
|
74
|
+
if (/pnpm binary not found|pnpm: NOT FOUND/i.test(cause)) {
|
|
75
|
+
return "Enable pnpm: corepack enable && corepack prepare pnpm@latest --activate";
|
|
76
|
+
}
|
|
77
|
+
return (GATE_REMEDIES[gateId] ??
|
|
78
|
+
`Re-run the gate for details: task ${gateId} (or task check); fix the reported product/process defect`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Format named-cause failure lines for a single gate.
|
|
82
|
+
*/
|
|
83
|
+
export function formatNamedCauseFailure(input) {
|
|
84
|
+
const cause = extractGateCause(input.stdout ?? "", input.stderr ?? "", input.exitCode, input.spawnError);
|
|
85
|
+
const remedy = remedyForGate(input.gateId, cause);
|
|
86
|
+
const lines = [
|
|
87
|
+
`check: gate ${input.gateId} failed (exit ${input.exitCode})`,
|
|
88
|
+
` cause: ${cause}`,
|
|
89
|
+
` remedy: ${remedy}`,
|
|
90
|
+
];
|
|
91
|
+
return {
|
|
92
|
+
gateId: input.gateId,
|
|
93
|
+
exitCode: input.exitCode,
|
|
94
|
+
cause,
|
|
95
|
+
remedy,
|
|
96
|
+
lines,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format degraded-mode skip report (which gates skipped and why).
|
|
101
|
+
*/
|
|
102
|
+
export function formatDegradedSkipReport(input) {
|
|
103
|
+
const exitCode = input.exitCode ?? 2;
|
|
104
|
+
const lines = [
|
|
105
|
+
`check: degraded mode — ${input.reason}`,
|
|
106
|
+
`check: skipped ${input.skipped.length} gate(s) due to missing framework toolchain (#3282):`,
|
|
107
|
+
];
|
|
108
|
+
for (const gate of input.skipped) {
|
|
109
|
+
lines.push(` - ${gate.id}: cause: ${gate.cause}; remedy: ${gate.remedy}`);
|
|
110
|
+
}
|
|
111
|
+
if (input.ran !== undefined && input.ran.length > 0) {
|
|
112
|
+
lines.push(`check: ran: ${input.ran.join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
if (input.failed !== undefined && input.failed.length > 0) {
|
|
115
|
+
lines.push(`check: failed: ${input.failed.join(", ")}`);
|
|
116
|
+
}
|
|
117
|
+
lines.push(`check: exit ${exitCode} (degraded/config) — skipped required gates are not a green pass; ` +
|
|
118
|
+
"install missing tooling (see remedies above) and re-run task check");
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=named-cause.js.map
|
|
@@ -57,7 +57,12 @@ export function dispatchTaskCheck(frameworkRoot, projectRoot, seams = {}) {
|
|
|
57
57
|
timeoutMs: seams.timeoutMs,
|
|
58
58
|
});
|
|
59
59
|
if (result.error !== undefined) {
|
|
60
|
-
|
|
60
|
+
// #3282: named cause + remedy instead of bare spawn failure.
|
|
61
|
+
const detail = result.error.message;
|
|
62
|
+
const missingTask = /ENOENT|not found|not recognized/i.test(detail);
|
|
63
|
+
process.stderr.write(`check: gate ${target} failed (exit 2)\n` +
|
|
64
|
+
` cause: ${missingTask ? "task binary not found on PATH (cannot spawn go-task)" : detail}\n` +
|
|
65
|
+
" remedy: Install go-task (https://taskfile.dev/installation/) and ensure `task` is on PATH; then re-run task check\n");
|
|
61
66
|
return 2;
|
|
62
67
|
}
|
|
63
68
|
if (result.signal === "SIGTERM" && result.status === null && seams.timeoutMs !== undefined) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor --help / -h text (#2712 session coda env docs + flag surface).
|
|
3
|
+
*/
|
|
4
|
+
import { DOCTOR_ALLOWED_FLAGS } from "./constants.js";
|
|
5
|
+
import { sessionCodaHelpText } from "./session-coda.js";
|
|
6
|
+
export function formatDoctorHelp() {
|
|
7
|
+
const flags = DOCTOR_ALLOWED_FLAGS.join(", ");
|
|
8
|
+
return [
|
|
9
|
+
"Usage: deft doctor [options]",
|
|
10
|
+
"",
|
|
11
|
+
"Diagnose the Directive install and print the one next step.",
|
|
12
|
+
"",
|
|
13
|
+
"Options:",
|
|
14
|
+
" --session Session-oriented probe",
|
|
15
|
+
" --fix, --repair Attempt safe repairs (Taskfile / OpenClaw pins)",
|
|
16
|
+
" --json Machine-readable JSON (no human footer / no coda)",
|
|
17
|
+
" --quiet Suppress mid-run chatter; final summary still prints",
|
|
18
|
+
" --full Bypass throttle; re-probe fully",
|
|
19
|
+
" --network Allow offline-tier network checks (payload staleness)",
|
|
20
|
+
" --project-root <path> Project root (default: cwd)",
|
|
21
|
+
" --force Replace divergent OpenClaw pins during --fix",
|
|
22
|
+
" --openclaw-all-agents Wire always-pins into every OpenClaw workspace seat",
|
|
23
|
+
" -h, --help Show this help",
|
|
24
|
+
"",
|
|
25
|
+
`Allowed flags: ${flags}`,
|
|
26
|
+
"",
|
|
27
|
+
sessionCodaHelpText(),
|
|
28
|
+
"",
|
|
29
|
+
].join("\n");
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=help.js.map
|
package/dist/doctor/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./checks.js";
|
|
|
3
3
|
export * from "./constants.js";
|
|
4
4
|
export * from "./doctor-state.js";
|
|
5
5
|
export * from "./flags.js";
|
|
6
|
+
export * from "./help.js";
|
|
6
7
|
export * from "./json.js";
|
|
7
8
|
export * from "./main.js";
|
|
8
9
|
export * from "./manifest.js";
|
|
@@ -13,6 +14,7 @@ export * from "./openclaw-soft-rebind.js";
|
|
|
13
14
|
export * from "./paths.js";
|
|
14
15
|
export * from "./payload-staleness.js";
|
|
15
16
|
export * from "./release-availability.js";
|
|
17
|
+
export * from "./session-coda.js";
|
|
16
18
|
export * from "./taskfile.js";
|
|
17
19
|
export * from "./types.js";
|
|
18
20
|
export * from "./which.js";
|