@deftai/directive-core 0.101.0 → 0.103.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 (75) hide show
  1. package/dist/authz/classify.js +97 -8
  2. package/dist/check/cached-orchestrator.d.ts +4 -0
  3. package/dist/check/cached-orchestrator.js +70 -5
  4. package/dist/check/cli-native-gates.d.ts +43 -0
  5. package/dist/check/cli-native-gates.js +84 -0
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.js +10 -0
  9. package/dist/doctor/checks.d.ts +5 -6
  10. package/dist/doctor/checks.js +13 -56
  11. package/dist/eval/later-graduation.d.ts +39 -0
  12. package/dist/eval/later-graduation.js +91 -0
  13. package/dist/eval/report.d.ts +6 -0
  14. package/dist/eval/report.js +27 -8
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/init-deposit/hygiene.d.ts +7 -1
  18. package/dist/init-deposit/hygiene.js +11 -3
  19. package/dist/init-deposit/scaffold.d.ts +12 -0
  20. package/dist/init-deposit/scaffold.js +81 -8
  21. package/dist/intake/issue-ingest.js +42 -0
  22. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  23. package/dist/policy/ceremony-dial-escalation.js +92 -0
  24. package/dist/policy/ceremony-dial.d.ts +3 -1
  25. package/dist/policy/ceremony-dial.js +52 -1
  26. package/dist/policy/check-resume.d.ts +9 -38
  27. package/dist/policy/check-resume.js +18 -156
  28. package/dist/policy/coverage-debt.d.ts +18 -37
  29. package/dist/policy/coverage-debt.js +38 -147
  30. package/dist/policy/index.d.ts +1 -1
  31. package/dist/policy/index.js +1 -1
  32. package/dist/product-first-done-gate/acceptance.js +22 -0
  33. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  34. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  35. package/dist/product-first-done-gate/evaluate.d.ts +40 -2
  36. package/dist/product-first-done-gate/evaluate.js +253 -61
  37. package/dist/product-first-done-gate/index.d.ts +2 -1
  38. package/dist/product-first-done-gate/index.js +2 -1
  39. package/dist/product-first-done-gate/types.d.ts +3 -0
  40. package/dist/product-first-done-gate/types.js +0 -7
  41. package/dist/run-summary/emit.d.ts +11 -1
  42. package/dist/run-summary/emit.js +48 -2
  43. package/dist/run-summary/index.d.ts +2 -1
  44. package/dist/run-summary/index.js +2 -1
  45. package/dist/run-summary/path.d.ts +1 -1
  46. package/dist/run-summary/path.js +1 -1
  47. package/dist/run-summary/share.d.ts +25 -0
  48. package/dist/run-summary/share.js +106 -0
  49. package/dist/run-summary/types.d.ts +60 -2
  50. package/dist/run-summary/types.js +7 -0
  51. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  52. package/dist/scope/acceptance-activate-gate.js +70 -0
  53. package/dist/scope/index.d.ts +1 -0
  54. package/dist/scope/index.js +1 -0
  55. package/dist/scope/transition.js +5 -0
  56. package/dist/session/index.d.ts +0 -1
  57. package/dist/session/index.js +0 -1
  58. package/dist/session/orientation-compression.js +19 -9
  59. package/dist/session/session-start.d.ts +6 -0
  60. package/dist/session/session-start.js +44 -22
  61. package/dist/session/toolchain-preflight.d.ts +15 -0
  62. package/dist/session/toolchain-preflight.js +65 -5
  63. package/dist/verify-ac/clauses.d.ts +51 -0
  64. package/dist/verify-ac/clauses.js +490 -0
  65. package/dist/verify-ac/evaluate.d.ts +73 -0
  66. package/dist/verify-ac/evaluate.js +167 -0
  67. package/dist/verify-ac/flag.d.ts +35 -0
  68. package/dist/verify-ac/flag.js +104 -0
  69. package/dist/verify-ac/index.d.ts +10 -0
  70. package/dist/verify-ac/index.js +10 -0
  71. package/package.json +7 -3
  72. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  73. package/dist/policy/coverage-check-resume-presets.js +0 -228
  74. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  75. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -218,10 +218,37 @@ const DOWNLOADER_DECODER_BINS = new Set([
218
218
  "aria2",
219
219
  "mbuffer",
220
220
  "cpio",
221
+ // #3311 residual after #3288: versioned / alternate write bins.
222
+ "gpg2",
223
+ "gpg1",
224
+ "rage",
225
+ "xh",
226
+ "httpie",
227
+ "wcurl",
228
+ "curlie",
229
+ "uudecode",
230
+ "iconv",
231
+ "gtar",
232
+ "star",
233
+ "gnutar",
234
+ "pax",
235
+ // #3336 residual after #3311: further write bins (oras/ncat/patch peers).
236
+ "oras",
237
+ "lwp-download",
238
+ "ncat",
239
+ "patch",
240
+ "base32",
241
+ "lrzip",
242
+ "unar",
243
+ "cabextract",
244
+ "ditto",
245
+ "dpkg-deb",
246
+ "hg",
247
+ "msguniq",
221
248
  ]);
222
249
  /**
223
250
  * Archive extractors / alt writers that can plant via pathish operands without
224
- * shell redirects (#3245 / #3288). Used for pathish authz/kill scans (prefer deny)
251
+ * shell redirects (#3245 / #3288 / #3311 / #3336). Used for pathish authz/kill scans (prefer deny)
225
252
  * and write-shape residual under UAT — not bare curl-class URL fetches.
226
253
  */
227
254
  const ARCHIVE_ALT_WRITE_BINS = new Set([
@@ -250,9 +277,36 @@ const ARCHIVE_ALT_WRITE_BINS = new Set([
250
277
  "aria2",
251
278
  "mbuffer",
252
279
  "cpio",
280
+ // #3311 residual after #3288.
281
+ "gpg2",
282
+ "gpg1",
283
+ "rage",
284
+ "xh",
285
+ "httpie",
286
+ "wcurl",
287
+ "curlie",
288
+ "uudecode",
289
+ "iconv",
290
+ "gtar",
291
+ "star",
292
+ "gnutar",
293
+ "pax",
294
+ // #3336 residual after #3311.
295
+ "oras",
296
+ "lwp-download",
297
+ "ncat",
298
+ "patch",
299
+ "base32",
300
+ "lrzip",
301
+ "unar",
302
+ "cabextract",
303
+ "ditto",
304
+ "dpkg-deb",
305
+ "hg",
306
+ "msguniq",
253
307
  ]);
254
308
  /**
255
- * Bins whose pathish operands are scanned for authz/kill destinations (#3213 / #3245 / #3288).
309
+ * Bins whose pathish operands are scanned for authz/kill destinations (#3213 / #3245 / #3288 / #3311 / #3336).
256
310
  * Prefer a Set over a long `||` chain so coverage counts one membership check, not N branches.
257
311
  */
258
312
  const PROTECTED_POSITIONAL_BINS = new Set([
@@ -275,6 +329,29 @@ const PROTECTED_POSITIONAL_BINS = new Set([
275
329
  "zstd",
276
330
  "unzstd",
277
331
  "mbuffer",
332
+ // #3311 residual: versioned crypto + archive/decoder peers.
333
+ "gpg2",
334
+ "gpg1",
335
+ "rage",
336
+ "uudecode",
337
+ "iconv",
338
+ "gtar",
339
+ "star",
340
+ "gnutar",
341
+ "pax",
342
+ // #3336 residual: positional dest writers (ditto/dpkg-deb/hg/lwp-download).
343
+ "oras",
344
+ "lwp-download",
345
+ "ncat",
346
+ "patch",
347
+ "base32",
348
+ "lrzip",
349
+ "unar",
350
+ "cabextract",
351
+ "ditto",
352
+ "dpkg-deb",
353
+ "hg",
354
+ "msguniq",
278
355
  ]);
279
356
  /** wget family (directory-prefix dest flags). */
280
357
  const WGET_FAMILY_BINS = new Set(["wget", "wget2"]);
@@ -282,8 +359,10 @@ const WGET_FAMILY_BINS = new Set(["wget", "wget2"]);
282
359
  const ARIA2_FAMILY_BINS = new Set(["aria2c", "aria2"]);
283
360
  /** 7z family (attached -oDIR only). */
284
361
  const SEVEN_Z_FAMILY_BINS = new Set(["7z", "7za", "7zr"]);
285
- /** tar family (chdir -C / --directory). */
286
- const TAR_FAMILY_BINS = new Set(["tar", "bsdtar"]);
362
+ /** tar family (chdir -C / --directory). Includes GNU/Schily aliases (#3311). */
363
+ const TAR_FAMILY_BINS = new Set(["tar", "bsdtar", "gtar", "star", "gnutar"]);
364
+ /** xh / httpie family (download-dir dest flags) (#3311). */
365
+ const XH_FAMILY_BINS = new Set(["xh", "httpie"]);
287
366
  /**
288
367
  * File destination flags for downloaders/decoders (#3206).
289
368
  * normalizeToken lowercases, so wget `-O` and curl `-o` share `-o`.
@@ -294,8 +373,13 @@ const DOWNLOADER_FILE_DEST_FLAGS = new Set([
294
373
  "-o",
295
374
  "--output",
296
375
  "--output-document",
376
+ "--output-file",
297
377
  "-out",
298
378
  "--out",
379
+ "--outfile",
380
+ // unar single-dash long form; GNU/others use --output-directory.
381
+ "-output-directory",
382
+ "--output-directory",
299
383
  ]);
300
384
  /** Directory destination flags (curl --output-dir / wget -P / aria2c -d); bin-scoped below. */
301
385
  const CURL_DIR_DEST_FLAGS = new Set(["--output-dir"]);
@@ -307,8 +391,11 @@ const ARIA2C_DIR_DEST_FLAGS = new Set(["-d", "--dir"]);
307
391
  */
308
392
  const TAR_DIR_DEST_FLAGS_EXACT = new Set(["-C"]);
309
393
  const TAR_DIR_DEST_FLAGS_LOWER = new Set(["--directory"]);
310
- /** unzip extract directory (#3245). */
394
+ /** unzip / cabextract extract directory (#3245 / #3336). */
311
395
  const UNZIP_DIR_DEST_FLAGS = new Set(["-d"]);
396
+ const UNZIP_DIR_DEST_BINS = new Set(["unzip", "cabextract"]);
397
+ /** xh / httpie download directory (#3311). */
398
+ const XH_DIR_DEST_FLAGS = new Set(["--download-dir"]);
312
399
  /**
313
400
  * cpio chdir before extract/create (#3288).
314
401
  * Case-sensitive short form: POSIX cpio uses capital `-D`; lower `-d` is a create option bit.
@@ -383,7 +470,9 @@ function isDownloaderDestFlag(flag, bin, rawFlag) {
383
470
  return true;
384
471
  return false;
385
472
  }
386
- if (bin === "unzip" && UNZIP_DIR_DEST_FLAGS.has(flag))
473
+ if (UNZIP_DIR_DEST_BINS.has(bin) && UNZIP_DIR_DEST_FLAGS.has(flag))
474
+ return true;
475
+ if (XH_FAMILY_BINS.has(bin) && XH_DIR_DEST_FLAGS.has(flag))
387
476
  return true;
388
477
  return false;
389
478
  }
@@ -551,8 +640,8 @@ function downloaderDecoderDestinations(tokens) {
551
640
  i++;
552
641
  continue;
553
642
  }
554
- // unzip attached -dDIR (#3245)
555
- if (bin === "unzip" && n.startsWith("-d") && n.length > 2) {
643
+ // unzip / cabextract attached -dDIR (#3245 / #3336)
644
+ if (UNZIP_DIR_DEST_BINS.has(bin) && n.startsWith("-d") && n.length > 2) {
556
645
  dests.push(pathishToken(raw.slice(2)));
557
646
  i++;
558
647
  continue;
@@ -19,6 +19,10 @@ export interface CachedCheckOptions extends CheckOrchestratorSeams {
19
19
  readonly sessionId?: string;
20
20
  /** #3282: disable run-summary emission (tests). */
21
21
  readonly emitRunSummary?: boolean;
22
+ /** PATH lookup seam (tests). */
23
+ readonly which?: (name: string) => string | null;
24
+ /** Override global CLI binary when dispatching CLI-native gates (#3335). */
25
+ readonly cliBin?: string | null;
22
26
  }
23
27
  /**
24
28
  * Run check gates sequentially with content-hash caching (#1713).
@@ -2,16 +2,19 @@ import { spawnSync } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { join, resolve } from "node:path";
4
4
  import { lintShippedRegistry, resolveTaskContract, runWithCache, } from "../cache/task-cache/index.js";
5
+ import { defaultWhich } from "../doctor/which.js";
5
6
  import { readCorePackageVersion } from "../engine-version.js";
6
- import { applyProductFirstGateMode, isHygieneGate, isProductAcGate, resolveProductFirstCheckMode, } from "../product-first-done-gate/index.js";
7
+ import { applyProductFirstGateMode, EMPTY_AC_CAUSE, EMPTY_AC_REMEDY, isHygieneGate, isProductAcGate, isSoftEmptyAcText, resolveProductFirstCheckMode, } from "../product-first-done-gate/index.js";
7
8
  import { RunSummaryEmitter } from "../run-summary/emit.js";
8
9
  import { runToolchainPreflight, SKIP_ALL_GATES, } from "../session/toolchain-preflight.js";
10
+ import { checkGateCliArgv, cliSpawnPlan, resolveGateDispatch, resolveGlobalCliBin, } from "./cli-native-gates.js";
9
11
  import { evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, } from "./consumer-gate-integrity.js";
10
12
  import { resolveCheckTarget } from "./context.js";
11
13
  import { checkGateId, checkGateSpawnArgs, gatesForCheckTarget, isSuiteCheckGate, } from "./gate-lists.js";
12
14
  import { formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate } from "./named-cause.js";
13
15
  function captureSpawn(taskBin, args, opts) {
14
- 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, {
15
18
  cwd: opts.cwd,
16
19
  encoding: "utf8",
17
20
  env: opts.env ?? process.env,
@@ -88,6 +91,7 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
88
91
  degraded,
89
92
  gates: gateOutcomes,
90
93
  });
94
+ emitter.emitKnownToolTurnDenominator();
91
95
  }
92
96
  catch {
93
97
  // fail-open
@@ -123,12 +127,18 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
123
127
  return 2;
124
128
  }
125
129
  // #3282: toolchain preflight — degraded skip when framework tools missing.
130
+ const which = options.which ?? defaultWhich;
126
131
  const preflight = options.preflight === undefined
127
132
  ? runToolchainPreflight({
128
133
  projectRoot: resolvedProject,
129
134
  frameworkRoot: resolvedFramework,
135
+ composedGates: gates,
136
+ consumerDeposit: target === "check:consumer",
137
+ which,
130
138
  })
131
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);
132
142
  // Expand SKIP_ALL_GATES sentinel to the live composition (avoids hardcode drift).
133
143
  const skipSet = new Set();
134
144
  for (const id of preflight?.skipGateIds ?? []) {
@@ -192,7 +202,26 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
192
202
  }
193
203
  options.onGateStart?.(gateId);
194
204
  const contract = resolveTaskContract(gateId);
195
- 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);
196
225
  let lastSpawn = { exitCode: 0, stdout: "", stderr: "" };
197
226
  const result = runWithCache({
198
227
  projectRoot: cwd,
@@ -201,11 +230,15 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
201
230
  noCache: options.noCache,
202
231
  runner: () => {
203
232
  const spawned = options.gateSpawnFn
204
- ? options.gateSpawnFn(gateId, taskBin, taskArgs, {
233
+ ? options.gateSpawnFn(gateId, spawnBin, spawnArgs, {
205
234
  cwd,
206
235
  env: options.env,
207
236
  })
208
- : captureSpawn(taskBin, taskArgs, { cwd, env: options.env });
237
+ : captureSpawn(spawnBin, spawnArgs, {
238
+ cwd,
239
+ env: options.env,
240
+ cli: dispatch.mode === "cli",
241
+ });
209
242
  lastSpawn = {
210
243
  exitCode: spawned.exitCode,
211
244
  stdout: spawned.stdout,
@@ -225,6 +258,38 @@ export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}
225
258
  // Fail-fast: do not start later gates (including suite) after a failure —
226
259
  // unless this is a hygiene gate under pressure mode (advisory only, #3284).
227
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
+ }
228
293
  if (modeResolution.hygieneAdvisory && isHygieneGate(gateId) && !isProductAcGate(gateId)) {
229
294
  process.stderr.write(`check: hygiene gate ${gateId} failed (exit ${result.exitCode}) but is ADVISORY ` +
230
295
  `under ${modeResolution.mode} mode — continuing (#3284)\n`);
@@ -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
@@ -1,4 +1,5 @@
1
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
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";
4
5
  export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, type NamedCauseMessage, remedyForGate, } from "./named-cause.js";
@@ -1,4 +1,5 @@
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
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";
4
5
  export { extractGateCause, formatDegradedSkipReport, formatNamedCauseFailure, remedyForGate, } from "./named-cause.js";
@@ -26,6 +26,7 @@ const GATE_REMEDIES = {
26
26
  "verify:agents-md-budget": "Trim AGENTS.md managed section or raise plan.policy.agentsMdBudget deliberately",
27
27
  };
28
28
  const SPAWN_ERROR_REMEDY = "Install go-task (https://taskfile.dev/installation/) and ensure `task` is on PATH; then re-run task check";
29
+ const CLI_SPAWN_ERROR_REMEDY = "Install: npm i -g @deftai/directive@latest";
29
30
  /**
30
31
  * Extract a short cause from gate stdout/stderr without leaking env values.
31
32
  * Strips lines that look like KEY=value assignments.
@@ -34,6 +35,9 @@ export function extractGateCause(stdout, stderr, exitCode, spawnError) {
34
35
  if (spawnError !== undefined && spawnError.length > 0) {
35
36
  // Normalize common missing-binary messages without path dumps.
36
37
  if (/ENOENT|not found|not recognized/i.test(spawnError)) {
38
+ if (/\b(deft|directive)(\.cmd)?\b/i.test(spawnError)) {
39
+ return "global deft/directive CLI not found on PATH";
40
+ }
37
41
  return "task binary not found on PATH (cannot spawn go-task)";
38
42
  }
39
43
  return sanitizeCauseLine(spawnError);
@@ -68,7 +72,13 @@ function sanitizeCauseLine(line) {
68
72
  return out;
69
73
  }
70
74
  export function remedyForGate(gateId, cause) {
75
+ if (/global deft\/directive CLI not found/i.test(cause)) {
76
+ return CLI_SPAWN_ERROR_REMEDY;
77
+ }
71
78
  if (/task binary not found|cannot spawn go-task/i.test(cause)) {
79
+ if (gateId.startsWith("verify:") || gateId.startsWith("verify-") || gateId === "doctor") {
80
+ return CLI_SPAWN_ERROR_REMEDY;
81
+ }
72
82
  return SPAWN_ERROR_REMEDY;
73
83
  }
74
84
  if (/pnpm binary not found|pnpm: NOT FOUND/i.test(cause)) {
@@ -87,12 +87,6 @@ export declare function checkTypescript7SideBySide(projectRoot: string, seams?:
87
87
  * `directive init` or `directive update`).
88
88
  */
89
89
  export declare function checkGitignoreCoverage(projectRoot: string, seams?: CheckSeams): CheckResult;
90
- /**
91
- * Surface undecided / invalid coverageDebt + checkResume policy (#3189).
92
- * Advisory skip when undecided; never hard-fails doctor / check:consumer.
93
- * Decided-off is quiet; dismiss-with-reason is pass with reason in detail.
94
- * Invalid typed blocks resolve fail-closed and surface via source=default-on-error.
95
- */
96
90
  /**
97
91
  * Fail closed when completed/ plan.status disagrees with the folder (#3242 AC1 / epic #3237 Q4).
98
92
  * Open plan.items under completed/ are reported by `checkCompletedOpenItems` (exit-exempt
@@ -104,6 +98,11 @@ export declare function checkCompletedLifecycleConsistency(projectRoot: string):
104
98
  * historical pre-#2862 corpora do not red-light doctor; scope:complete fails closed.
105
99
  */
106
100
  export declare function checkCompletedOpenItems(projectRoot: string): CheckResult;
101
+ /**
102
+ * Surface invalid coverageDebt + checkResume typed blocks (#3314).
103
+ * Absent / default / valid typed values pass. Invalid blocks skip (advisory,
104
+ * fail-closed resolution). Doctor no longer reports "undecided".
105
+ */
107
106
  export declare function checkCoverageCheckResumePolicy(projectRoot: string): CheckResult;
108
107
  export declare function deriveExitCode(checks: readonly CheckResult[], errors: readonly string[]): number;
109
108
  export declare function runChecksImpl(projectRoot: string, seams?: CheckSeams & {
@@ -8,7 +8,6 @@ import { resolveLifecycleRoot } from "../layout/resolve.js";
8
8
  import { scanCompletedLifecycleConsistency } from "../lifecycle/completed-consistency.js";
9
9
  import { resolveCheckResume } from "../policy/check-resume.js";
10
10
  import { resolveCoverageDebt } from "../policy/coverage-debt.js";
11
- import { policyColonInvocation } from "../policy/policy-invocation.js";
12
11
  import { classifyXbriefSchemaDistance } from "../staleness-tickler/probe-xbrief.js";
13
12
  import { findSkillPathsInText } from "../text/redos-safe.js";
14
13
  import { stripGitignoreInlineComment } from "../triage/bootstrap/gitignore.js";
@@ -1070,12 +1069,6 @@ export function checkGitignoreCoverage(projectRoot, seams = {}) {
1070
1069
  },
1071
1070
  };
1072
1071
  }
1073
- /**
1074
- * Surface undecided / invalid coverageDebt + checkResume policy (#3189).
1075
- * Advisory skip when undecided; never hard-fails doctor / check:consumer.
1076
- * Decided-off is quiet; dismiss-with-reason is pass with reason in detail.
1077
- * Invalid typed blocks resolve fail-closed and surface via source=default-on-error.
1078
- */
1079
1072
  /**
1080
1073
  * Fail closed when completed/ plan.status disagrees with the folder (#3242 AC1 / epic #3237 Q4).
1081
1074
  * Open plan.items under completed/ are reported by `checkCompletedOpenItems` (exit-exempt
@@ -1152,6 +1145,11 @@ export function checkCompletedOpenItems(projectRoot) {
1152
1145
  },
1153
1146
  };
1154
1147
  }
1148
+ /**
1149
+ * Surface invalid coverageDebt + checkResume typed blocks (#3314).
1150
+ * Absent / default / valid typed values pass. Invalid blocks skip (advisory,
1151
+ * fail-closed resolution). Doctor no longer reports "undecided".
1152
+ */
1155
1153
  export function checkCoverageCheckResumePolicy(projectRoot) {
1156
1154
  const debt = resolveCoverageDebt(projectRoot);
1157
1155
  const resume = resolveCheckResume(projectRoot);
@@ -1160,66 +1158,25 @@ export function checkCoverageCheckResumePolicy(projectRoot) {
1160
1158
  name: "coverage-check-resume-policy",
1161
1159
  status: "skip",
1162
1160
  detail: "advisory: coverageDebt and/or checkResume block is invalid; " +
1163
- "resolution is fail-closed (mode off, localStamp off, CI never trusts stamps). " +
1161
+ "resolution is fail-closed (mode off, localStamp off). " +
1164
1162
  `coverageDebt.error=${JSON.stringify(debt.error)}; ` +
1165
1163
  `checkResume.error=${JSON.stringify(resume.error)}. ` +
1166
- "Fix the typed block or re-apply Strict / Hatch-aware / dismiss-with-reason.",
1164
+ "Fix the typed block in PROJECT-DEFINITION.",
1167
1165
  data: {
1168
- coverageDebt: { status: debt.status, source: debt.source, error: debt.error },
1169
- checkResume: { status: resume.status, source: resume.source, error: resume.error },
1166
+ coverageDebt: { mode: debt.mode, source: debt.source, error: debt.error },
1167
+ checkResume: { localStamp: resume.localStamp, source: resume.source, error: resume.error },
1170
1168
  advisory: true,
1171
1169
  invalid: true,
1172
1170
  },
1173
1171
  };
1174
1172
  }
1175
- const undecided = debt.status === "unset" || resume.status === "unset";
1176
- if (!undecided) {
1177
- const dismissParts = [];
1178
- if (debt.dismissReason) {
1179
- dismissParts.push(`coverageDebt.dismissReason=${JSON.stringify(debt.dismissReason)}`);
1180
- }
1181
- if (resume.dismissReason) {
1182
- dismissParts.push(`checkResume.dismissReason=${JSON.stringify(resume.dismissReason)}`);
1183
- }
1184
- const dismissNote = dismissParts.length > 0 ? ` Dismissed: ${dismissParts.join("; ")}.` : "";
1185
- return {
1186
- name: "coverage-check-resume-policy",
1187
- status: "pass",
1188
- detail: `coverageDebt status=${debt.status} mode=${debt.mode}; ` +
1189
- `checkResume status=${resume.status} localStamp=${resume.localStamp}; ` +
1190
- `ciTrustsLocalStamp=false (fixed v1).${dismissNote}`,
1191
- data: {
1192
- coverageDebt: {
1193
- status: debt.status,
1194
- mode: debt.mode,
1195
- autoFile: debt.autoFile,
1196
- dismissReason: debt.dismissReason,
1197
- },
1198
- checkResume: {
1199
- status: resume.status,
1200
- localStamp: resume.localStamp,
1201
- ciTrustsLocalStamp: false,
1202
- dismissReason: resume.dismissReason,
1203
- },
1204
- },
1205
- };
1206
- }
1207
- // Advisory only (status=skip): never hard-fails doctor / check:consumer (#3189).
1208
- // Behavior remains fail-closed while unset; session-start nudge carries the ask.
1209
1173
  return {
1210
1174
  name: "coverage-check-resume-policy",
1211
- status: "skip",
1212
- detail: "advisory: coverageDebt and/or checkResume policy is undecided (status=unset). " +
1213
- "Behavior stays fail-closed (no hatch soft-pass, no local suite stamp, CI never trusts stamps). " +
1214
- "Choose Strict / Hatch-aware on the next interactive session-start nudge, or record " +
1215
- "dismiss-with-reason. Inspect: " +
1216
- `\`${policyColonInvocation("show", " --field=coverageDebt")}\` / ` +
1217
- `\`${policyColonInvocation("show", " --field=checkResume")}\`.`,
1175
+ status: "pass",
1176
+ detail: `coverageDebt.mode=${debt.mode}; checkResume.localStamp=${resume.localStamp}.`,
1218
1177
  data: {
1219
- coverageDebt: { status: debt.status, mode: debt.mode },
1220
- checkResume: { status: resume.status, localStamp: resume.localStamp },
1221
- suggested_fix: policyColonInvocation("show", " --field=coverageDebt"),
1222
- advisory: true,
1178
+ coverageDebt: { mode: debt.mode, autoFile: debt.autoFile, source: debt.source },
1179
+ checkResume: { localStamp: resume.localStamp, source: resume.source },
1223
1180
  },
1224
1181
  };
1225
1182
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * #3286 Later graduation trigger from a #3282 run summary (#3320).
3
+ *
4
+ * Named surface: eval:report prints graduate / do-not-graduate / unevaluable.
5
+ * A graduation or decline MUST cite an emitted share. Counts without a
6
+ * denominator are unevaluable — never invent a ratio.
7
+ */
8
+ import { type RitualGateShare } from "../run-summary/share.js";
9
+ /** #3286 Later trigger: graduate when ritual+gate share is at least this fraction. */
10
+ export declare const LATER_GRADUATION_SHARE_THRESHOLD = 0.25;
11
+ export type LaterGraduationVerdict = "graduate" | "do-not-graduate" | "unevaluable";
12
+ export interface LaterGraduationTrigger {
13
+ readonly evaluable: boolean;
14
+ readonly verdict: LaterGraduationVerdict;
15
+ readonly summary: string;
16
+ readonly ritualGateCount: number;
17
+ readonly totalToolTurns: number | null;
18
+ readonly share: number | null;
19
+ readonly threshold: number;
20
+ }
21
+ export interface EvaluateLaterGraduationOptions {
22
+ readonly projectRoot: string;
23
+ readonly runSummaryPath?: string;
24
+ readonly env?: NodeJS.ProcessEnv;
25
+ readonly threshold?: number;
26
+ }
27
+ export declare function laterGraduationFromShare(share: RitualGateShare, threshold?: number): LaterGraduationTrigger;
28
+ /**
29
+ * Resolve a readable run-summary path. Unset env + missing default file → null
30
+ * (fail-open / unevaluable). stdout dest (`-`) is not a file.
31
+ */
32
+ export declare function resolveRunSummaryReadPath(projectRoot: string, options?: {
33
+ readonly runSummaryPath?: string;
34
+ readonly env?: NodeJS.ProcessEnv;
35
+ }): string | null;
36
+ /** Evaluate #3286 Later from the run-summary file (or unevaluable). */
37
+ export declare function evaluateLaterGraduationTrigger(options: EvaluateLaterGraduationOptions): LaterGraduationTrigger;
38
+ export declare function formatLaterGraduationLine(trigger: LaterGraduationTrigger): string;
39
+ //# sourceMappingURL=later-graduation.d.ts.map