@deftai/directive-core 0.100.0 → 0.102.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 (120) hide show
  1. package/dist/authz/classify.js +193 -27
  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 +30 -5
  5. package/dist/check/cached-orchestrator.js +297 -10
  6. package/dist/check/cli-native-gates.d.ts +43 -0
  7. package/dist/check/cli-native-gates.js +84 -0
  8. package/dist/check/gate-lists.d.ts +10 -4
  9. package/dist/check/gate-lists.js +19 -5
  10. package/dist/check/index.d.ts +4 -2
  11. package/dist/check/index.js +3 -1
  12. package/dist/check/named-cause.d.ts +45 -0
  13. package/dist/check/named-cause.js +131 -0
  14. package/dist/check/orchestrator.js +6 -1
  15. package/dist/doctor/checks.d.ts +5 -6
  16. package/dist/doctor/checks.js +13 -56
  17. package/dist/doctor/help.d.ts +5 -0
  18. package/dist/doctor/help.js +31 -0
  19. package/dist/doctor/index.d.ts +2 -0
  20. package/dist/doctor/index.js +2 -0
  21. package/dist/doctor/main.js +58 -0
  22. package/dist/doctor/session-coda.d.ts +82 -0
  23. package/dist/doctor/session-coda.js +151 -0
  24. package/dist/doctor/types.d.ts +16 -0
  25. package/dist/eval/later-graduation.d.ts +39 -0
  26. package/dist/eval/later-graduation.js +91 -0
  27. package/dist/eval/report.d.ts +6 -0
  28. package/dist/eval/report.js +27 -8
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.js +3 -0
  31. package/dist/init-deposit/gitignore.js +2 -0
  32. package/dist/intake/issue-ingest.js +49 -0
  33. package/dist/policy/ac-pass-banking.d.ts +58 -0
  34. package/dist/policy/ac-pass-banking.js +150 -0
  35. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  36. package/dist/policy/ceremony-dial-escalation.js +92 -0
  37. package/dist/policy/ceremony-dial.d.ts +32 -1
  38. package/dist/policy/ceremony-dial.js +128 -1
  39. package/dist/policy/check-resume.d.ts +9 -38
  40. package/dist/policy/check-resume.js +18 -156
  41. package/dist/policy/coverage-debt.d.ts +18 -37
  42. package/dist/policy/coverage-debt.js +38 -147
  43. package/dist/policy/index.d.ts +2 -1
  44. package/dist/policy/index.js +16 -2
  45. package/dist/preflight-cache/evaluate.d.ts +3 -0
  46. package/dist/preflight-cache/evaluate.js +3 -0
  47. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  48. package/dist/product-first-done-gate/acceptance.js +269 -0
  49. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  50. package/dist/product-first-done-gate/check-mode.js +148 -0
  51. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  52. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  53. package/dist/product-first-done-gate/evaluate.d.ts +76 -0
  54. package/dist/product-first-done-gate/evaluate.js +411 -0
  55. package/dist/product-first-done-gate/index.d.ts +12 -0
  56. package/dist/product-first-done-gate/index.js +12 -0
  57. package/dist/product-first-done-gate/types.d.ts +67 -0
  58. package/dist/product-first-done-gate/types.js +54 -0
  59. package/dist/run-summary/emit.d.ts +66 -0
  60. package/dist/run-summary/emit.js +217 -0
  61. package/dist/run-summary/index.d.ts +5 -0
  62. package/dist/run-summary/index.js +5 -0
  63. package/dist/run-summary/path.d.ts +25 -0
  64. package/dist/run-summary/path.js +78 -0
  65. package/dist/run-summary/share.d.ts +25 -0
  66. package/dist/run-summary/share.js +106 -0
  67. package/dist/run-summary/types.d.ts +134 -0
  68. package/dist/run-summary/types.js +28 -0
  69. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  70. package/dist/scope/acceptance-activate-gate.js +70 -0
  71. package/dist/scope/acceptance-evidence.d.ts +34 -0
  72. package/dist/scope/acceptance-evidence.js +84 -10
  73. package/dist/scope/decompose.d.ts +5 -0
  74. package/dist/scope/decompose.js +11 -2
  75. package/dist/scope/index.d.ts +1 -0
  76. package/dist/scope/index.js +1 -0
  77. package/dist/scope/transition.js +5 -0
  78. package/dist/session/ac-pass-banking.d.ts +221 -0
  79. package/dist/session/ac-pass-banking.js +761 -0
  80. package/dist/session/deposit-sha.d.ts +49 -0
  81. package/dist/session/deposit-sha.js +121 -0
  82. package/dist/session/effort-budget.d.ts +6 -0
  83. package/dist/session/effort-budget.js +21 -3
  84. package/dist/session/index.d.ts +5 -1
  85. package/dist/session/index.js +5 -1
  86. package/dist/session/orientation-compression.d.ts +127 -0
  87. package/dist/session/orientation-compression.js +435 -0
  88. package/dist/session/orientation-state.d.ts +29 -0
  89. package/dist/session/orientation-state.js +85 -0
  90. package/dist/session/session-start.d.ts +31 -0
  91. package/dist/session/session-start.js +219 -22
  92. package/dist/session/toolchain-preflight.d.ts +79 -0
  93. package/dist/session/toolchain-preflight.js +211 -0
  94. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  95. package/dist/triage/bootstrap/gitignore.js +8 -2
  96. package/dist/triage/bootstrap/index.js +15 -3
  97. package/dist/triage/classify/index.d.ts +1 -0
  98. package/dist/triage/classify/index.js +2 -0
  99. package/dist/triage/classify/label-mirror.js +16 -0
  100. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  101. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  102. package/dist/triage/reconcile/reconcile.js +28 -6
  103. package/dist/triage/summary/index.js +12 -4
  104. package/dist/triage/welcome/default-mode.js +16 -0
  105. package/dist/ts-check-lane/run-lane.js +21 -4
  106. package/dist/vbrief-validate/conformance.js +7 -0
  107. package/dist/verify-ac/clauses.d.ts +51 -0
  108. package/dist/verify-ac/clauses.js +490 -0
  109. package/dist/verify-ac/evaluate.d.ts +55 -0
  110. package/dist/verify-ac/evaluate.js +145 -0
  111. package/dist/verify-ac/flag.d.ts +35 -0
  112. package/dist/verify-ac/flag.js +104 -0
  113. package/dist/verify-ac/index.d.ts +10 -0
  114. package/dist/verify-ac/index.js +10 -0
  115. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  116. package/package.json +15 -3
  117. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  118. package/dist/policy/coverage-check-resume-presets.js +0 -228
  119. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  120. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -1,30 +1,63 @@
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";
5
+ import { defaultWhich } from "../doctor/which.js";
4
6
  import { readCorePackageVersion } from "../engine-version.js";
7
+ import { applyProductFirstGateMode, EMPTY_AC_CAUSE, EMPTY_AC_REMEDY, isHygieneGate, isProductAcGate, isSoftEmptyAcText, resolveProductFirstCheckMode, } from "../product-first-done-gate/index.js";
8
+ import { RunSummaryEmitter } from "../run-summary/emit.js";
9
+ import { runToolchainPreflight, SKIP_ALL_GATES, } from "../session/toolchain-preflight.js";
10
+ import { checkGateCliArgv, cliSpawnPlan, resolveGateDispatch, resolveGlobalCliBin, } from "./cli-native-gates.js";
5
11
  import { evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, } from "./consumer-gate-integrity.js";
6
12
  import { resolveCheckTarget } from "./context.js";
7
13
  import { checkGateId, checkGateSpawnArgs, gatesForCheckTarget, isSuiteCheckGate, } from "./gate-lists.js";
14
+ import { formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate } from "./named-cause.js";
8
15
  function captureSpawn(taskBin, args, opts) {
9
- const result = spawnSync(taskBin, args, {
16
+ const plan = opts.cli === true ? cliSpawnPlan(taskBin, args) : { command: taskBin, args };
17
+ const result = spawnSync(plan.command, plan.args, {
10
18
  cwd: opts.cwd,
11
19
  encoding: "utf8",
12
20
  env: opts.env ?? process.env,
13
21
  });
22
+ if (result.error !== undefined) {
23
+ return {
24
+ exitCode: result.status ?? 1,
25
+ stdout: result.stdout ?? "",
26
+ stderr: result.stderr ?? "",
27
+ spawnError: result.error.message,
28
+ };
29
+ }
14
30
  return {
15
31
  exitCode: result.status ?? 1,
16
32
  stdout: result.stdout ?? "",
17
33
  stderr: result.stderr ?? "",
18
34
  };
19
35
  }
36
+ function writeLines(lines, stream = "stderr") {
37
+ const write = stream === "stdout"
38
+ ? process.stdout.write.bind(process.stdout)
39
+ : process.stderr.write.bind(process.stderr);
40
+ for (const line of lines) {
41
+ write(`${line}\n`);
42
+ }
43
+ }
20
44
  /**
21
45
  * Run check gates sequentially with content-hash caching (#1713).
22
46
  * Falls back to fail-open execution for undeclared / non-cacheable gates.
23
47
  *
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).
48
+ * Gate order (#3284 / #3188):
49
+ * 1. Product AC (`verify:ac`) first fail-fast; never skippable when commands exist
50
+ * 2. Hygiene preflight (may be advisory under pressure / degraded)
51
+ * 3. Suite last (`ts:check-lane`)
52
+ *
53
+ * Modes (env / ceremony dial / hard budget — see resolveProductFirstCheckMode):
54
+ * - full: AC hard → hygiene hard → suite
55
+ * - pressure: AC hard → hygiene advisory → suite
56
+ * - rapid: AC only (ceremony dial rapid/minimal positive content)
57
+ *
58
+ * #3282: toolchain preflight enables degraded skip report when go-task/pnpm
59
+ * are missing; gate failures print named cause + remedy; run-summary JSONL
60
+ * is appended when DEFT_RUN_SUMMARY_PATH is set (fail-open).
28
61
  */
29
62
  export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}) {
30
63
  const resolvedFramework = resolve(frameworkRoot);
@@ -33,8 +66,42 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
33
66
  const taskBin = options.taskBin ?? "task";
34
67
  const target = resolveCheckTarget(resolvedFramework, resolvedProject);
35
68
  const cwd = target === "check:framework-source" ? resolvedFramework : resolvedProject;
36
- const gates = gatesForCheckTarget(target);
69
+ const modeResolution = resolveProductFirstCheckMode({
70
+ environ: options.env ?? process.env,
71
+ projectRoot: resolvedProject,
72
+ });
73
+ const baseGates = gatesForCheckTarget(target);
74
+ const gates = applyProductFirstGateMode(baseGates, modeResolution.mode, checkGateId);
37
75
  const codeVersion = readCorePackageVersion();
76
+ const sessionId = options.sessionId ?? randomUUID();
77
+ const gateOutcomes = [];
78
+ const emitSummary = (exitCode, degraded) => {
79
+ if (options.emitRunSummary === false)
80
+ return;
81
+ try {
82
+ const emitter = new RunSummaryEmitter({
83
+ projectRoot: resolvedProject,
84
+ sessionId,
85
+ frameworkVersion: codeVersion,
86
+ env: options.env,
87
+ });
88
+ emitter.emitCheckInvocation({
89
+ target,
90
+ exit_code: exitCode,
91
+ degraded,
92
+ gates: gateOutcomes,
93
+ });
94
+ emitter.emitKnownToolTurnDenominator();
95
+ }
96
+ catch {
97
+ // fail-open
98
+ }
99
+ };
100
+ if (modeResolution.mode !== "full") {
101
+ process.stderr.write(`check: product-first mode=${modeResolution.mode} ` +
102
+ `(sources=${modeResolution.sources.join(",")}; ` +
103
+ `hygieneAdvisory=${modeResolution.hygieneAdvisory}; acOnly=${modeResolution.acOnly}) (#3284)\n`);
104
+ }
38
105
  // #3070: fail loud with deposit-repair guidance when consumer check-graph
39
106
  // includes (e.g. tasks/verify.yml for verify:orphan-active) are missing,
40
107
  // instead of opaque go-task "Task does not exist" exit 200/201.
@@ -42,6 +109,7 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
42
109
  const integrity = evaluateConsumerGateIntegrity(resolvedFramework);
43
110
  if (!integrity.ok) {
44
111
  process.stderr.write(formatConsumerGateIntegrityFailure(integrity));
112
+ emitSummary(2, false);
45
113
  return 2;
46
114
  }
47
115
  }
@@ -50,22 +118,111 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
50
118
  for (const finding of registryLint.findings.filter((f) => f.kind === "under-declared-input")) {
51
119
  process.stderr.write(`check: task registry lint failed for ${finding.taskId}: ${finding.detail}\n`);
52
120
  }
121
+ emitSummary(2, false);
53
122
  return 2;
54
123
  }
55
124
  if (gates.length === 0) {
56
125
  process.stderr.write(`check: no gate list for target ${target}\n`);
126
+ emitSummary(2, false);
57
127
  return 2;
58
128
  }
129
+ // #3282: toolchain preflight — degraded skip when framework tools missing.
130
+ const which = options.which ?? defaultWhich;
131
+ const preflight = options.preflight === undefined
132
+ ? runToolchainPreflight({
133
+ projectRoot: resolvedProject,
134
+ frameworkRoot: resolvedFramework,
135
+ composedGates: gates,
136
+ consumerDeposit: target === "check:consumer",
137
+ which,
138
+ })
139
+ : options.preflight;
140
+ const taskPresent = preflight === null || !preflight.findings.some((f) => f.tool === "task" && !f.present);
141
+ const cliBin = options.cliBin !== undefined ? options.cliBin : resolveGlobalCliBin(which);
142
+ // Expand SKIP_ALL_GATES sentinel to the live composition (avoids hardcode drift).
143
+ const skipSet = new Set();
144
+ for (const id of preflight?.skipGateIds ?? []) {
145
+ if (id === SKIP_ALL_GATES) {
146
+ for (const g of gates) {
147
+ skipSet.add(checkGateId(g));
148
+ }
149
+ }
150
+ else {
151
+ skipSet.add(id);
152
+ }
153
+ }
154
+ const degraded = preflight?.degraded === true;
155
+ if (preflight?.degraded) {
156
+ for (const line of preflight.lines) {
157
+ process.stderr.write(`${line}\n`);
158
+ }
159
+ }
160
+ // When task/node are missing, every gate is skipped — report named skips and
161
+ // exit 2 (config/environment), never exit 0 green (#3282 Greptile P1).
162
+ // Missing framework tooling must not look like a clean product pass.
163
+ if (degraded && skipSet.size > 0) {
164
+ const allSkipped = gates.every((g) => skipSet.has(checkGateId(g)));
165
+ if (allSkipped) {
166
+ const skipped = gates.map((g) => {
167
+ const id = checkGateId(g);
168
+ const cause = preflight?.findings.find((f) => !f.present)?.cause ?? "toolchain preflight degraded";
169
+ const remedy = preflight?.findings.find((f) => !f.present)?.remedy ?? remedyForGate(id, cause);
170
+ gateOutcomes.push({ id, status: "skipped", cause, remedy });
171
+ return { id, cause, remedy };
172
+ });
173
+ writeLines(formatDegradedSkipReport({
174
+ reason: "done-gate toolchain incomplete at check start",
175
+ skipped,
176
+ exitCode: 2,
177
+ }));
178
+ emitSummary(2, true);
179
+ return 2;
180
+ }
181
+ }
59
182
  for (const gateSpec of gates) {
60
183
  const gateId = checkGateId(gateSpec);
184
+ // #3282: skip gates that require missing tools (e.g. pnpm-only suite).
185
+ if (skipSet.has(gateId)) {
186
+ const missing = preflight?.findings.find((f) => !f.present);
187
+ const cause = missing?.cause ?? "toolchain preflight marked gate skippable";
188
+ const remedy = missing?.remedy ?? remedyForGate(gateId, cause);
189
+ process.stderr.write(`check: skipping gate ${gateId} (degraded) — cause: ${cause}; remedy: ${remedy}\n`);
190
+ gateOutcomes.push({ id: gateId, status: "skipped", cause, remedy });
191
+ options.onGateStart?.(gateId);
192
+ options.onGateComplete?.(gateId, 0, false);
193
+ continue;
194
+ }
61
195
  // #3188: log suite entry so operators/tests can prove fast failures never
62
196
  // reach vitest+coverage (suite gates are ordered last in gate-lists).
63
197
  if (isSuiteCheckGate(gateSpec)) {
64
198
  process.stderr.write(`check: starting suite gate ${gateId} after fast preflight (#3188)\n`);
65
199
  }
200
+ if (isProductAcGate(gateId)) {
201
+ process.stderr.write(`check: product AC gate ${gateId} first (#3284)\n`);
202
+ }
66
203
  options.onGateStart?.(gateId);
67
204
  const contract = resolveTaskContract(gateId);
68
- const taskArgs = checkGateSpawnArgs(gateSpec, taskfilePath);
205
+ const dispatch = resolveGateDispatch({
206
+ gateId,
207
+ taskPresent,
208
+ cliBin,
209
+ });
210
+ if ("skip" in dispatch) {
211
+ process.stderr.write(`check: skipping gate ${gateId} (no runner) — cause: ${dispatch.cause}; remedy: ${dispatch.remedy}\n`);
212
+ gateOutcomes.push({
213
+ id: gateId,
214
+ status: "skipped",
215
+ cause: dispatch.cause,
216
+ remedy: dispatch.remedy,
217
+ });
218
+ options.onGateComplete?.(gateId, 0, false);
219
+ continue;
220
+ }
221
+ const spawnBin = dispatch.mode === "cli" ? dispatch.bin : taskBin;
222
+ const spawnArgs = dispatch.mode === "cli"
223
+ ? checkGateCliArgv(gateSpec)
224
+ : checkGateSpawnArgs(gateSpec, taskfilePath);
225
+ let lastSpawn = { exitCode: 0, stdout: "", stderr: "" };
69
226
  const result = runWithCache({
70
227
  projectRoot: cwd,
71
228
  contract,
@@ -73,11 +230,21 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
73
230
  noCache: options.noCache,
74
231
  runner: () => {
75
232
  const spawned = options.gateSpawnFn
76
- ? options.gateSpawnFn(gateId, taskBin, taskArgs, {
233
+ ? options.gateSpawnFn(gateId, spawnBin, spawnArgs, {
77
234
  cwd,
78
235
  env: options.env,
79
236
  })
80
- : captureSpawn(taskBin, taskArgs, { cwd, env: options.env });
237
+ : captureSpawn(spawnBin, spawnArgs, {
238
+ cwd,
239
+ env: options.env,
240
+ cli: dispatch.mode === "cli",
241
+ });
242
+ lastSpawn = {
243
+ exitCode: spawned.exitCode,
244
+ stdout: spawned.stdout,
245
+ stderr: spawned.stderr,
246
+ spawnError: "spawnError" in spawned ? spawned.spawnError : undefined,
247
+ };
81
248
  if (spawned.stdout.length > 0) {
82
249
  process.stdout.write(spawned.stdout);
83
250
  }
@@ -88,17 +255,137 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
88
255
  },
89
256
  });
90
257
  options.onGateComplete?.(gateId, result.exitCode, result.fromCache);
91
- // Fail-fast: do not start later gates (including suite) after a failure.
258
+ // Fail-fast: do not start later gates (including suite) after a failure
259
+ // unless this is a hygiene gate under pressure mode (advisory only, #3284).
92
260
  if (result.exitCode !== 0) {
261
+ const combinedOut = `${lastSpawn.stdout}\n${lastSpawn.stderr}`;
262
+ if (isProductAcGate(gateId) && isSoftEmptyAcText(combinedOut)) {
263
+ process.stderr.write(`check: verify:ac empty-resolution is not a green run (soft_empty); ` +
264
+ `degraded (#3334) — cause: ${EMPTY_AC_CAUSE}; remedy: ${EMPTY_AC_REMEDY}\n`);
265
+ gateOutcomes.push({
266
+ id: gateId,
267
+ status: "skipped",
268
+ exit_code: result.exitCode,
269
+ cause: EMPTY_AC_CAUSE,
270
+ remedy: EMPTY_AC_REMEDY,
271
+ from_cache: result.fromCache,
272
+ });
273
+ let sawCurrent = false;
274
+ for (const later of gates) {
275
+ const id = checkGateId(later);
276
+ if (!sawCurrent) {
277
+ if (id === gateId)
278
+ sawCurrent = true;
279
+ continue;
280
+ }
281
+ if (!gateOutcomes.some((o) => o.id === id)) {
282
+ gateOutcomes.push({
283
+ id,
284
+ status: "skipped",
285
+ cause: `skipped after ${gateId} soft_empty`,
286
+ remedy: EMPTY_AC_REMEDY,
287
+ });
288
+ }
289
+ }
290
+ emitSummary(1, true);
291
+ return 1;
292
+ }
293
+ if (modeResolution.hygieneAdvisory && isHygieneGate(gateId) && !isProductAcGate(gateId)) {
294
+ process.stderr.write(`check: hygiene gate ${gateId} failed (exit ${result.exitCode}) but is ADVISORY ` +
295
+ `under ${modeResolution.mode} mode — continuing (#3284)\n`);
296
+ const named = formatNamedCauseFailure({
297
+ gateId,
298
+ exitCode: result.exitCode,
299
+ stdout: lastSpawn.stdout,
300
+ stderr: lastSpawn.stderr,
301
+ spawnError: lastSpawn.spawnError,
302
+ });
303
+ writeLines(named.lines);
304
+ gateOutcomes.push({
305
+ id: gateId,
306
+ status: "run",
307
+ exit_code: result.exitCode,
308
+ cause: `advisory hygiene failure: ${named.cause}`,
309
+ remedy: named.remedy,
310
+ from_cache: result.fromCache,
311
+ });
312
+ continue;
313
+ }
314
+ const named = formatNamedCauseFailure({
315
+ gateId,
316
+ exitCode: result.exitCode,
317
+ stdout: lastSpawn.stdout,
318
+ stderr: lastSpawn.stderr,
319
+ spawnError: lastSpawn.spawnError,
320
+ });
321
+ writeLines(named.lines);
322
+ gateOutcomes.push({
323
+ id: gateId,
324
+ status: "failed",
325
+ exit_code: result.exitCode,
326
+ cause: named.cause,
327
+ remedy: named.remedy,
328
+ from_cache: result.fromCache,
329
+ });
330
+ // Mark remaining as skipped for the summary (not run).
331
+ let sawCurrent = false;
332
+ for (const later of gates) {
333
+ const id = checkGateId(later);
334
+ if (!sawCurrent) {
335
+ if (id === gateId)
336
+ sawCurrent = true;
337
+ continue;
338
+ }
339
+ if (!gateOutcomes.some((o) => o.id === id)) {
340
+ gateOutcomes.push({
341
+ id,
342
+ status: "skipped",
343
+ cause: `skipped after ${gateId} failed`,
344
+ remedy: "Fix the failed gate above, then re-run task check",
345
+ });
346
+ }
347
+ }
348
+ if (isProductAcGate(gateId)) {
349
+ process.stderr.write(`check: product AC gate ${gateId} failed (exit ${result.exitCode}); ` +
350
+ `failing closed before hygiene (#3284)\n`);
351
+ emitSummary(result.exitCode, degraded);
352
+ return result.exitCode;
353
+ }
93
354
  if (!isSuiteCheckGate(gateSpec)) {
94
355
  const remaining = gates.some(isSuiteCheckGate)
95
356
  ? "skipping remaining gates including suite"
96
357
  : "skipping remaining gates";
97
358
  process.stderr.write(`check: fast gate ${gateId} failed (exit ${result.exitCode}); ${remaining} (#3188)\n`);
98
359
  }
360
+ emitSummary(result.exitCode, degraded);
99
361
  return result.exitCode;
100
362
  }
363
+ gateOutcomes.push({
364
+ id: gateId,
365
+ status: "run",
366
+ exit_code: 0,
367
+ from_cache: result.fromCache,
368
+ });
369
+ }
370
+ // Partial degraded (some gates skipped for pnpm, others ran): never report a
371
+ // green pass when required suite/toolchain gates were skipped (#3282 Greptile P1).
372
+ const skippedOutcomes = gateOutcomes.filter((o) => o.status === "skipped");
373
+ if (degraded && skippedOutcomes.length > 0) {
374
+ const skipped = skippedOutcomes.map((o) => ({
375
+ id: o.id,
376
+ cause: o.cause ?? "degraded",
377
+ remedy: o.remedy ?? remedyForGate(o.id, o.cause ?? "degraded"),
378
+ }));
379
+ writeLines(formatDegradedSkipReport({
380
+ reason: "partial toolchain; skipped gates did not run",
381
+ skipped,
382
+ ran: gateOutcomes.filter((o) => o.status === "run").map((o) => o.id),
383
+ exitCode: 2,
384
+ }));
385
+ emitSummary(2, true);
386
+ return 2;
101
387
  }
388
+ emitSummary(0, degraded);
102
389
  return 0;
103
390
  }
104
391
  //# sourceMappingURL=cached-orchestrator.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * CLI-native check-gate dispatch (#3335).
3
+ *
4
+ * Consumer `verify:*` (and sibling CLI verbs) must run via global
5
+ * `deft`/`directive` when go-task is absent. Coordinate with the #3324
6
+ * engine-invoke deposit marker: same global-CLI surface, no install-go-task
7
+ * remedy when every composed gate is CLI-dispatchable.
8
+ */
9
+ import type { CheckGateSpec } from "./gate-lists.js";
10
+ /** Install the published CLI — not go-task — when a deposit has no global deft. */
11
+ export declare const GLOBAL_CLI_REMEDY = "Install: npm i -g @deftai/directive@latest";
12
+ export type GateDispatchMode = "task" | "cli";
13
+ /** True when the gate is a native CLI verb and does not require go-task. */
14
+ export declare function isCliNativeGate(gateId: string): boolean;
15
+ export declare function allGatesCliDispatchable(gates: readonly CheckGateSpec[]): boolean;
16
+ /**
17
+ * Argv for `deft <verb> …` (no go-task `--` separator, no `--taskfile`).
18
+ */
19
+ export declare function checkGateCliArgv(spec: CheckGateSpec): string[];
20
+ export declare function resolveGlobalCliBin(which: (name: string) => string | null): string | null;
21
+ /** Quote one argv token for `cmd.exe /d /s /c` (mirrors tasks/engine-invoke.cjs). */
22
+ export declare function quoteWin32Arg(arg: string): string;
23
+ /**
24
+ * Spawn plan for the global CLI. Win32 uses cmd.exe so `.cmd` shims run
25
+ * without `shell: true` (#3324 / #2547).
26
+ */
27
+ export declare function cliSpawnPlan(cliBin: string, argv: readonly string[], platform?: NodeJS.Platform): {
28
+ command: string;
29
+ args: string[];
30
+ };
31
+ export declare function resolveGateDispatch(input: {
32
+ readonly gateId: string;
33
+ readonly taskPresent: boolean;
34
+ readonly cliBin: string | null;
35
+ }): {
36
+ readonly mode: GateDispatchMode;
37
+ readonly bin: string;
38
+ } | {
39
+ readonly skip: true;
40
+ readonly cause: string;
41
+ readonly remedy: string;
42
+ };
43
+ //# sourceMappingURL=cli-native-gates.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * CLI-native check-gate dispatch (#3335).
3
+ *
4
+ * Consumer `verify:*` (and sibling CLI verbs) must run via global
5
+ * `deft`/`directive` when go-task is absent. Coordinate with the #3324
6
+ * engine-invoke deposit marker: same global-CLI surface, no install-go-task
7
+ * remedy when every composed gate is CLI-dispatchable.
8
+ */
9
+ import { checkGateId } from "./gate-lists.js";
10
+ /** Install the published CLI — not go-task — when a deposit has no global deft. */
11
+ export const GLOBAL_CLI_REMEDY = "Install: npm i -g @deftai/directive@latest";
12
+ const WIN32_CMD_METACHAR_RE = /[\s"&|<>^()%!]/;
13
+ /** True when the gate is a native CLI verb and does not require go-task. */
14
+ export function isCliNativeGate(gateId) {
15
+ if (gateId.startsWith("verify:") || gateId.startsWith("verify-"))
16
+ return true;
17
+ return (gateId === "doctor" ||
18
+ gateId === "vbrief:validate" ||
19
+ gateId === "toolchain:check" ||
20
+ gateId === "toolchain:check-consumer" ||
21
+ gateId === "ts:check-lane");
22
+ }
23
+ export function allGatesCliDispatchable(gates) {
24
+ return gates.every((spec) => isCliNativeGate(checkGateId(spec)));
25
+ }
26
+ /**
27
+ * Argv for `deft <verb> …` (no go-task `--` separator, no `--taskfile`).
28
+ */
29
+ export function checkGateCliArgv(spec) {
30
+ const id = checkGateId(spec);
31
+ const extra = typeof spec === "string" ? [] : (spec.args ?? []);
32
+ if (id === "toolchain:check-consumer") {
33
+ return ["toolchain-check", "--consumer", ...extra];
34
+ }
35
+ if (id === "toolchain:check") {
36
+ return ["toolchain-check", ...extra];
37
+ }
38
+ if (id === "ts:check-lane") {
39
+ return ["ts-check-lane", ...extra];
40
+ }
41
+ return [id, ...extra];
42
+ }
43
+ export function resolveGlobalCliBin(which) {
44
+ return which("deft") ?? which("directive");
45
+ }
46
+ /** Quote one argv token for `cmd.exe /d /s /c` (mirrors tasks/engine-invoke.cjs). */
47
+ export function quoteWin32Arg(arg) {
48
+ if (arg.length > 0 && !WIN32_CMD_METACHAR_RE.test(arg)) {
49
+ return arg;
50
+ }
51
+ return `"${arg.replace(/"/g, '""')}"`;
52
+ }
53
+ /**
54
+ * Spawn plan for the global CLI. Win32 uses cmd.exe so `.cmd` shims run
55
+ * without `shell: true` (#3324 / #2547).
56
+ */
57
+ export function cliSpawnPlan(cliBin, argv, platform = process.platform) {
58
+ if (platform === "win32") {
59
+ const commandLine = [cliBin, ...argv].map(quoteWin32Arg).join(" ");
60
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", commandLine] };
61
+ }
62
+ return { command: cliBin, args: [...argv] };
63
+ }
64
+ export function resolveGateDispatch(input) {
65
+ if (input.taskPresent) {
66
+ return { mode: "task", bin: "task" };
67
+ }
68
+ if (isCliNativeGate(input.gateId) && input.cliBin !== null) {
69
+ return { mode: "cli", bin: input.cliBin };
70
+ }
71
+ if (isCliNativeGate(input.gateId)) {
72
+ return {
73
+ skip: true,
74
+ cause: "no go-task and no global deft/directive CLI",
75
+ remedy: GLOBAL_CLI_REMEDY,
76
+ };
77
+ }
78
+ return {
79
+ skip: true,
80
+ cause: "go-task binary not found on PATH",
81
+ remedy: "Install go-task: https://taskfile.dev/installation/",
82
+ };
83
+ }
84
+ //# sourceMappingURL=cli-native-gates.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,8 @@
1
- export { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
1
+ export { type CachedCheckOptions, dispatchCachedTaskCheck } from "./cached-orchestrator.js";
2
+ export { allGatesCliDispatchable, checkGateCliArgv, cliSpawnPlan, GLOBAL_CLI_REMEDY, isCliNativeGate, resolveGateDispatch, resolveGlobalCliBin, } from "./cli-native-gates.js";
2
3
  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";
4
+ 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";
5
+ export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, type NamedCauseMessage, remedyForGate, } from "./named-cause.js";
4
6
  export type { CheckOrchestratorOptions, CheckOrchestratorSeams } from "./orchestrator.js";
5
7
  export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
6
8
  export { detectTestRunner, type RunnerDetectResult, runnerDetectionTable, type TestRunnerKind, } from "./runner-detect.js";
@@ -1,6 +1,8 @@
1
1
  export { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
2
+ export { allGatesCliDispatchable, checkGateCliArgv, cliSpawnPlan, GLOBAL_CLI_REMEDY, isCliNativeGate, resolveGateDispatch, resolveGlobalCliBin, } from "./cli-native-gates.js";
2
3
  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";
4
+ export { CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, isFastBeforeSlowOrder, isSuiteCheckGate, PRODUCT_FIRST_AC_GATE, SUITE_CHECK_GATE_IDS, } from "./gate-lists.js";
5
+ export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate, } from "./named-cause.js";
4
6
  export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
5
7
  export { detectTestRunner, runnerDetectionTable, } from "./runner-detect.js";
6
8
  //# 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