@deftai/directive-core 0.99.0 → 0.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/authz/actions.d.ts +6 -0
  2. package/dist/authz/actions.js +3 -0
  3. package/dist/authz/classify.js +241 -30
  4. package/dist/authz/decompose-apply.d.ts +81 -0
  5. package/dist/authz/decompose-apply.js +377 -0
  6. package/dist/authz/index.d.ts +3 -1
  7. package/dist/authz/index.js +3 -1
  8. package/dist/authz/store.d.ts +53 -0
  9. package/dist/authz/store.js +219 -1
  10. package/dist/authz/types.d.ts +19 -2
  11. package/dist/authz/types.js +5 -1
  12. package/dist/check/cached-orchestrator.d.ts +26 -5
  13. package/dist/check/cached-orchestrator.js +228 -6
  14. package/dist/check/gate-lists.d.ts +10 -4
  15. package/dist/check/gate-lists.js +19 -5
  16. package/dist/check/index.d.ts +3 -2
  17. package/dist/check/index.js +2 -1
  18. package/dist/check/named-cause.d.ts +45 -0
  19. package/dist/check/named-cause.js +121 -0
  20. package/dist/check/orchestrator.js +6 -1
  21. package/dist/doctor/checks.d.ts +44 -0
  22. package/dist/doctor/checks.js +428 -2
  23. package/dist/doctor/help.d.ts +5 -0
  24. package/dist/doctor/help.js +31 -0
  25. package/dist/doctor/index.d.ts +2 -0
  26. package/dist/doctor/index.js +2 -0
  27. package/dist/doctor/main.d.ts +6 -7
  28. package/dist/doctor/main.js +130 -79
  29. package/dist/doctor/session-coda.d.ts +82 -0
  30. package/dist/doctor/session-coda.js +151 -0
  31. package/dist/doctor/types.d.ts +16 -0
  32. package/dist/doctor/which.d.ts +20 -1
  33. package/dist/doctor/which.js +67 -1
  34. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  35. package/dist/finish-loop/pr-finish-loop.js +116 -5
  36. package/dist/finish-loop/types.d.ts +1 -1
  37. package/dist/hooks/dispatcher.d.ts +9 -5
  38. package/dist/hooks/dispatcher.js +23 -15
  39. package/dist/hooks/readonly.d.ts +6 -3
  40. package/dist/hooks/readonly.js +44 -25
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.js +3 -0
  43. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  44. package/dist/init-deposit/agent-hooks.js +51 -31
  45. package/dist/init-deposit/gitignore.js +2 -0
  46. package/dist/intake/issue-ingest.js +36 -0
  47. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  48. package/dist/lifecycle/completed-consistency.js +443 -0
  49. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  50. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  51. package/dist/lifecycle/index.d.ts +2 -0
  52. package/dist/lifecycle/index.js +2 -0
  53. package/dist/literal-acceptance/capture.d.ts +52 -0
  54. package/dist/literal-acceptance/capture.js +658 -0
  55. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  56. package/dist/literal-acceptance/evaluate.js +193 -0
  57. package/dist/literal-acceptance/index.d.ts +12 -0
  58. package/dist/literal-acceptance/index.js +12 -0
  59. package/dist/literal-acceptance/run.d.ts +38 -0
  60. package/dist/literal-acceptance/run.js +216 -0
  61. package/dist/literal-acceptance/safety.d.ts +24 -0
  62. package/dist/literal-acceptance/safety.js +251 -0
  63. package/dist/literal-acceptance/types.d.ts +82 -0
  64. package/dist/literal-acceptance/types.js +26 -0
  65. package/dist/policy/ac-pass-banking.d.ts +58 -0
  66. package/dist/policy/ac-pass-banking.js +150 -0
  67. package/dist/policy/ceremony-dial.d.ts +96 -11
  68. package/dist/policy/ceremony-dial.js +250 -23
  69. package/dist/policy/index.d.ts +2 -0
  70. package/dist/policy/index.js +16 -1
  71. package/dist/policy/merge-approval-head.d.ts +129 -0
  72. package/dist/policy/merge-approval-head.js +461 -0
  73. package/dist/policy/require-human-merge.d.ts +5 -0
  74. package/dist/policy/require-human-merge.js +5 -0
  75. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  76. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  77. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  78. package/dist/pr-merge-readiness/compute.js +99 -14
  79. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  80. package/dist/pr-merge-readiness/gh.js +268 -0
  81. package/dist/pr-merge-readiness/index.d.ts +2 -2
  82. package/dist/pr-merge-readiness/index.js +2 -2
  83. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  84. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  85. package/dist/pr-wait-mergeable/cascade.js +115 -1
  86. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  87. package/dist/pr-wait-mergeable/main.js +4 -0
  88. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  89. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  90. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  91. package/dist/preflight/evaluate.d.ts +15 -1
  92. package/dist/preflight/evaluate.js +33 -2
  93. package/dist/preflight-cache/evaluate.d.ts +3 -0
  94. package/dist/preflight-cache/evaluate.js +3 -0
  95. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  96. package/dist/product-first-done-gate/acceptance.js +247 -0
  97. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  98. package/dist/product-first-done-gate/check-mode.js +148 -0
  99. package/dist/product-first-done-gate/evaluate.d.ts +50 -0
  100. package/dist/product-first-done-gate/evaluate.js +255 -0
  101. package/dist/product-first-done-gate/index.d.ts +11 -0
  102. package/dist/product-first-done-gate/index.js +11 -0
  103. package/dist/product-first-done-gate/types.d.ts +64 -0
  104. package/dist/product-first-done-gate/types.js +61 -0
  105. package/dist/render/framework-commands.js +4 -0
  106. package/dist/run-summary/emit.d.ts +56 -0
  107. package/dist/run-summary/emit.js +171 -0
  108. package/dist/run-summary/index.d.ts +4 -0
  109. package/dist/run-summary/index.js +4 -0
  110. package/dist/run-summary/path.d.ts +25 -0
  111. package/dist/run-summary/path.js +78 -0
  112. package/dist/run-summary/types.d.ts +76 -0
  113. package/dist/run-summary/types.js +21 -0
  114. package/dist/scope/acceptance-evidence.d.ts +110 -0
  115. package/dist/scope/acceptance-evidence.js +422 -0
  116. package/dist/scope/coverage-map.d.ts +98 -0
  117. package/dist/scope/coverage-map.js +558 -0
  118. package/dist/scope/decompose.d.ts +5 -0
  119. package/dist/scope/decompose.js +134 -11
  120. package/dist/scope/index.d.ts +3 -0
  121. package/dist/scope/index.js +3 -0
  122. package/dist/scope/parent-lineage.d.ts +125 -0
  123. package/dist/scope/parent-lineage.js +626 -0
  124. package/dist/scope/transition.d.ts +8 -0
  125. package/dist/scope/transition.js +61 -2
  126. package/dist/session/ac-pass-banking.d.ts +221 -0
  127. package/dist/session/ac-pass-banking.js +761 -0
  128. package/dist/session/active-cli.d.ts +79 -0
  129. package/dist/session/active-cli.js +382 -0
  130. package/dist/session/deposit-sha.d.ts +49 -0
  131. package/dist/session/deposit-sha.js +121 -0
  132. package/dist/session/effort-budget.d.ts +136 -0
  133. package/dist/session/effort-budget.js +352 -0
  134. package/dist/session/index.d.ts +7 -0
  135. package/dist/session/index.js +7 -0
  136. package/dist/session/orientation-compression.d.ts +127 -0
  137. package/dist/session/orientation-compression.js +425 -0
  138. package/dist/session/orientation-state.d.ts +29 -0
  139. package/dist/session/orientation-state.js +85 -0
  140. package/dist/session/session-start.d.ts +31 -0
  141. package/dist/session/session-start.js +228 -2
  142. package/dist/session/toolchain-preflight.d.ts +64 -0
  143. package/dist/session/toolchain-preflight.js +151 -0
  144. package/dist/session/verify-session-ritual.d.ts +14 -0
  145. package/dist/session/verify-session-ritual.js +33 -0
  146. package/dist/story-ready/evaluate.d.ts +11 -0
  147. package/dist/story-ready/evaluate.js +41 -3
  148. package/dist/triage/actions/candidates-log.d.ts +1 -2
  149. package/dist/triage/actions/candidates-log.js +37 -6
  150. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  151. package/dist/triage/bootstrap/gitignore.js +8 -2
  152. package/dist/triage/bootstrap/index.js +15 -3
  153. package/dist/triage/classify/index.d.ts +1 -0
  154. package/dist/triage/classify/index.js +2 -0
  155. package/dist/triage/classify/label-mirror.js +16 -0
  156. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  157. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  158. package/dist/triage/reconcile/reconcile.js +28 -6
  159. package/dist/triage/summary/index.js +12 -4
  160. package/dist/triage/welcome/default-mode.js +16 -0
  161. package/dist/ts-check-lane/run-lane.js +21 -4
  162. package/dist/vbrief-validate/conformance.js +7 -0
  163. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  164. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  165. package/dist/xbrief-migrate/migrate-project.js +92 -3
  166. package/package.json +15 -3
@@ -14,6 +14,7 @@ import { humanMergeDisclosureLine, resolveHumanMergePolicy, } from "../policy/re
14
14
  import { resolvePolicy } from "../policy/resolve.js";
15
15
  import { maybeFormatProductSignalConsentPrompt } from "../product-signal/consent-prompt.js";
16
16
  import { formatFrameworkCommand } from "../render/framework-commands.js";
17
+ import { RunSummaryEmitter } from "../run-summary/emit.js";
17
18
  import { formatScmReadinessLines, probeScmReadiness, scmReadinessToDict, } from "../scm/readiness.js";
18
19
  import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
19
20
  import { runDefaultMode } from "../triage/welcome/default-mode.js";
@@ -21,11 +22,14 @@ import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
21
22
  import { emitSessionValueReadback } from "../value/readback.js";
22
23
  import { verifyRequiredTools } from "../verify-env/verify-tools.js";
23
24
  import { maybeFormatCoverageCheckResumeNudge } from "./coverage-check-resume-nudge.js";
25
+ import { effortBudgetToDict, maybeFormatEffortBudgetLines, resolveProductionHostEffortDescriptor, } from "./effort-budget.js";
24
26
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
27
+ import { runOrientationCompression, } from "./orientation-compression.js";
25
28
  import { emitSessionStartProcessCost } from "./process-cost.js";
26
29
  import { probeSessionReleaseAvailability, } from "./release-availability.js";
27
30
  import { newRitualStatePayload, readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
28
31
  import { timestampIso } from "./time.js";
32
+ import { runToolchainPreflight, toolchainPreflightToDict, } from "./toolchain-preflight.js";
29
33
  export const SESSION_POSTURES = ["read-only", "mutation"];
30
34
  export const READ_ONLY_POSTURE = "read-only";
31
35
  export const MUTATION_POSTURE = "mutation";
@@ -383,6 +387,38 @@ function resolveHostContentSurface(projectRoot, options, runtimeMode) {
383
387
  };
384
388
  }
385
389
  }
390
+ function resolveEffortBudget(options) {
391
+ try {
392
+ const seams = options.effortBudgetSeams ?? {};
393
+ const environ = seams.environ ?? options.env ?? process.env;
394
+ // Production host adapter (#3266 / #1461): always resolve a descriptor from env
395
+ // (and CLI-forwarded seams) so host-native max-turns/budget signals are not lost.
396
+ const productionHost = resolveProductionHostEffortDescriptor(environ);
397
+ const hostDescriptor = seams.hostDescriptor !== undefined && seams.hostDescriptor !== null
398
+ ? { ...productionHost, ...seams.hostDescriptor }
399
+ : productionHost;
400
+ return maybeFormatEffortBudgetLines({
401
+ environ,
402
+ hostDescriptor,
403
+ });
404
+ }
405
+ catch {
406
+ // best-effort — session start must not abort on effort-budget probe failures (#3266)
407
+ return {
408
+ budget: {
409
+ detected: false,
410
+ posture: "unbounded",
411
+ kind: "none",
412
+ maxTurns: null,
413
+ maxBudget: null,
414
+ remainingTurns: null,
415
+ remainingBudget: null,
416
+ sources: [],
417
+ },
418
+ lines: [],
419
+ };
420
+ }
421
+ }
386
422
  function runReadOnlySessionStart(projectRoot, options, instant, environment) {
387
423
  const lines = [];
388
424
  const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
@@ -396,11 +432,14 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
396
432
  const scm = resolveSessionScmReadiness(options, false);
397
433
  // #3162: host content-surface class + managed drift (advisory).
398
434
  const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
435
+ // #3266: hard effort-budget detection (advisory; bank-the-pass guidance).
436
+ const effortBudget = resolveEffortBudget(options);
399
437
  lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
400
438
  lines.push(userMdLine);
401
439
  lines.push(formatEnvironmentContext(environment));
402
440
  lines.push(...formatScmReadinessLines(scm));
403
441
  lines.push(...hostSurface.lines);
442
+ lines.push(...effortBudget.lines);
404
443
  const resultPayload = {
405
444
  ready: true,
406
445
  exit_code: 0,
@@ -423,6 +462,7 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
423
462
  environment: environmentContextToDict(environment),
424
463
  scm: scmReadinessToDict(scm),
425
464
  host_content_surface: hostContentSurfaceToDict(hostSurface.report),
465
+ effort_budget: effortBudgetToDict(effortBudget.budget),
426
466
  message: READ_ONLY_RESULT_MESSAGE,
427
467
  };
428
468
  return { code: 0, payload: resultPayload, lines };
@@ -467,12 +507,15 @@ function runSessionRearm(projectRoot, options, instant, environment) {
467
507
  const scm = resolveSessionScmReadiness(options, false);
468
508
  // #3162: host content-surface class + managed drift (advisory).
469
509
  const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
510
+ // #3266: hard effort-budget detection (advisory).
511
+ const effortBudget = resolveEffortBudget(options);
470
512
  const lines = [
471
513
  READ_ONLY_ALIGNMENT_MESSAGE,
472
514
  userMdLine,
473
515
  formatEnvironmentContext(environment),
474
516
  ...formatScmReadinessLines(scm),
475
517
  ...hostSurface.lines,
518
+ ...effortBudget.lines,
476
519
  REARM_SKIPPED_FAT_PATH_MESSAGE,
477
520
  ];
478
521
  // Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
@@ -665,6 +708,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
665
708
  environment: environmentContextToDict(environment),
666
709
  scm: scmReadinessToDict(scm),
667
710
  host_content_surface: hostContentSurfaceToDict(hostSurface.report),
711
+ effort_budget: effortBudgetToDict(effortBudget.budget),
668
712
  message: code === 0 ? "session ritual re-armed" : "session ritual re-arm failed",
669
713
  },
670
714
  lines,
@@ -749,9 +793,10 @@ export function runSessionStart(projectRoot, options = {}) {
749
793
  // #3214 / #3156: select ritual (ceremony) depth before building deferral maps.
750
794
  // Rapid/minimal auto-defer informational cold steps only; mutation readiness
751
795
  // (doctor, cache_fresh, agent_hooks, verify_tools) stays constant.
752
- // Two-stage + provisional intake (#3214 design note / #1581 ordering): fill
796
+ // Two-stage + provisional intake (#3214 / #3263 / #1581 ordering): fill
753
797
  // missing size/tier/shape from env/verb/files/deposit BEFORE resolve — never
754
- // block on plan-item effort (post-planning only). Cold incomplete rapid.
798
+ // block on plan-item effort (post-planning only). Cold incomplete size is
799
+ // tier-conditional (#3263): mid/low → standard; frontier/unknown → rapid.
755
800
  const { inputs: resolvedDialInputs, provisional: provisionalDial } = resolveSessionCeremonyDialInputs(projectRoot, options.ceremonyDialInputs, {
756
801
  ...options.ceremonyDialHints,
757
802
  env: options.env,
@@ -837,6 +882,14 @@ export function runSessionStart(projectRoot, options = {}) {
837
882
  name: "host_content_surface",
838
883
  duration_ms: elapsedMs(hostSurfaceStepStarted),
839
884
  });
885
+ // #3266: hard effort-budget detection (advisory; bank-the-pass guidance).
886
+ const effortBudgetStepStarted = performance.now();
887
+ const effortBudget = resolveEffortBudget(options);
888
+ lines.push(...effortBudget.lines);
889
+ stepTimings.push({
890
+ name: "effort_budget",
891
+ duration_ms: elapsedMs(effortBudgetStepStarted),
892
+ });
840
893
  if (!quickSteps.branch_policy) {
841
894
  const stepStarted = performance.now();
842
895
  const result = resolvePolicy(projectRoot);
@@ -902,6 +955,120 @@ export function runSessionStart(projectRoot, options = {}) {
902
955
  });
903
956
  stepTimings.push({ name: "verify_tools", duration_ms: durationMs });
904
957
  }
958
+ // #3282 / #3286: orientation compression — compose doctor + toolchain preflight
959
+ // (+ agents:refresh / cache-fresh deposit-sha fast-paths) as inline sections with
960
+ // per-section status lines. Composition of existing steps, not a new monolith.
961
+ // Read-only posture never reaches here (#2176). Dual-path Later (`deft orient`)
962
+ // remains open — see orientation bundle.later.
963
+ let toolchainPreflightResult = null;
964
+ let orientationBundle = null;
965
+ {
966
+ const stepStarted = performance.now();
967
+ if (options.orientation === null) {
968
+ // Explicit skip (tests): still honour legacy preflight-only path when set.
969
+ if (options.toolchainPreflight === null) {
970
+ stepTimings.push({ name: "orientation", duration_ms: 0, skipped: true });
971
+ stepTimings.push({ name: "toolchain_preflight", duration_ms: 0, skipped: true });
972
+ }
973
+ else if (options.toolchainPreflight !== undefined) {
974
+ toolchainPreflightResult = options.toolchainPreflight;
975
+ lines.push(...toolchainPreflightResult.lines);
976
+ stepTimings.push({
977
+ name: "toolchain_preflight",
978
+ duration_ms: elapsedMs(stepStarted),
979
+ });
980
+ }
981
+ else {
982
+ stepTimings.push({ name: "orientation", duration_ms: 0, skipped: true });
983
+ }
984
+ }
985
+ else if (options.orientation !== undefined) {
986
+ orientationBundle = options.orientation;
987
+ toolchainPreflightResult = orientationBundle.preflight;
988
+ lines.push(...orientationBundle.lines);
989
+ for (const section of orientationBundle.sections) {
990
+ stepTimings.push({
991
+ name: section.name,
992
+ duration_ms: section.durationMs,
993
+ ...(section.status === "skipped" ? { skipped: true } : {}),
994
+ });
995
+ // Record gated ritual steps so verify:session-ritual can skip re-runs.
996
+ // Do not overwrite explicit --defer (deferred_reason) records.
997
+ if ((section.name === "doctor" || section.name === "cache_fresh") &&
998
+ !gatedSteps[section.name]?.deferred_reason) {
999
+ gatedSteps[section.name] = ritualStep({
1000
+ ok: section.ok,
1001
+ ts: instant,
1002
+ message: section.lines[0] ?? section.status,
1003
+ exitCode: section.exitCode,
1004
+ durationMs: section.durationMs,
1005
+ command: section.name === "doctor" ? ["doctor"] : ["verify:cache-fresh"],
1006
+ });
1007
+ }
1008
+ }
1009
+ stepTimings.push({ name: "orientation", duration_ms: elapsedMs(stepStarted) });
1010
+ }
1011
+ else {
1012
+ try {
1013
+ // Skip composed doctor/cache_fresh work when those gated steps are deferred.
1014
+ const doctorDeferred = Boolean(gatedSteps.doctor?.deferred_reason);
1015
+ const cacheDeferred = Boolean(gatedSteps.cache_fresh?.deferred_reason);
1016
+ orientationBundle = runOrientationCompression({
1017
+ projectRoot,
1018
+ frameworkRoot: options.frameworkRoot ?? projectRoot,
1019
+ compact: options.compact,
1020
+ env: options.env,
1021
+ now: instant,
1022
+ toolchainPreflight: options.toolchainPreflight,
1023
+ toolchainPreflightOptions: options.toolchainPreflightOptions,
1024
+ includeDoctor: !doctorDeferred,
1025
+ includeCacheFresh: !cacheDeferred,
1026
+ ...options.orientationOptions,
1027
+ });
1028
+ toolchainPreflightResult = orientationBundle.preflight;
1029
+ lines.push(...orientationBundle.lines);
1030
+ for (const section of orientationBundle.sections) {
1031
+ stepTimings.push({
1032
+ name: section.name,
1033
+ duration_ms: section.durationMs,
1034
+ ...(section.status === "skipped" ? { skipped: true } : {}),
1035
+ });
1036
+ if ((section.name === "doctor" || section.name === "cache_fresh") &&
1037
+ !gatedSteps[section.name]?.deferred_reason) {
1038
+ gatedSteps[section.name] = ritualStep({
1039
+ ok: section.ok,
1040
+ ts: instant,
1041
+ message: section.lines[0] ?? section.status,
1042
+ exitCode: section.exitCode,
1043
+ durationMs: section.durationMs,
1044
+ command: section.name === "doctor" ? ["doctor"] : ["verify:cache-fresh"],
1045
+ });
1046
+ }
1047
+ }
1048
+ stepTimings.push({ name: "orientation", duration_ms: elapsedMs(stepStarted) });
1049
+ }
1050
+ catch {
1051
+ // fail-open: orientation composition must not abort session:start —
1052
+ // fall back to preflight-only (#3282).
1053
+ try {
1054
+ if (options.toolchainPreflight !== null) {
1055
+ toolchainPreflightResult =
1056
+ options.toolchainPreflight ??
1057
+ runToolchainPreflight({
1058
+ projectRoot,
1059
+ frameworkRoot: options.frameworkRoot ?? projectRoot,
1060
+ ...options.toolchainPreflightOptions,
1061
+ });
1062
+ lines.push(...toolchainPreflightResult.lines);
1063
+ }
1064
+ }
1065
+ catch {
1066
+ // ignore
1067
+ }
1068
+ stepTimings.push({ name: "orientation", duration_ms: elapsedMs(stepStarted) });
1069
+ }
1070
+ }
1071
+ }
905
1072
  // #3214: rapid/minimal skip informational cold-path ceremony only.
906
1073
  if (!quickSteps.triage_welcome && !skipFatPath) {
907
1074
  const stepStarted = performance.now();
@@ -1036,6 +1203,7 @@ export function runSessionStart(projectRoot, options = {}) {
1036
1203
  reasons: [...provisionalDial.reasons],
1037
1204
  },
1038
1205
  };
1206
+ const preflightDict = toolchainPreflightResult !== null ? toolchainPreflightToDict(toolchainPreflightResult) : null;
1039
1207
  const payload = {
1040
1208
  ...newRitualStatePayload({
1041
1209
  sessionId: coldSessionId,
@@ -1047,6 +1215,8 @@ export function runSessionStart(projectRoot, options = {}) {
1047
1215
  }),
1048
1216
  // #3214: record dial choice on ritual-state for audit / later re-arm context.
1049
1217
  ceremony_dial: dialDict,
1218
+ // #3282: durable preflight snapshot for harness / later check degraded mode.
1219
+ ...(preflightDict !== null ? { toolchain_preflight: preflightDict } : {}),
1050
1220
  };
1051
1221
  let statePath;
1052
1222
  try {
@@ -1070,8 +1240,45 @@ export function runSessionStart(projectRoot, options = {}) {
1070
1240
  .filter(([, step]) => !step.ok && !step.deferred_reason)
1071
1241
  .map(([name]) => name);
1072
1242
  // verify_tools is recorded on quick_steps; failure makes ready=false.
1243
+ // #3282: toolchain preflight degraded mode does NOT flip ready=false by itself —
1244
+ // agents still proceed with a named skip report at check time.
1073
1245
  const code = failed.length > 0 ? 1 : 0;
1074
1246
  const totalMs = elapsedMs(overallStarted);
1247
+ // #3282 / #3286: event-driven run-summary (dial + preflight + orientation call
1248
+ // count for dual-path Later graduation trigger) — fail-open, silent when unset.
1249
+ if (options.emitRunSummary !== false) {
1250
+ try {
1251
+ const emitter = new RunSummaryEmitter({
1252
+ projectRoot,
1253
+ sessionId: coldSessionId,
1254
+ env: options.env,
1255
+ });
1256
+ emitter.emitSessionStart({
1257
+ ceremony_dial: dialDict,
1258
+ preflight: preflightDict ?? undefined,
1259
+ ceremony_tier: COLD_CEREMONY_TIER,
1260
+ ready: code === 0,
1261
+ exit_code: code,
1262
+ ...(orientationBundle !== null
1263
+ ? {
1264
+ orientation_call_count: orientationBundle.orientationCallCount,
1265
+ orientation_compact: orientationBundle.compact,
1266
+ deposit_sha: orientationBundle.depositSha,
1267
+ orientation_later_status: orientationBundle.later.status,
1268
+ orientation_sections: orientationBundle.sections.map((s) => ({
1269
+ name: s.name,
1270
+ status: s.status,
1271
+ ok: s.ok,
1272
+ sha_match: s.shaMatch,
1273
+ })),
1274
+ }
1275
+ : {}),
1276
+ });
1277
+ }
1278
+ catch {
1279
+ // fail-open
1280
+ }
1281
+ }
1075
1282
  // #3117: bind live deposit generation when payload surfaces load (cold path).
1076
1283
  let freshnessBind = null;
1077
1284
  try {
@@ -1103,6 +1310,24 @@ export function runSessionStart(projectRoot, options = {}) {
1103
1310
  exit_code: code,
1104
1311
  ceremony_tier: COLD_CEREMONY_TIER,
1105
1312
  ceremony_dial: dialDict,
1313
+ ...(preflightDict !== null ? { toolchain_preflight: preflightDict } : {}),
1314
+ ...(orientationBundle !== null
1315
+ ? {
1316
+ orientation: {
1317
+ deposit_sha: orientationBundle.depositSha,
1318
+ compact: orientationBundle.compact,
1319
+ call_count: orientationBundle.orientationCallCount,
1320
+ later: orientationBundle.later,
1321
+ sections: orientationBundle.sections.map((s) => ({
1322
+ name: s.name,
1323
+ status: s.status,
1324
+ ok: s.ok,
1325
+ sha_match: s.shaMatch,
1326
+ exit_code: s.exitCode,
1327
+ })),
1328
+ },
1329
+ }
1330
+ : {}),
1106
1331
  state_path: statePath,
1107
1332
  ...(freshnessBind ? { freshness: freshnessBind } : {}),
1108
1333
  quick_steps: quickSteps,
@@ -1119,6 +1344,7 @@ export function runSessionStart(projectRoot, options = {}) {
1119
1344
  environment: environmentContextToDict(environment),
1120
1345
  scm: scmReadinessToDict(scm),
1121
1346
  host_content_surface: hostContentSurfaceToDict(hostSurface.report),
1347
+ effort_budget: effortBudgetToDict(effortBudget.budget),
1122
1348
  message: code === 0 ? "session ritual recorded" : "session ritual failed",
1123
1349
  };
1124
1350
  // #2994: local process-cost event (best-effort; never blocks ceremony).
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Done-gate toolchain preflight at session start (#3282).
3
+ *
4
+ * Verifies the framework done-gate's own toolchain (task binary, package
5
+ * manager, node, optional CLI dist) up front so agents get a named cause +
6
+ * remedy in one turn instead of reverse-engineering `directive check` failures.
7
+ *
8
+ * Never bootstraps tooling the PRODUCT does not need. Failures name cause and
9
+ * remedy without embedding env values.
10
+ */
11
+ export declare const TOOLCHAIN_PREFLIGHT_TOOLS: readonly ["task", "pnpm", "node", "git"];
12
+ export type ToolchainPreflightTool = (typeof TOOLCHAIN_PREFLIGHT_TOOLS)[number];
13
+ export type ToolchainPreflightStatus = "ok" | "missing" | "degraded";
14
+ export interface ToolchainPreflightFinding {
15
+ readonly tool: ToolchainPreflightTool | "cli_dist";
16
+ readonly present: boolean;
17
+ /** Machine-stable cause code (never embeds env values). */
18
+ readonly cause: string | null;
19
+ /** Operator/agent remedy command or instruction. */
20
+ readonly remedy: string | null;
21
+ }
22
+ export interface ToolchainPreflightResult {
23
+ readonly status: ToolchainPreflightStatus;
24
+ readonly ok: boolean;
25
+ /** True when missing tools would force check into degraded/skip mode. */
26
+ readonly degraded: boolean;
27
+ readonly findings: readonly ToolchainPreflightFinding[];
28
+ /** Human lines for session:start stdout (named cause + remedy). */
29
+ readonly lines: readonly string[];
30
+ /**
31
+ * Gate ids that check should skip when this preflight is degraded.
32
+ * Product-ordering of remaining gates is owned by #3284.
33
+ */
34
+ readonly skipGateIds: readonly string[];
35
+ }
36
+ export interface ToolchainPreflightOptions {
37
+ readonly projectRoot?: string;
38
+ readonly frameworkRoot?: string;
39
+ /** which(name) seam for tests. */
40
+ readonly which?: (name: string) => string | null;
41
+ /** existsSync seam for tests. */
42
+ readonly exists?: (path: string) => boolean;
43
+ /**
44
+ * When true (default), also probe for a built CLI dist under frameworkRoot
45
+ * (framework-source dogfood). Consumers without a local dist are fine when
46
+ * a global `deft`/`directive` is on PATH.
47
+ */
48
+ readonly probeCliDist?: boolean;
49
+ }
50
+ /**
51
+ * Gates that specifically need a package manager / built dist.
52
+ * When task/node is missing, the orchestrator skips *all* scheduled gates
53
+ * (dynamic from gate-lists), not this static list — avoids hardcode drift.
54
+ */
55
+ export declare const PNPM_DEPENDENT_GATE_IDS: readonly string[];
56
+ /** Sentinel skip id meaning "every gate in the active check composition". */
57
+ export declare const SKIP_ALL_GATES = "*";
58
+ /**
59
+ * Run done-gate toolchain preflight. Pure probe — never installs or builds.
60
+ */
61
+ export declare function runToolchainPreflight(options?: ToolchainPreflightOptions): ToolchainPreflightResult;
62
+ /** Serialize preflight for run-summary / ritual-state (no env values). */
63
+ export declare function toolchainPreflightToDict(result: ToolchainPreflightResult): Record<string, unknown>;
64
+ //# sourceMappingURL=toolchain-preflight.d.ts.map
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Done-gate toolchain preflight at session start (#3282).
3
+ *
4
+ * Verifies the framework done-gate's own toolchain (task binary, package
5
+ * manager, node, optional CLI dist) up front so agents get a named cause +
6
+ * remedy in one turn instead of reverse-engineering `directive check` failures.
7
+ *
8
+ * Never bootstraps tooling the PRODUCT does not need. Failures name cause and
9
+ * remedy without embedding env values.
10
+ */
11
+ import { existsSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { defaultWhich } from "../doctor/which.js";
14
+ export const TOOLCHAIN_PREFLIGHT_TOOLS = ["task", "pnpm", "node", "git"];
15
+ const REMEDY = {
16
+ task: "Install go-task: https://taskfile.dev/installation/ (e.g. winget install Task.Task / brew install go-task)",
17
+ pnpm: "Enable pnpm via corepack: corepack enable && corepack prepare pnpm@latest --activate",
18
+ node: "Install Node 20+ (see .nvmrc); then corepack enable",
19
+ git: "Install Git: https://git-scm.com/downloads",
20
+ };
21
+ const CAUSE = {
22
+ task: "go-task binary not found on PATH",
23
+ pnpm: "pnpm binary not found on PATH",
24
+ node: "node binary not found on PATH",
25
+ git: "git binary not found on PATH",
26
+ };
27
+ /**
28
+ * Gates that specifically need a package manager / built dist.
29
+ * When task/node is missing, the orchestrator skips *all* scheduled gates
30
+ * (dynamic from gate-lists), not this static list — avoids hardcode drift.
31
+ */
32
+ export const PNPM_DEPENDENT_GATE_IDS = [
33
+ "toolchain:check",
34
+ "toolchain:check-consumer",
35
+ "ts:check-lane",
36
+ ];
37
+ /** Sentinel skip id meaning "every gate in the active check composition". */
38
+ export const SKIP_ALL_GATES = "*";
39
+ function probeTool(tool, which) {
40
+ const present = which(tool) !== null;
41
+ return {
42
+ tool,
43
+ present,
44
+ cause: present ? null : CAUSE[tool],
45
+ remedy: present ? null : REMEDY[tool],
46
+ };
47
+ }
48
+ function probeCliDist(frameworkRoot, which, exists) {
49
+ const globalCli = which("deft") !== null || which("directive") !== null;
50
+ if (globalCli) {
51
+ return { tool: "cli_dist", present: true, cause: null, remedy: null };
52
+ }
53
+ if (frameworkRoot) {
54
+ const distBin = join(frameworkRoot, "packages", "cli", "dist", "bin.js");
55
+ if (exists(distBin)) {
56
+ return { tool: "cli_dist", present: true, cause: null, remedy: null };
57
+ }
58
+ return {
59
+ tool: "cli_dist",
60
+ present: false,
61
+ cause: "CLI dist missing and no global deft/directive on PATH",
62
+ remedy: "Run `task build` (framework source) or `npm i -g @deftai/directive@latest`",
63
+ };
64
+ }
65
+ return {
66
+ tool: "cli_dist",
67
+ present: false,
68
+ cause: "no global deft/directive on PATH",
69
+ remedy: "Install: npm i -g @deftai/directive@latest",
70
+ };
71
+ }
72
+ function formatFindingLine(finding) {
73
+ if (finding.present) {
74
+ return `[deft preflight] ${finding.tool}: ok`;
75
+ }
76
+ const cause = finding.cause ?? "missing";
77
+ const remedy = finding.remedy ?? "install the tool";
78
+ return `[deft preflight] ${finding.tool}: MISSING — cause: ${cause}; remedy: ${remedy}`;
79
+ }
80
+ /**
81
+ * Run done-gate toolchain preflight. Pure probe — never installs or builds.
82
+ */
83
+ export function runToolchainPreflight(options = {}) {
84
+ const which = options.which ?? defaultWhich;
85
+ const exists = options.exists ?? existsSync;
86
+ const findings = [];
87
+ for (const tool of TOOLCHAIN_PREFLIGHT_TOOLS) {
88
+ findings.push(probeTool(tool, which));
89
+ }
90
+ if (options.probeCliDist !== false) {
91
+ findings.push(probeCliDist(options.frameworkRoot ?? options.projectRoot, which, exists));
92
+ }
93
+ const missingCritical = findings.filter((f) => !f.present && (f.tool === "task" || f.tool === "pnpm" || f.tool === "node"));
94
+ const taskMissing = findings.some((f) => f.tool === "task" && !f.present);
95
+ const pnpmMissing = findings.some((f) => f.tool === "pnpm" && !f.present);
96
+ const nodeMissing = findings.some((f) => f.tool === "node" && !f.present);
97
+ const skip = new Set();
98
+ if (taskMissing || nodeMissing) {
99
+ // Dynamic: orchestrator expands SKIP_ALL_GATES to the live gate list.
100
+ skip.add(SKIP_ALL_GATES);
101
+ }
102
+ else if (pnpmMissing) {
103
+ for (const id of PNPM_DEPENDENT_GATE_IDS) {
104
+ skip.add(id);
105
+ }
106
+ }
107
+ const degraded = missingCritical.length > 0;
108
+ const ok = !degraded;
109
+ const status = ok ? "ok" : "degraded";
110
+ const lines = [];
111
+ lines.push(`[deft preflight] toolchain status: ${status}`);
112
+ for (const finding of findings) {
113
+ if (!finding.present) {
114
+ lines.push(formatFindingLine(finding));
115
+ }
116
+ }
117
+ if (ok) {
118
+ lines.push("[deft preflight] done-gate toolchain ready (task, pnpm, node)");
119
+ }
120
+ else {
121
+ lines.push("[deft preflight] degraded mode: directive check will skip toolchain-dependent gates " +
122
+ "with a named skip report rather than bootstrapping product-unneeded tooling (#3282)");
123
+ if (skip.size > 0) {
124
+ lines.push(`[deft preflight] gates subject to skip when degraded: ${[...skip].sort().join(", ")}`);
125
+ }
126
+ }
127
+ return {
128
+ status,
129
+ ok,
130
+ degraded,
131
+ findings,
132
+ lines,
133
+ skipGateIds: [...skip].sort(),
134
+ };
135
+ }
136
+ /** Serialize preflight for run-summary / ritual-state (no env values). */
137
+ export function toolchainPreflightToDict(result) {
138
+ return {
139
+ status: result.status,
140
+ ok: result.ok,
141
+ degraded: result.degraded,
142
+ findings: result.findings.map((f) => ({
143
+ tool: f.tool,
144
+ present: f.present,
145
+ cause: f.cause,
146
+ remedy: f.remedy,
147
+ })),
148
+ skip_gate_ids: [...result.skipGateIds],
149
+ };
150
+ }
151
+ //# sourceMappingURL=toolchain-preflight.js.map
@@ -1,3 +1,4 @@
1
+ import { type ActiveCliCheckResult, type ActiveCliCheckSeams } from "./active-cli.js";
1
2
  import { type GitRunner } from "./git.js";
2
3
  import { type DirectivePosture } from "./posture.js";
3
4
  import { type GatedStepName, type SessionCeremonyTier } from "./session-start.js";
@@ -39,6 +40,19 @@ export interface VerifySessionRitualOptions {
39
40
  readonly handoffText?: string | null;
40
41
  /** Re-run selected gated prerequisites even when their recorded step is green. */
41
42
  readonly forceGatedSteps?: readonly GatedStepName[];
43
+ /**
44
+ * #3233: engine version the post-upgrade / ritual check must match on the
45
+ * shell-active CLI. When null/omitted, still fail closed on multi-prefix
46
+ * version skew (active older than another PATH candidate).
47
+ */
48
+ readonly targetEngineVersion?: string | null;
49
+ /** Injectable active-CLI seams for hermetic tests (#3233). */
50
+ readonly activeCliSeams?: ActiveCliCheckSeams;
51
+ /**
52
+ * Override the active-CLI probe entirely (tests). Defaults to
53
+ * {@link checkActiveCliAgainstTarget}.
54
+ */
55
+ readonly checkActiveCli?: (targetVersion: string | null, seams?: ActiveCliCheckSeams) => ActiveCliCheckResult;
42
56
  }
43
57
  export interface InspectSessionRitualOptions {
44
58
  readonly tier?: "quick" | "gated";
@@ -1,5 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readCorePackageVersion } from "../engine-version.js";
2
3
  import { formatFrameworkCommand } from "../render/framework-commands.js";
4
+ import { checkActiveCliAgainstTarget, } from "./active-cli.js";
3
5
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
4
6
  import { pythonJsonDump } from "./json.js";
5
7
  import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
@@ -389,6 +391,37 @@ export function verifySessionRitual(projectRoot, options = {}) {
389
391
  ritualStateRequired,
390
392
  };
391
393
  }
394
+ // #3233: after gated entrypoints, verify the shell-active deft/directive
395
+ // is not a stale higher-precedence shadow of a multi-prefix install.
396
+ // Independent target = explicit option, else in-process publishable engine
397
+ // (never the active CLI's own version — see resolveDefaultActiveCliTarget).
398
+ const targetEngineVersion = options.targetEngineVersion ?? null;
399
+ const checkActiveCli = options.checkActiveCli ?? checkActiveCliAgainstTarget;
400
+ let inProcessVersion = null;
401
+ try {
402
+ inProcessVersion = readCorePackageVersion();
403
+ }
404
+ catch {
405
+ inProcessVersion = null;
406
+ }
407
+ const activeCliSeams = {
408
+ inProcessVersion,
409
+ ...options.activeCliSeams,
410
+ };
411
+ const activeCli = checkActiveCli(targetEngineVersion, activeCliSeams);
412
+ if (!activeCli.ok) {
413
+ return {
414
+ code: activeCli.code === 0 ? 1 : activeCli.code,
415
+ message: activeCli.lines.length > 0 ? activeCli.lines.join("\n") : activeCli.message,
416
+ tier,
417
+ statePath,
418
+ bypassed: false,
419
+ wouldFailCode: null,
420
+ posture,
421
+ ritualStateRequired,
422
+ recoveryTier: "cold",
423
+ };
424
+ }
392
425
  }
393
426
  const evaluated = evaluateLoadedState(projectRoot, state, {
394
427
  tier,
@@ -1,4 +1,5 @@
1
1
  import { type PathLike } from "node:fs";
2
+ import { type ParentLineageResult } from "../scope/parent-lineage.js";
2
3
  import { type ParsedAllocation, parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND } from "./allocation.js";
3
4
  export declare const ACTIVE_FOLDER = "active";
4
5
  export declare const ELIGIBLE_STATUS = "running";
@@ -7,17 +8,27 @@ export interface EvaluateResult {
7
8
  readonly exitCode: 0 | 1 | 2;
8
9
  readonly message: string;
9
10
  readonly dispatchKind: string | null;
11
+ /** #3241 parent-lineage probe (present when vBRIEF checks ran). */
12
+ readonly parentLineage?: ParentLineageResult;
10
13
  }
11
14
  export interface EvaluateOptions {
12
15
  readonly gitStatus?: string | null;
13
16
  readonly allocationContext?: string | null;
14
17
  readonly allowDirty?: boolean;
15
18
  readonly parsed?: ParsedAllocation;
19
+ /** Project root for resolving child planRef → parent (#3241). */
20
+ readonly projectRoot?: string;
21
+ /** Skip parent-lineage check (tests / opt-out). Default false. */
22
+ readonly skipParentLineage?: boolean;
16
23
  }
17
24
  /**
18
25
  * Pure evaluator — returns exit code + human message. Faithful to
19
26
  * `scripts/preflight_story_start.evaluate`.
27
+ *
28
+ * #3241: after structural active+running checks, re-check parent requirement
29
+ * lineage (coverage map + behavioral deltas) when the parent authors IDs.
20
30
  */
21
31
  export declare function evaluate(vbriefPath: PathLike, options?: EvaluateOptions): EvaluateResult;
32
+ export { evaluateParentLineage, formatParentLineageLine, type ParentLineageResult, } from "../scope/parent-lineage.js";
22
33
  export { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND };
23
34
  //# sourceMappingURL=evaluate.d.ts.map