@deftai/directive-core 0.105.0 → 0.107.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 (173) hide show
  1. package/dist/authz/classify.js +591 -34
  2. package/dist/check/gate-lists.js +2 -0
  3. package/dist/check/named-cause.js +1 -1
  4. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  5. package/dist/design-critique/exclusive-chip.js +68 -0
  6. package/dist/design-critique/parent-audit.d.ts +59 -0
  7. package/dist/design-critique/parent-audit.js +121 -0
  8. package/dist/doctor/checks.d.ts +6 -0
  9. package/dist/doctor/checks.js +35 -0
  10. package/dist/forward-coverage/diff-coverage.d.ts +39 -0
  11. package/dist/forward-coverage/diff-coverage.js +112 -0
  12. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  13. package/dist/forward-coverage/diff-lines.js +173 -0
  14. package/dist/forward-coverage/evaluate.d.ts +24 -9
  15. package/dist/forward-coverage/evaluate.js +125 -19
  16. package/dist/hooks/dest-form.d.ts +46 -0
  17. package/dist/hooks/dest-form.js +575 -0
  18. package/dist/hooks/dispatcher.d.ts +17 -1
  19. package/dist/hooks/dispatcher.js +82 -11
  20. package/dist/hooks/index.d.ts +1 -0
  21. package/dist/hooks/index.js +1 -0
  22. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  23. package/dist/init-deposit/agent-hooks.js +1 -1
  24. package/dist/intake/clause-derivation.d.ts +9 -0
  25. package/dist/intake/clause-derivation.js +30 -3
  26. package/dist/intake/github-auth-modes-cli.js +14 -2
  27. package/dist/intake/github-auth-modes.d.ts +54 -9
  28. package/dist/intake/github-auth-modes.js +326 -123
  29. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  30. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  31. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  32. package/dist/lifecycle/completed-write-guard.js +373 -0
  33. package/dist/lifecycle/index.d.ts +1 -0
  34. package/dist/lifecycle/index.js +1 -0
  35. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  36. package/dist/lifecycle-visible/evaluate.js +710 -0
  37. package/dist/lifecycle-visible/index.d.ts +2 -0
  38. package/dist/lifecycle-visible/index.js +2 -0
  39. package/dist/literal-acceptance/capture.d.ts +16 -0
  40. package/dist/literal-acceptance/capture.js +78 -10
  41. package/dist/literal-acceptance/evaluate.js +25 -3
  42. package/dist/literal-acceptance/index.d.ts +2 -2
  43. package/dist/literal-acceptance/index.js +2 -2
  44. package/dist/literal-acceptance/run.js +9 -3
  45. package/dist/literal-acceptance/types.d.ts +6 -0
  46. package/dist/literal-acceptance/types.js +6 -0
  47. package/dist/orchestration/judgment-policy.d.ts +13 -0
  48. package/dist/orchestration/judgment-policy.js +15 -0
  49. package/dist/orchestration/probe-session.js +13 -8
  50. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  51. package/dist/orchestration/verify-judgment-gates.js +43 -11
  52. package/dist/policy/ceremony-dial.js +7 -7
  53. package/dist/policy/host-hooks.d.ts +20 -0
  54. package/dist/policy/host-hooks.js +121 -2
  55. package/dist/policy/org-force-on-migration.js +2 -1
  56. package/dist/policy/plan-extensions.d.ts +1 -1
  57. package/dist/policy/plan-extensions.js +2 -0
  58. package/dist/policy/product-signal.js +6 -7
  59. package/dist/policy/require-human-merge.js +8 -6
  60. package/dist/policy/resolve.d.ts +7 -2
  61. package/dist/policy/resolve.js +25 -8
  62. package/dist/policy/runtime-authority.d.ts +18 -0
  63. package/dist/policy/runtime-authority.js +11 -0
  64. package/dist/policy/value-feedback.js +7 -8
  65. package/dist/policy/write-fence.js +3 -0
  66. package/dist/preflight-cache/evaluate.d.ts +12 -0
  67. package/dist/preflight-cache/evaluate.js +24 -4
  68. package/dist/preflight-cache/index.d.ts +1 -1
  69. package/dist/preflight-cache/index.js +1 -1
  70. package/dist/product-first-done-gate/evaluate.d.ts +4 -0
  71. package/dist/product-first-done-gate/evaluate.js +82 -10
  72. package/dist/release/closed-verb-gate.d.ts +12 -0
  73. package/dist/release/closed-verb-gate.js +51 -0
  74. package/dist/release/index.d.ts +1 -0
  75. package/dist/release/index.js +1 -0
  76. package/dist/release/pipeline.js +7 -0
  77. package/dist/release/types.d.ts +12 -0
  78. package/dist/render/constants.d.ts +2 -1
  79. package/dist/render/constants.js +2 -1
  80. package/dist/render/framework-commands.js +4 -0
  81. package/dist/render/spec-validate.js +23 -12
  82. package/dist/run-summary/types.d.ts +4 -0
  83. package/dist/scm/design-critique-chip.d.ts +37 -0
  84. package/dist/scm/design-critique-chip.js +171 -0
  85. package/dist/scm/index.d.ts +1 -0
  86. package/dist/scm/index.js +1 -0
  87. package/dist/scm/main.d.ts +3 -0
  88. package/dist/scm/main.js +15 -1
  89. package/dist/scm/readiness-cli.d.ts +2 -0
  90. package/dist/scm/readiness-cli.js +42 -1
  91. package/dist/scm/readiness.d.ts +2 -1
  92. package/dist/scm/readiness.js +7 -1
  93. package/dist/scope/decompose.js +2 -1
  94. package/dist/scope/delivery-evidence.d.ts +27 -1
  95. package/dist/scope/index.d.ts +1 -0
  96. package/dist/scope/index.js +1 -0
  97. package/dist/scope/lifecycle-write.d.ts +24 -0
  98. package/dist/scope/lifecycle-write.js +64 -0
  99. package/dist/scope/main.js +18 -6
  100. package/dist/scope/promote-from-issue.js +5 -5
  101. package/dist/scope/transition.js +90 -11
  102. package/dist/scope-provenance/evaluate.js +8 -4
  103. package/dist/session/ac-pass-banking.d.ts +5 -0
  104. package/dist/session/ac-pass-banking.js +3 -2
  105. package/dist/session/git.d.ts +18 -0
  106. package/dist/session/git.js +81 -1
  107. package/dist/session/occupancy.d.ts +2 -1
  108. package/dist/session/occupancy.js +11 -4
  109. package/dist/session/process-cost.d.ts +32 -10
  110. package/dist/session/process-cost.js +189 -1
  111. package/dist/session/product-state-hash.js +41 -24
  112. package/dist/session/ritual-entrypoint.js +16 -1
  113. package/dist/session/session-start.d.ts +18 -0
  114. package/dist/session/session-start.js +49 -2
  115. package/dist/session/verify-session-ritual.d.ts +46 -4
  116. package/dist/session/verify-session-ritual.js +118 -10
  117. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  118. package/dist/swarm/finalize-cohort-cli.js +165 -35
  119. package/dist/swarm/finalize-cohort.d.ts +1 -0
  120. package/dist/swarm/finalize-cohort.js +14 -2
  121. package/dist/swarm/launch.d.ts +69 -0
  122. package/dist/swarm/launch.js +191 -1
  123. package/dist/swarm/routing-set-cli.js +2 -0
  124. package/dist/swarm/routing-verify.d.ts +1 -1
  125. package/dist/swarm/routing-verify.js +2 -2
  126. package/dist/triage/actions/index.d.ts +4 -0
  127. package/dist/triage/actions/index.js +6 -0
  128. package/dist/triage/bootstrap/index.js +6 -6
  129. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  130. package/dist/triage/evaluate/evaluate.js +169 -0
  131. package/dist/triage/evaluate/github.d.ts +16 -0
  132. package/dist/triage/evaluate/github.js +153 -0
  133. package/dist/triage/evaluate/index.d.ts +9 -0
  134. package/dist/triage/evaluate/index.js +8 -0
  135. package/dist/triage/evaluate/paths.d.ts +7 -0
  136. package/dist/triage/evaluate/paths.js +25 -0
  137. package/dist/triage/evaluate/sink.d.ts +5 -0
  138. package/dist/triage/evaluate/sink.js +52 -0
  139. package/dist/triage/evaluate/types.d.ts +93 -0
  140. package/dist/triage/evaluate/types.js +13 -0
  141. package/dist/triage/evaluate/validity.d.ts +8 -0
  142. package/dist/triage/evaluate/validity.js +87 -0
  143. package/dist/triage/evaluate/value.d.ts +12 -0
  144. package/dist/triage/evaluate/value.js +33 -0
  145. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  146. package/dist/triage/evaluate/wip-census.js +39 -0
  147. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  148. package/dist/triage/evaluate/worktrees.js +68 -0
  149. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  150. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  151. package/dist/triage/help/registry-data.d.ts +22 -8
  152. package/dist/triage/help/registry-data.js +54 -6
  153. package/dist/triage/index.d.ts +1 -0
  154. package/dist/triage/index.js +1 -0
  155. package/dist/triage/welcome/writers.d.ts +1 -1
  156. package/dist/triage/welcome/writers.js +10 -5
  157. package/dist/value/feedback-file.d.ts +17 -2
  158. package/dist/value/feedback-file.js +80 -6
  159. package/dist/value/readback.d.ts +4 -1
  160. package/dist/value/readback.js +51 -12
  161. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  162. package/dist/vbrief-reconcile/labels.js +30 -0
  163. package/dist/vbrief-validate/schema.d.ts +4 -0
  164. package/dist/vbrief-validate/schema.js +1 -1
  165. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  166. package/dist/vbrief-validation/story-quality.js +8 -1
  167. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  168. package/dist/verify-env/agent-hook-readiness.js +12 -8
  169. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  170. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  171. package/dist/verify-env/agent-hooks.js +3 -4
  172. package/dist/xbrief-migrate/agents-header.js +69 -7
  173. package/package.json +7 -3
@@ -7,6 +7,21 @@ import { GhRestError, restCreateIssue, restIssueListPaginated, } from "../scm/gh
7
7
  import { resolveProjectRoot } from "../scope/project-context.js";
8
8
  export const DEFAULT_UPSTREAM_REPO = "deftai/directive";
9
9
  export const FRAMEWORK_GAP_TITLE_PREFIX = "[framework-gap]";
10
+ /** Sole sanctioned title classification (#3713). Does not write `adoption-blocker`. */
11
+ export const ADOPTION_BLOCKER_TITLE_TOKEN = "BLOCKER";
12
+ const FRAMEWORK_GAP_PREFIX_RE = /^\[framework-gap\]\s*/i;
13
+ const BLOCKER_TITLE_TOKEN_RE = /^BLOCKER\b[:\s-]*/i;
14
+ /** Strip source prefix and the optional BLOCKER token so marked/unmarked titles dedup. */
15
+ export function stripTitleClassificationTokens(title) {
16
+ let rest = title.trim();
17
+ let previous = "";
18
+ while (rest !== previous) {
19
+ previous = rest;
20
+ rest = rest.replace(FRAMEWORK_GAP_PREFIX_RE, "").trim();
21
+ rest = rest.replace(BLOCKER_TITLE_TOKEN_RE, "").trim();
22
+ }
23
+ return rest;
24
+ }
10
25
  function sanitizeOneLine(value) {
11
26
  return value.replace(/\r?\n/g, " ").trim();
12
27
  }
@@ -18,23 +33,54 @@ function sectionOrPlaceholder(value, placeholder) {
18
33
  export function isMaintainerFrameworkRepo(projectRoot) {
19
34
  return isFrameworkRepoRoot(resolve(projectRoot));
20
35
  }
21
- /** Normalize a title for duplicate comparison (#1709). */
36
+ /** Normalize a title for duplicate comparison (#1709 / #3713). */
22
37
  export function normalizeForDedup(title) {
23
- return title
24
- .trim()
25
- .replace(/^\[framework-gap\]\s*/i, "")
38
+ return stripTitleClassificationTokens(title)
26
39
  .toLowerCase()
27
40
  .replace(/[^\p{L}\p{N}]+/gu, " ")
28
41
  .trim();
29
42
  }
30
43
  /** Build the upstream issue title for a framework-gap report. */
31
- export function buildFrameworkGapTitle(summary) {
44
+ export function buildFrameworkGapTitle(summary, options) {
32
45
  const clean = sanitizeOneLine(summary);
46
+ if (options?.adoptionBlocker) {
47
+ const rest = stripTitleClassificationTokens(clean);
48
+ return `${FRAMEWORK_GAP_TITLE_PREFIX} ${ADOPTION_BLOCKER_TITLE_TOKEN} ${rest}`;
49
+ }
33
50
  if (clean.toLowerCase().startsWith(FRAMEWORK_GAP_TITLE_PREFIX.toLowerCase())) {
34
51
  return clean;
35
52
  }
36
53
  return `${FRAMEWORK_GAP_TITLE_PREFIX} ${clean}`;
37
54
  }
55
+ function buildAdoptionImpactSection(input) {
56
+ if (!input.adoptionBlocker) {
57
+ return [];
58
+ }
59
+ return [
60
+ "## Adoption impact",
61
+ "",
62
+ "This report is a consumer hard-stop: the consumer cannot complete an intended Directive flow and has no reasonable workaround.",
63
+ "",
64
+ `Title token: \`${ADOPTION_BLOCKER_TITLE_TOKEN}\`. This token does not apply the \`adoption-blocker\` ranking label. A privileged actor applies that label after the body-evidence test.`,
65
+ "",
66
+ "### Affected consumer flow and version",
67
+ "",
68
+ sectionOrPlaceholder(input.flowAndVersion, "_(not provided)_"),
69
+ "",
70
+ "### Documented alternatives attempted",
71
+ "",
72
+ sectionOrPlaceholder(input.alternatives, "_(not provided — why documented alternatives are not a reasonable workaround)_"),
73
+ "",
74
+ "### Observed recovery cost",
75
+ "",
76
+ sectionOrPlaceholder(input.recoveryCost, "_(not provided)_"),
77
+ "",
78
+ "### Triage owner and date",
79
+ "",
80
+ "_(to be filled by a privileged actor when applying `adoption-blocker`)_",
81
+ "",
82
+ ];
83
+ }
38
84
  /** Build the structured framework-gap issue body. */
39
85
  export function buildFrameworkGapBody(input) {
40
86
  const summary = sanitizeOneLine(input.summary);
@@ -59,6 +105,7 @@ export function buildFrameworkGapBody(input) {
59
105
  "",
60
106
  sectionOrPlaceholder(input.sessionNotes, "_(not provided)_"),
61
107
  "",
108
+ ...buildAdoptionImpactSection(input),
62
109
  "---",
63
110
  "_Filed via `task feedback:file` from a directive consumer project (Refs #1709 value-feedback gap escalation)._",
64
111
  "",
@@ -126,7 +173,7 @@ export function runFeedbackFile(options) {
126
173
  }
127
174
  const projectRoot = resolve(projectRootRaw);
128
175
  const repo = resolveRepo(options);
129
- const title = buildFrameworkGapTitle(summary);
176
+ const title = buildFrameworkGapTitle(summary, { adoptionBlocker: options.adoptionBlocker });
130
177
  const body = buildFrameworkGapBody(options);
131
178
  if (isMaintainerFrameworkRepo(projectRoot)) {
132
179
  return {
@@ -254,6 +301,8 @@ export function parseFeedbackFileArgs(argv) {
254
301
  out.dryRun = true;
255
302
  else if (arg === "--json")
256
303
  out.json = true;
304
+ else if (arg === "--blocker")
305
+ out.adoptionBlocker = true;
257
306
  else if (arg === "--summary") {
258
307
  out.summary = argv[++i];
259
308
  }
@@ -287,6 +336,27 @@ export function parseFeedbackFileArgs(argv) {
287
336
  else if (arg?.startsWith("--session-notes=")) {
288
337
  out.sessionNotes = arg.slice("--session-notes=".length);
289
338
  }
339
+ else if (arg === "--flow" || arg === "--flow-and-version") {
340
+ out.flowAndVersion = argv[++i];
341
+ }
342
+ else if (arg?.startsWith("--flow=")) {
343
+ out.flowAndVersion = arg.slice("--flow=".length);
344
+ }
345
+ else if (arg?.startsWith("--flow-and-version=")) {
346
+ out.flowAndVersion = arg.slice("--flow-and-version=".length);
347
+ }
348
+ else if (arg === "--alternatives") {
349
+ out.alternatives = argv[++i];
350
+ }
351
+ else if (arg?.startsWith("--alternatives=")) {
352
+ out.alternatives = arg.slice("--alternatives=".length);
353
+ }
354
+ else if (arg === "--recovery-cost") {
355
+ out.recoveryCost = argv[++i];
356
+ }
357
+ else if (arg?.startsWith("--recovery-cost=")) {
358
+ out.recoveryCost = arg.slice("--recovery-cost=".length);
359
+ }
290
360
  else if (arg === "--repo") {
291
361
  out.repo = argv[++i];
292
362
  }
@@ -327,6 +397,10 @@ export function feedbackFileMain(argv) {
327
397
  expected: args.expected,
328
398
  actual: args.actual,
329
399
  sessionNotes: args.sessionNotes,
400
+ adoptionBlocker: args.adoptionBlocker,
401
+ flowAndVersion: args.flowAndVersion,
402
+ alternatives: args.alternatives,
403
+ recoveryCost: args.recoveryCost,
330
404
  confirm: args.confirm,
331
405
  dryRun: args.dryRun,
332
406
  json: args.json,
@@ -1,5 +1,6 @@
1
1
  import { type BehavioralEventRecord } from "../lifecycle/events.js";
2
2
  import { type ValueFeedbackResolved } from "../policy/value-feedback.js";
3
+ import { type CeremonyCostRollup } from "../session/process-cost.js";
3
4
  /** Repeat-suppression window for the budgeted session readback (#1709 / #1279 parity). */
4
5
  export declare const VALUE_READBACK_SUPPRESSION_HOURS = 4;
5
6
  export declare const VALUE_READBACK_HISTORY_REL: string;
@@ -24,6 +25,8 @@ export interface ValueShowResult {
24
25
  readonly empty: boolean;
25
26
  readonly text: string;
26
27
  readonly trend: ValueShowTrend | null;
28
+ /** Composed #3508 ceremony-cost rollup (CLI process time, not turn clock). */
29
+ readonly ceremonyCost: CeremonyCostRollup | null;
27
30
  }
28
31
  export interface SessionReadbackResult {
29
32
  readonly line: string | null;
@@ -64,7 +67,7 @@ export declare function computeValueShowTrend(projectRoot: string, options?: {
64
67
  now?: Date;
65
68
  }): ValueShowTrend;
66
69
  export declare function formatValueShowReport(trend: ValueShowTrend): string;
67
- /** Pull-based value:show CLI core (#1709). */
70
+ /** Pull-based value:show CLI core (#1709) with composed ceremony-cost reader (#3508). */
68
71
  export declare function runValueShow(options: {
69
72
  projectRoot?: string | null;
70
73
  window?: string;
@@ -8,6 +8,7 @@ import { DEFAULT_EVENT_LOG, readEvents } from "../lifecycle/events.js";
8
8
  import { policyColonInvocation } from "../policy/policy-invocation.js";
9
9
  import { isValueFeedbackPathAllowed, resolveValueFeedback, } from "../policy/value-feedback.js";
10
10
  import { resolveProjectRoot } from "../scope/project-context.js";
11
+ import { computeCeremonyCostRollup, formatCeremonyCostReport, PROCESS_COST_EVENT_NAMES, } from "../session/process-cost.js";
11
12
  import { MAX_LINE_CHARS } from "../triage/welcome/constants.js";
12
13
  import { probeAdoptionAtWorkBoundary } from "./adoption-emit.js";
13
14
  import { probeFrictionAtWorkBoundary } from "./friction-emit.js";
@@ -391,7 +392,36 @@ export function formatValueShowReport(trend) {
391
392
  ];
392
393
  return `${lines.join("\n")}\n`;
393
394
  }
394
- /** Pull-based value:show CLI core (#1709). */
395
+ function attachCeremonyCost(result, ceremonyCost, format) {
396
+ if (format === "json") {
397
+ const payload = result.trend !== null
398
+ ? {
399
+ ...result.trend,
400
+ ceremonyCost,
401
+ process_cost_events: PROCESS_COST_EVENT_NAMES,
402
+ gated: result.gated,
403
+ attribution_gated: result.gated,
404
+ }
405
+ : {
406
+ ceremonyCost,
407
+ process_cost_events: PROCESS_COST_EVENT_NAMES,
408
+ gated: result.gated,
409
+ attribution_gated: result.gated,
410
+ empty: result.empty,
411
+ };
412
+ return {
413
+ ...result,
414
+ ceremonyCost,
415
+ text: `${JSON.stringify(payload, null, 2)}\n`,
416
+ };
417
+ }
418
+ return {
419
+ ...result,
420
+ ceremonyCost,
421
+ text: `${result.text}${formatCeremonyCostReport(ceremonyCost)}`,
422
+ };
423
+ }
424
+ /** Pull-based value:show CLI core (#1709) with composed ceremony-cost reader (#3508). */
395
425
  export function runValueShow(options) {
396
426
  const rootRaw = resolveProjectRoot(options.projectRoot ?? undefined);
397
427
  if (rootRaw === null) {
@@ -401,30 +431,39 @@ export function runValueShow(options) {
401
431
  empty: true,
402
432
  text: "Error: could not resolve project root.\n",
403
433
  trend: null,
434
+ ceremonyCost: null,
404
435
  };
405
436
  }
406
437
  const root = resolve(rootRaw);
438
+ const windowMs = parseWindowMs(options.window);
439
+ const ceremonyCost = computeCeremonyCostRollup({
440
+ projectRoot: root,
441
+ logPath: options.logPath,
442
+ windowMs,
443
+ now: options.now,
444
+ windowLabel: formatWindowLabel(windowMs),
445
+ });
407
446
  if (maintainerReadbackDisabled(root)) {
408
- return {
447
+ return attachCeremonyCost({
409
448
  exitCode: 0,
410
- gated: true,
449
+ gated: false,
411
450
  empty: true,
412
- text: "[value] Skipped: maintainer framework repo (set DEFT_VALUE_SELF_DOGFOOD=1 to dogfood).\n",
451
+ text: "[value] Attribution skipped: maintainer framework repo " +
452
+ "(set DEFT_VALUE_SELF_DOGFOOD=1 to dogfood).\n",
413
453
  trend: null,
414
- };
454
+ }, ceremonyCost, options.format);
415
455
  }
416
456
  const policy = options.policyOverride ?? resolveValueFeedback(root);
417
457
  if (!policy.enabled) {
418
- return {
458
+ return attachCeremonyCost({
419
459
  exitCode: 1,
420
460
  gated: true,
421
461
  empty: true,
422
462
  text: "[value] Blocked: plan.policy.valueFeedback is OFF. " +
423
463
  `Enable with \`${policyColonInvocation("enable-value-feedback", " -- --confirm")}\`.\n`,
424
464
  trend: null,
425
- };
465
+ }, ceremonyCost, options.format);
426
466
  }
427
- const windowMs = parseWindowMs(options.window);
428
467
  const trend = computeValueShowTrend(root, {
429
468
  windowMs,
430
469
  logPath: options.logPath,
@@ -432,21 +471,21 @@ export function runValueShow(options) {
432
471
  });
433
472
  const empty = trend.total === 0;
434
473
  if (options.format === "json") {
435
- return {
474
+ return attachCeremonyCost({
436
475
  exitCode: 0,
437
476
  gated: false,
438
477
  empty,
439
478
  text: `${JSON.stringify(trend, null, 2)}\n`,
440
479
  trend,
441
- };
480
+ }, ceremonyCost, "json");
442
481
  }
443
- return {
482
+ return attachCeremonyCost({
444
483
  exitCode: 0,
445
484
  gated: false,
446
485
  empty,
447
486
  text: formatValueShowReport(trend),
448
487
  trend,
449
- };
488
+ }, ceremonyCost, "text");
450
489
  }
451
490
  export function parseValueShowArgs(argv) {
452
491
  const out = {};
@@ -9,6 +9,7 @@ export declare function computeDesiredLabels(plan: Record<string, unknown>, unre
9
9
  export declare class ScmLabelClient implements LabelClient {
10
10
  fetchLabels(repo: string, issueNumber: number): string[];
11
11
  apply(repo: string, issueNumber: number, add: readonly string[], remove: readonly string[]): void;
12
+ private applyMut;
12
13
  }
13
14
  export interface ReconcileLabelsOptions {
14
15
  readonly repo?: string | null;
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { applyDesignCritiqueCatalogChip, isDesignCritiqueCatalogChip, } from "../design-critique/exclusive-chip.js";
3
4
  import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
4
5
  import { call } from "../scm/call.js";
5
6
  import { extractIssueRef } from "../triage/reconcile/parse-uri.js";
@@ -69,6 +70,35 @@ export class ScmLabelClient {
69
70
  return names;
70
71
  }
71
72
  apply(repo, issueNumber, add, remove) {
73
+ const catalogAdds = add.filter(isDesignCritiqueCatalogChip);
74
+ if (catalogAdds.length > 0) {
75
+ const nextChip = catalogAdds[catalogAdds.length - 1];
76
+ const restAdd = add.filter((name) => !isDesignCritiqueCatalogChip(name));
77
+ const restRemove = remove.filter((name) => !isDesignCritiqueCatalogChip(name));
78
+ let applied = false;
79
+ const inner = {
80
+ fetchLabels: (r, n) => this.fetchLabels(r, n),
81
+ apply: (r, n, a, rem) => {
82
+ applied = true;
83
+ const addSet = new Set([...a, ...restAdd]);
84
+ const removeSet = new Set([...rem, ...restRemove]);
85
+ for (const name of addSet)
86
+ removeSet.delete(name);
87
+ this.applyMut(r, n, [...addSet], [...removeSet]);
88
+ },
89
+ };
90
+ applyDesignCritiqueCatalogChip(inner, repo, issueNumber, nextChip);
91
+ if (!applied && (restAdd.length > 0 || restRemove.length > 0)) {
92
+ this.applyMut(repo, issueNumber, restAdd, restRemove);
93
+ }
94
+ return;
95
+ }
96
+ this.applyMut(repo, issueNumber, add, remove);
97
+ }
98
+ applyMut(repo, issueNumber, add, remove) {
99
+ if (add.length === 0 && remove.length === 0) {
100
+ return;
101
+ }
72
102
  const args = ["edit", String(issueNumber), "--repo", repo];
73
103
  for (const name of add)
74
104
  args.push("--add-label", name);
@@ -1,4 +1,8 @@
1
1
  export type JsonObject = Record<string, unknown>;
2
+ export declare function resolveInfoBlock(data: JsonObject): {
3
+ key: "vBRIEFInfo" | "xBRIEFInfo";
4
+ info: JsonObject;
5
+ } | null;
2
6
  /** Validate vBRIEF/xBRIEF structural requirements (v0.6 + v0.8 additive). */
3
7
  export declare function validateVbriefSchema(data: JsonObject, filepath: string): string[];
4
8
  /** Normalize a narrative key for D3 comparison. */
@@ -22,7 +22,7 @@ function validatePlanRefs(planRefs, path, errors) {
22
22
  }
23
23
  }
24
24
  }
25
- function resolveInfoBlock(data) {
25
+ export function resolveInfoBlock(data) {
26
26
  for (const key of VALID_INFO_ROOT_KEYS) {
27
27
  if (!(key in data)) {
28
28
  continue;
@@ -25,5 +25,12 @@ export interface StoryQualityParams {
25
25
  readonly swarm: JsonObject;
26
26
  readonly concurrentReady?: boolean;
27
27
  }
28
+ /**
29
+ * Shared Gate A text for `readiness=ready` + `parallel_safe: false` (#3666 / #3252).
30
+ * Authoring (`scope:decompose`) and launch (`swarm:readiness` / `swarm:launch`)
31
+ * both emit this string. It must not recommend `readiness=sequential` as a
32
+ * launch remediation — Gate B rejects that value on the same call.
33
+ */
34
+ export declare const READY_REQUIRES_PARALLEL_SAFE = "readiness=ready requires parallel_safe=true. This story is not eligible for concurrent swarm:launch; dispatch non-concurrent work via the interactive swarm-skill solo-worker path (that path is not gated by swarm:readiness exiting 0; exit 0 gates concurrent workers only; see #3669)";
28
35
  export declare function storyQualityIssues(params: StoryQualityParams): string[];
29
36
  //# sourceMappingURL=story-quality.d.ts.map
@@ -266,6 +266,13 @@ export function deprecatedSubitemsIssues(items, prefix = "plan.items") {
266
266
  visit(items, prefix);
267
267
  return issues;
268
268
  }
269
+ /**
270
+ * Shared Gate A text for `readiness=ready` + `parallel_safe: false` (#3666 / #3252).
271
+ * Authoring (`scope:decompose`) and launch (`swarm:readiness` / `swarm:launch`)
272
+ * both emit this string. It must not recommend `readiness=sequential` as a
273
+ * launch remediation — Gate B rejects that value on the same call.
274
+ */
275
+ export const READY_REQUIRES_PARALLEL_SAFE = "readiness=ready requires parallel_safe=true. This story is not eligible for concurrent swarm:launch; dispatch non-concurrent work via the interactive swarm-skill solo-worker path (that path is not gated by swarm:readiness exiting 0; exit 0 gates concurrent workers only; see #3669)";
269
276
  export function storyQualityIssues(params) {
270
277
  const issues = [];
271
278
  const concurrentReady = params.concurrentReady ?? true;
@@ -304,7 +311,7 @@ export function storyQualityIssues(params) {
304
311
  issues.push(...fileScopeIssues(params.swarm));
305
312
  issues.push(...verifyCommandIssues(params.swarm));
306
313
  if (params.swarm.parallel_safe === false) {
307
- issues.push("readiness=ready requires parallel_safe=true; use readiness=sequential or needs_refinement for non-concurrent work");
314
+ issues.push(READY_REQUIRES_PARALLEL_SAFE);
308
315
  }
309
316
  if (params.swarm.file_scope_confidence === "low") {
310
317
  issues.push("readiness=ready requires file_scope_confidence above low");
@@ -5,10 +5,10 @@ import { type AgentHookHealthResult } from "./agent-hooks.js";
5
5
  import { type AgentHookLiveProbeResult, type AgentHookLiveProbeSeams } from "./agent-hooks-live-probe.js";
6
6
  import type { OutputStream } from "./verify-hooks-installed.js";
7
7
  export type AgentHookReadinessRegistration = "registered" | "disabled" | "missing" | "drifted" | "not-applicable";
8
- export type AgentHookReadinessFunctionality = "functional" | "non-functional" | "unavailable" | "not-run" | "disabled" | "not-applicable";
8
+ export type AgentHookReadinessFunctionality = "functional" | "non-functional" | "unavailable" | "timed-out" | "not-run" | "disabled" | "not-applicable";
9
9
  export type AgentHookReadinessTrust = "manual-review-required" | "not-applicable" | "disabled";
10
10
  export type AgentHookReadinessInterception = "not-directly-verified" | "not-applicable" | "disabled";
11
- export type AgentHookReadinessLiveStatus = "functional" | "non-functional" | "unavailable" | "not-run" | "disabled" | "skipped";
11
+ export type AgentHookReadinessLiveStatus = "functional" | "non-functional" | "unavailable" | "timed-out" | "not-run" | "disabled" | "skipped";
12
12
  export interface AgentHookReadinessHostResult {
13
13
  readonly host: HookHost;
14
14
  readonly registration: AgentHookReadinessRegistration;
@@ -1,6 +1,6 @@
1
1
  import { resolve } from "node:path";
2
2
  import { resolveFrameworkRootForProject } from "../doctor/paths.js";
3
- import { loadHostHooksPolicyFromProject } from "../policy/host-hooks.js";
3
+ import { loadHostHooksPolicyFromProject, UNUSED_HOST_HOOKS_RECOVERY, } from "../policy/host-hooks.js";
4
4
  import { evaluateAgentHooks } from "./agent-hooks.js";
5
5
  import { probeAgentHooksLive, } from "./agent-hooks-live-probe.js";
6
6
  /** Stable CLI JSON projection; keep the four readiness dimensions orthogonal. */
@@ -114,31 +114,35 @@ export function evaluateAgentHookReadiness(projectRoot, options = {}) {
114
114
  ? "functional"
115
115
  : liveStatus === "unavailable"
116
116
  ? "unavailable"
117
- : liveStatus === "non-functional"
118
- ? "non-functional"
119
- : "not-run",
117
+ : liveStatus === "timed-out"
118
+ ? "timed-out"
119
+ : liveStatus === "non-functional"
120
+ ? "non-functional"
121
+ : "not-run",
120
122
  };
121
123
  });
122
124
  const allDisabled = enabledHosts.length === 0;
125
+ const timeoutOnly = liveProbe.cases.length > 0 && liveProbe.cases.every((entry) => entry.issue === "timed-out");
123
126
  const liveStatus = allDisabled
124
127
  ? "disabled"
125
128
  : liveProbe.code === 0
126
129
  ? "functional"
127
130
  : liveProbe.code === 2
128
131
  ? "unavailable"
129
- : "non-functional";
132
+ : timeoutOnly
133
+ ? "timed-out"
134
+ : "non-functional";
130
135
  const trustReview = policy.codex
131
136
  ? "\n Codex trust: manual-review-required. Open `/hooks` and approve the exact project hook commands. " +
132
137
  "The live probe validates the shim and codec, not host interception."
133
138
  : "";
139
+ const unusedHostRecovery = liveProbe.code === 0 || timeoutOnly ? "" : `\n ${UNUSED_HOST_HOOKS_RECOVERY}`;
134
140
  return {
135
141
  code: liveProbe.code,
136
142
  message: `${liveProbe.code === 0 ? "✓" : "❌"} deft agent hook readiness: ${liveProbe.message}\n` +
137
143
  renderHosts(evaluatedHosts) +
138
144
  trustReview +
139
- (liveProbe.code === 0
140
- ? ""
141
- : "\n If an affected host is unused, inspect `deft policy:show --field=hostHooks`, set `plan.policy.hostHooks.<host> = false`, and run `deft update`."),
145
+ unusedHostRecovery,
142
146
  stream: liveProbe.code === 0 ? "stdout" : "stderr",
143
147
  skipped: false,
144
148
  liveStatus,
@@ -1,5 +1,9 @@
1
1
  import { type HookEvent, type HookHost } from "../hooks/dispatcher.js";
2
2
  export declare const LIVE_PROBE_CASE_TIMEOUT_MS = 1500;
3
+ /** Extra attempts after a timeout. One retry keeps 4×2×1.5s×2 = 24s under Cursor 30s (#3570). */
4
+ export declare const LIVE_PROBE_CASE_RETRY_COUNT = 1;
5
+ export declare const LIVE_PROBE_TIMEOUT_RECOVERY = "Recovery: retry the gated ritual when load is lower. Do not reinstall @deftai/directive or disable hostHooks for a timeout.";
6
+ export declare const LIVE_PROBE_BROKEN_RECOVERY = "Recovery: reinstall @deftai/directive and run `deft update`.";
3
7
  export type AgentHookLiveProbeIssue = "hook-command-missing" | "spawn-failed" | "timed-out" | "empty-stdout" | "unparseable-json" | "missing-allow" | "missing-deny";
4
8
  export interface AgentHookLiveProbeCase {
5
9
  readonly host: HookHost;
@@ -8,7 +12,7 @@ export interface AgentHookLiveProbeCase {
8
12
  readonly issue: AgentHookLiveProbeIssue;
9
13
  readonly detail: string;
10
14
  }
11
- export type AgentHookLiveProbeHostStatus = "functional" | "non-functional" | "unavailable";
15
+ export type AgentHookLiveProbeHostStatus = "functional" | "non-functional" | "unavailable" | "timed-out";
12
16
  export interface AgentHookLiveProbeHostResult {
13
17
  readonly host: HookHost;
14
18
  readonly status: AgentHookLiveProbeHostStatus;
@@ -6,6 +6,10 @@ import { DEFT_HOOK_COMMAND_MARKER } from "../init-deposit/agent-hooks.js";
6
6
  import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
7
7
  import { quoteWin32CommandForShell, resolveCommandOnPath, shouldUseShellForCommand, } from "./command-spawn.js";
8
8
  export const LIVE_PROBE_CASE_TIMEOUT_MS = 1_500;
9
+ /** Extra attempts after a timeout. One retry keeps 4×2×1.5s×2 = 24s under Cursor 30s (#3570). */
10
+ export const LIVE_PROBE_CASE_RETRY_COUNT = 1;
11
+ export const LIVE_PROBE_TIMEOUT_RECOVERY = "Recovery: retry the gated ritual when load is lower. Do not reinstall @deftai/directive or disable hostHooks for a timeout.";
12
+ export const LIVE_PROBE_BROKEN_RECOVERY = "Recovery: reinstall @deftai/directive and run `deft update`.";
9
13
  const LIVE_PROBE_EVENT = "tool.before";
10
14
  function elapsedMs(started) {
11
15
  return Math.max(0, Math.round(performance.now() - started));
@@ -122,21 +126,38 @@ function validateFixtureOutput(host, fixture, stdout) {
122
126
  ? { ok: true }
123
127
  : { ok: false, issue: "missing-deny", detail: `expected ${host} deny envelope` };
124
128
  }
129
+ function liveProbeRecovery(cases) {
130
+ if (cases.length > 0 && cases.every((entry) => entry.issue === "timed-out")) {
131
+ return LIVE_PROBE_TIMEOUT_RECOVERY;
132
+ }
133
+ return LIVE_PROBE_BROKEN_RECOVERY;
134
+ }
135
+ function hostStatusFromFailure(hostFailure) {
136
+ if (hostFailure === null)
137
+ return "functional";
138
+ if (hostFailure.issue === "timed-out")
139
+ return "timed-out";
140
+ return "non-functional";
141
+ }
125
142
  function runFixtureProbe(command, host, projectRoot, fixture, spawnHook) {
126
- const spawned = spawnHook({
143
+ const input = {
127
144
  command,
128
145
  args: ["--host", host, "--event", LIVE_PROBE_EVENT, "--project-root", projectRoot],
129
146
  stdin: fixture === "allow" ? allowFixture(projectRoot) : denyFixture(projectRoot),
130
147
  cwd: projectRoot,
131
148
  env: fixture === "allow" ? process.env : denyProbeEnv(),
132
- });
149
+ };
150
+ let spawned = spawnHook(input);
151
+ for (let retry = 0; retry < LIVE_PROBE_CASE_RETRY_COUNT && spawned.timedOut === true; retry += 1) {
152
+ spawned = spawnHook(input);
153
+ }
133
154
  if (spawned.timedOut === true) {
134
155
  return {
135
156
  host,
136
157
  event: LIVE_PROBE_EVENT,
137
158
  fixture,
138
159
  issue: "timed-out",
139
- detail: `hook command exceeded ${LIVE_PROBE_CASE_TIMEOUT_MS}ms`,
160
+ detail: `hook command exceeded ${LIVE_PROBE_CASE_TIMEOUT_MS}ms after ${LIVE_PROBE_CASE_RETRY_COUNT + 1} attempts`,
140
161
  };
141
162
  }
142
163
  if (spawned.status !== 0) {
@@ -204,7 +225,7 @@ export function probeAgentHooksLive(projectRoot, seams = {}) {
204
225
  break;
205
226
  }
206
227
  }
207
- hostResults.push({ host, status: hostFailure === null ? "functional" : "non-functional" });
228
+ hostResults.push({ host, status: hostStatusFromFailure(hostFailure) });
208
229
  }
209
230
  if (failures.length === 0) {
210
231
  return {
@@ -220,7 +241,7 @@ export function probeAgentHooksLive(projectRoot, seams = {}) {
220
241
  .join("; ");
221
242
  return {
222
243
  code: 1,
223
- message: `deft agent hooks live probe FAILED: ${summary}. Recovery: reinstall @deftai/directive and run \`deft update\`.`,
244
+ message: `deft agent hooks live probe FAILED: ${summary}. ${liveProbeRecovery(failures)}`,
224
245
  cases: failures,
225
246
  hosts: hostResults,
226
247
  durationMs: elapsedMs(started),
@@ -1,7 +1,7 @@
1
1
  import { statSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { inspectAgentHookDeposit } from "../init-deposit/agent-hooks.js";
4
- import { loadHostHooksPolicyFromProject } from "../policy/host-hooks.js";
4
+ import { loadHostHooksPolicyFromProject, UNUSED_HOST_HOOKS_RECOVERY, } from "../policy/host-hooks.js";
5
5
  function isDirectory(path) {
6
6
  try {
7
7
  return statSync(path).isDirectory();
@@ -30,9 +30,8 @@ export function evaluateAgentHooks(projectRoot, hostHooksPolicy = loadHostHooksP
30
30
  unhealthy
31
31
  .map((entry) => ` - ${entry.host}: ${entry.status} at ${entry.path} — ${entry.detail}`)
32
32
  .join("\n") +
33
- "\n Recovery: run `deft update` (or `directive init`) to refresh project hooks." +
34
- " If a failed host is intentionally unused, inspect `deft policy:show --field=hostHooks`, " +
35
- "set `plan.policy.hostHooks.<host> = false`, and run `deft update` again.",
33
+ "\n Recovery: run `deft update` (or `directive init`) to refresh project hooks. " +
34
+ UNUSED_HOST_HOOKS_RECOVERY,
36
35
  stream: "stderr",
37
36
  registrations,
38
37
  };