@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.
Files changed (85) hide show
  1. package/dist/authz/classify.js +100 -23
  2. package/dist/authz/decompose-apply.d.ts +14 -0
  3. package/dist/authz/decompose-apply.js +93 -18
  4. package/dist/check/cached-orchestrator.d.ts +26 -5
  5. package/dist/check/cached-orchestrator.js +228 -6
  6. package/dist/check/gate-lists.d.ts +10 -4
  7. package/dist/check/gate-lists.js +19 -5
  8. package/dist/check/index.d.ts +3 -2
  9. package/dist/check/index.js +2 -1
  10. package/dist/check/named-cause.d.ts +45 -0
  11. package/dist/check/named-cause.js +121 -0
  12. package/dist/check/orchestrator.js +6 -1
  13. package/dist/doctor/help.d.ts +5 -0
  14. package/dist/doctor/help.js +31 -0
  15. package/dist/doctor/index.d.ts +2 -0
  16. package/dist/doctor/index.js +2 -0
  17. package/dist/doctor/main.js +58 -0
  18. package/dist/doctor/session-coda.d.ts +82 -0
  19. package/dist/doctor/session-coda.js +151 -0
  20. package/dist/doctor/types.d.ts +16 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +2 -0
  23. package/dist/init-deposit/gitignore.js +2 -0
  24. package/dist/intake/issue-ingest.js +7 -0
  25. package/dist/policy/ac-pass-banking.d.ts +58 -0
  26. package/dist/policy/ac-pass-banking.js +150 -0
  27. package/dist/policy/ceremony-dial.d.ts +29 -0
  28. package/dist/policy/ceremony-dial.js +76 -0
  29. package/dist/policy/index.d.ts +1 -0
  30. package/dist/policy/index.js +15 -1
  31. package/dist/preflight-cache/evaluate.d.ts +3 -0
  32. package/dist/preflight-cache/evaluate.js +3 -0
  33. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  34. package/dist/product-first-done-gate/acceptance.js +247 -0
  35. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  36. package/dist/product-first-done-gate/check-mode.js +148 -0
  37. package/dist/product-first-done-gate/evaluate.d.ts +50 -0
  38. package/dist/product-first-done-gate/evaluate.js +255 -0
  39. package/dist/product-first-done-gate/index.d.ts +11 -0
  40. package/dist/product-first-done-gate/index.js +11 -0
  41. package/dist/product-first-done-gate/types.d.ts +64 -0
  42. package/dist/product-first-done-gate/types.js +61 -0
  43. package/dist/run-summary/emit.d.ts +56 -0
  44. package/dist/run-summary/emit.js +171 -0
  45. package/dist/run-summary/index.d.ts +4 -0
  46. package/dist/run-summary/index.js +4 -0
  47. package/dist/run-summary/path.d.ts +25 -0
  48. package/dist/run-summary/path.js +78 -0
  49. package/dist/run-summary/types.d.ts +76 -0
  50. package/dist/run-summary/types.js +21 -0
  51. package/dist/scope/acceptance-evidence.d.ts +34 -0
  52. package/dist/scope/acceptance-evidence.js +84 -10
  53. package/dist/scope/decompose.d.ts +5 -0
  54. package/dist/scope/decompose.js +11 -2
  55. package/dist/session/ac-pass-banking.d.ts +221 -0
  56. package/dist/session/ac-pass-banking.js +761 -0
  57. package/dist/session/deposit-sha.d.ts +49 -0
  58. package/dist/session/deposit-sha.js +121 -0
  59. package/dist/session/effort-budget.d.ts +6 -0
  60. package/dist/session/effort-budget.js +21 -3
  61. package/dist/session/index.d.ts +5 -0
  62. package/dist/session/index.js +5 -0
  63. package/dist/session/orientation-compression.d.ts +127 -0
  64. package/dist/session/orientation-compression.js +425 -0
  65. package/dist/session/orientation-state.d.ts +29 -0
  66. package/dist/session/orientation-state.js +85 -0
  67. package/dist/session/session-start.d.ts +25 -0
  68. package/dist/session/session-start.js +175 -0
  69. package/dist/session/toolchain-preflight.d.ts +64 -0
  70. package/dist/session/toolchain-preflight.js +151 -0
  71. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  72. package/dist/triage/bootstrap/gitignore.js +8 -2
  73. package/dist/triage/bootstrap/index.js +15 -3
  74. package/dist/triage/classify/index.d.ts +1 -0
  75. package/dist/triage/classify/index.js +2 -0
  76. package/dist/triage/classify/label-mirror.js +16 -0
  77. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  78. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  79. package/dist/triage/reconcile/reconcile.js +28 -6
  80. package/dist/triage/summary/index.js +12 -4
  81. package/dist/triage/welcome/default-mode.js +16 -0
  82. package/dist/ts-check-lane/run-lane.js +21 -4
  83. package/dist/vbrief-validate/conformance.js +7 -0
  84. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  85. 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 is fast-before-slow (#3188): non-suite gates complete (or fail)
25
- * before any suite gate (`ts:check-lane` / vitest+coverage) is started. A
26
- * non-zero exit aborts the loop immediately the suite never starts after a
27
- * fast-gate failure (observable via `onGateStart` / suite start log).
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 gates = gatesForCheckTarget(target);
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: cheap preflight gates first; `ts:check-lane` (lint+build+
32
- * vitest coverage suite) last so a stale cache / orphan-active / branch miss
33
- * fails in seconds without starting the suite.
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[];
@@ -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: cheap preflight gates first; `ts:check-lane` (lint+build+
45
- * vitest coverage suite) last so a stale cache / orphan-active / branch miss
46
- * fails in seconds without starting the suite.
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
- // Cheap lifecycle / policy first (no suite co-list today; keep fail-fast order)
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",
@@ -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";
@@ -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
- process.stderr.write(`check: failed to invoke task: ${result.error.message}\n`);
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,5 @@
1
+ /**
2
+ * Doctor --help / -h text (#2712 session coda env docs + flag surface).
3
+ */
4
+ export declare function formatDoctorHelp(): string;
5
+ //# sourceMappingURL=help.d.ts.map
@@ -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
@@ -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";