@deftai/directive-core 0.100.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/authz/classify.js +193 -27
  2. package/dist/authz/decompose-apply.d.ts +14 -0
  3. package/dist/authz/decompose-apply.js +93 -18
  4. package/dist/check/cached-orchestrator.d.ts +30 -5
  5. package/dist/check/cached-orchestrator.js +297 -10
  6. package/dist/check/cli-native-gates.d.ts +43 -0
  7. package/dist/check/cli-native-gates.js +84 -0
  8. package/dist/check/gate-lists.d.ts +10 -4
  9. package/dist/check/gate-lists.js +19 -5
  10. package/dist/check/index.d.ts +4 -2
  11. package/dist/check/index.js +3 -1
  12. package/dist/check/named-cause.d.ts +45 -0
  13. package/dist/check/named-cause.js +131 -0
  14. package/dist/check/orchestrator.js +6 -1
  15. package/dist/doctor/checks.d.ts +5 -6
  16. package/dist/doctor/checks.js +13 -56
  17. package/dist/doctor/help.d.ts +5 -0
  18. package/dist/doctor/help.js +31 -0
  19. package/dist/doctor/index.d.ts +2 -0
  20. package/dist/doctor/index.js +2 -0
  21. package/dist/doctor/main.js +58 -0
  22. package/dist/doctor/session-coda.d.ts +82 -0
  23. package/dist/doctor/session-coda.js +151 -0
  24. package/dist/doctor/types.d.ts +16 -0
  25. package/dist/eval/later-graduation.d.ts +39 -0
  26. package/dist/eval/later-graduation.js +91 -0
  27. package/dist/eval/report.d.ts +6 -0
  28. package/dist/eval/report.js +27 -8
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.js +3 -0
  31. package/dist/init-deposit/gitignore.js +2 -0
  32. package/dist/intake/issue-ingest.js +49 -0
  33. package/dist/policy/ac-pass-banking.d.ts +58 -0
  34. package/dist/policy/ac-pass-banking.js +150 -0
  35. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  36. package/dist/policy/ceremony-dial-escalation.js +92 -0
  37. package/dist/policy/ceremony-dial.d.ts +32 -1
  38. package/dist/policy/ceremony-dial.js +128 -1
  39. package/dist/policy/check-resume.d.ts +9 -38
  40. package/dist/policy/check-resume.js +18 -156
  41. package/dist/policy/coverage-debt.d.ts +18 -37
  42. package/dist/policy/coverage-debt.js +38 -147
  43. package/dist/policy/index.d.ts +2 -1
  44. package/dist/policy/index.js +16 -2
  45. package/dist/preflight-cache/evaluate.d.ts +3 -0
  46. package/dist/preflight-cache/evaluate.js +3 -0
  47. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  48. package/dist/product-first-done-gate/acceptance.js +269 -0
  49. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  50. package/dist/product-first-done-gate/check-mode.js +148 -0
  51. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  52. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  53. package/dist/product-first-done-gate/evaluate.d.ts +76 -0
  54. package/dist/product-first-done-gate/evaluate.js +411 -0
  55. package/dist/product-first-done-gate/index.d.ts +12 -0
  56. package/dist/product-first-done-gate/index.js +12 -0
  57. package/dist/product-first-done-gate/types.d.ts +67 -0
  58. package/dist/product-first-done-gate/types.js +54 -0
  59. package/dist/run-summary/emit.d.ts +66 -0
  60. package/dist/run-summary/emit.js +217 -0
  61. package/dist/run-summary/index.d.ts +5 -0
  62. package/dist/run-summary/index.js +5 -0
  63. package/dist/run-summary/path.d.ts +25 -0
  64. package/dist/run-summary/path.js +78 -0
  65. package/dist/run-summary/share.d.ts +25 -0
  66. package/dist/run-summary/share.js +106 -0
  67. package/dist/run-summary/types.d.ts +134 -0
  68. package/dist/run-summary/types.js +28 -0
  69. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  70. package/dist/scope/acceptance-activate-gate.js +70 -0
  71. package/dist/scope/acceptance-evidence.d.ts +34 -0
  72. package/dist/scope/acceptance-evidence.js +84 -10
  73. package/dist/scope/decompose.d.ts +5 -0
  74. package/dist/scope/decompose.js +11 -2
  75. package/dist/scope/index.d.ts +1 -0
  76. package/dist/scope/index.js +1 -0
  77. package/dist/scope/transition.js +5 -0
  78. package/dist/session/ac-pass-banking.d.ts +221 -0
  79. package/dist/session/ac-pass-banking.js +761 -0
  80. package/dist/session/deposit-sha.d.ts +49 -0
  81. package/dist/session/deposit-sha.js +121 -0
  82. package/dist/session/effort-budget.d.ts +6 -0
  83. package/dist/session/effort-budget.js +21 -3
  84. package/dist/session/index.d.ts +5 -1
  85. package/dist/session/index.js +5 -1
  86. package/dist/session/orientation-compression.d.ts +127 -0
  87. package/dist/session/orientation-compression.js +435 -0
  88. package/dist/session/orientation-state.d.ts +29 -0
  89. package/dist/session/orientation-state.js +85 -0
  90. package/dist/session/session-start.d.ts +31 -0
  91. package/dist/session/session-start.js +219 -22
  92. package/dist/session/toolchain-preflight.d.ts +79 -0
  93. package/dist/session/toolchain-preflight.js +211 -0
  94. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  95. package/dist/triage/bootstrap/gitignore.js +8 -2
  96. package/dist/triage/bootstrap/index.js +15 -3
  97. package/dist/triage/classify/index.d.ts +1 -0
  98. package/dist/triage/classify/index.js +2 -0
  99. package/dist/triage/classify/label-mirror.js +16 -0
  100. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  101. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  102. package/dist/triage/reconcile/reconcile.js +28 -6
  103. package/dist/triage/summary/index.js +12 -4
  104. package/dist/triage/welcome/default-mode.js +16 -0
  105. package/dist/ts-check-lane/run-lane.js +21 -4
  106. package/dist/vbrief-validate/conformance.js +7 -0
  107. package/dist/verify-ac/clauses.d.ts +51 -0
  108. package/dist/verify-ac/clauses.js +490 -0
  109. package/dist/verify-ac/evaluate.d.ts +55 -0
  110. package/dist/verify-ac/evaluate.js +145 -0
  111. package/dist/verify-ac/flag.d.ts +35 -0
  112. package/dist/verify-ac/flag.js +104 -0
  113. package/dist/verify-ac/index.d.ts +10 -0
  114. package/dist/verify-ac/index.js +10 -0
  115. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  116. package/package.json +15 -3
  117. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  118. package/dist/policy/coverage-check-resume-presets.js +0 -228
  119. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  120. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -7,6 +7,8 @@ import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposi
7
7
  import { hostContentSurfaceToDict, maybeFormatHostContentSurfaceLines, } from "../platform/host-content-surface.js";
8
8
  import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
9
9
  import { ceremonyDialToDict, formatCeremonyDialStatusLine, mergeCeremonyDialDeferrals, resolveCeremonyDial, resolveSessionCeremonyDialInputs, } from "../policy/ceremony-dial.js";
10
+ import { emitCeremonyDialEscalationEvaluation, evaluateSessionStartCeremonyDialEscalation, formatCeremonyDialPinBypassLine, isCeremonyStartTierPinned, resolveCeremonyStartTierProvenance, } from "../policy/ceremony-dial-escalation.js";
11
+ import { maybeFormatCoverageCheckResumeDisclosure } from "../policy/coverage-debt.js";
10
12
  import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
11
13
  import { disclosureLine } from "../policy/disclosure.js";
12
14
  import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
@@ -14,19 +16,21 @@ import { humanMergeDisclosureLine, resolveHumanMergePolicy, } from "../policy/re
14
16
  import { resolvePolicy } from "../policy/resolve.js";
15
17
  import { maybeFormatProductSignalConsentPrompt } from "../product-signal/consent-prompt.js";
16
18
  import { formatFrameworkCommand } from "../render/framework-commands.js";
19
+ import { RunSummaryEmitter } from "../run-summary/emit.js";
17
20
  import { formatScmReadinessLines, probeScmReadiness, scmReadinessToDict, } from "../scm/readiness.js";
18
21
  import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
19
22
  import { runDefaultMode } from "../triage/welcome/default-mode.js";
20
23
  import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
21
24
  import { emitSessionValueReadback } from "../value/readback.js";
22
25
  import { verifyRequiredTools } from "../verify-env/verify-tools.js";
23
- import { maybeFormatCoverageCheckResumeNudge } from "./coverage-check-resume-nudge.js";
24
26
  import { effortBudgetToDict, maybeFormatEffortBudgetLines, resolveProductionHostEffortDescriptor, } from "./effort-budget.js";
25
27
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
28
+ import { runOrientationCompression, } from "./orientation-compression.js";
26
29
  import { emitSessionStartProcessCost } from "./process-cost.js";
27
30
  import { probeSessionReleaseAvailability, } from "./release-availability.js";
28
31
  import { newRitualStatePayload, readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
29
32
  import { timestampIso } from "./time.js";
33
+ import { runToolchainPreflight, toolchainPreflightToDict, } from "./toolchain-preflight.js";
30
34
  export const SESSION_POSTURES = ["read-only", "mutation"];
31
35
  export const READ_ONLY_POSTURE = "read-only";
32
36
  export const MUTATION_POSTURE = "mutation";
@@ -45,6 +49,18 @@ export const QUICK_STEPS = [
45
49
  "verify_tools",
46
50
  ];
47
51
  export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
52
+ /** Standing one-liner when coverageDebt/checkResume is non-default (#3314). */
53
+ function pushCoverageCheckResumeDisclosure(lines, projectRoot) {
54
+ try {
55
+ const line = maybeFormatCoverageCheckResumeDisclosure(projectRoot);
56
+ if (line !== null) {
57
+ lines.push(line);
58
+ }
59
+ }
60
+ catch {
61
+ // best-effort — ritual must not abort
62
+ }
63
+ }
48
64
  /** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
49
65
  export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
50
66
  /** Human-readable skip notice when optional network is off the hot path (#2991). */
@@ -524,16 +540,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
524
540
  if (humanMergeLine !== null) {
525
541
  lines.push(humanMergeLine);
526
542
  }
527
- // #3189: re-arm still surfaces undecided coverage/check-resume once per ritual.
528
- try {
529
- const coverageNudge = maybeFormatCoverageCheckResumeNudge({ projectRoot });
530
- if (coverageNudge.length > 0) {
531
- lines.push(coverageNudge.trimEnd());
532
- }
533
- }
534
- catch {
535
- // best-effort — re-arm must not abort
536
- }
543
+ pushCoverageCheckResumeDisclosure(lines, projectRoot);
537
544
  const priorQuick = eligibility.state.quickSteps;
538
545
  const priorTriage = priorQuick.triage_welcome ?? ritualStep({ ok: true, ts: instant });
539
546
  // Greptile P1: legacy ritual-state without verify_tools must re-run tools —
@@ -799,6 +806,11 @@ export function runSessionStart(projectRoot, options = {}) {
799
806
  env: options.env,
800
807
  });
801
808
  const ceremonyDialSelection = options.ceremonyDial ?? resolveCeremonyDial(projectRoot, { inputs: resolvedDialInputs });
809
+ const startTierProvenance = resolveCeremonyStartTierProvenance({
810
+ selection: ceremonyDialSelection,
811
+ injectedSelection: options.ceremonyDial !== undefined,
812
+ hint: options.ceremonyDialStartTierProvenance,
813
+ });
802
814
  const effectiveDeferrals = mergeCeremonyDialDeferrals(deferrals, ceremonyDialSelection);
803
815
  const skipFatPath = ceremonyDialSelection.profile.skipFatPath;
804
816
  const { head: gitHeadValue, error: gitError } = gitHead(projectRoot, runGit);
@@ -828,7 +840,13 @@ export function runSessionStart(projectRoot, options = {}) {
828
840
  const quickSteps = recordDeferredSteps(QUICK_STEPS, effectiveDeferrals, instant);
829
841
  const gatedSteps = recordDeferredSteps(GATED_STEPS, effectiveDeferrals, instant);
830
842
  const lines = [];
831
- lines.push(formatCeremonyDialStatusLine(ceremonyDialSelection));
843
+ lines.push(formatCeremonyDialStatusLine(ceremonyDialSelection, {
844
+ startTierProvenance,
845
+ }));
846
+ const pinBypassLine = formatCeremonyDialPinBypassLine(startTierProvenance);
847
+ if (pinBypassLine !== null) {
848
+ lines.push(pinBypassLine);
849
+ }
832
850
  if (provisionalDial.reasons.length > 0 && options.ceremonyDial === undefined) {
833
851
  lines.push(`[deft ceremony-dial] provisional: ${provisionalDial.reasons.join("; ")}`);
834
852
  }
@@ -916,6 +934,8 @@ export function runSessionStart(projectRoot, options = {}) {
916
934
  else {
917
935
  stepTimings.push({ name: "branch_policy", duration_ms: 0, skipped: true });
918
936
  }
937
+ // Standing disclosure is independent of branch_policy deferral (#3314 / Greptile).
938
+ pushCoverageCheckResumeDisclosure(lines, projectRoot);
919
939
  // #3214 / #3156: verify_tools is mutation readiness — always run, even under
920
940
  // rapid/minimal. Dial skipFatPath only lightens *ceremony* (triage welcome,
921
941
  // optional network, staleness tickler), never readiness gates.
@@ -952,6 +972,120 @@ export function runSessionStart(projectRoot, options = {}) {
952
972
  });
953
973
  stepTimings.push({ name: "verify_tools", duration_ms: durationMs });
954
974
  }
975
+ // #3282 / #3286: orientation compression — compose doctor + toolchain preflight
976
+ // (+ agents:refresh / cache-fresh deposit-sha fast-paths) as inline sections with
977
+ // per-section status lines. Composition of existing steps, not a new monolith.
978
+ // Read-only posture never reaches here (#2176). Dual-path Later (`deft orient`)
979
+ // remains open — see orientation bundle.later.
980
+ let toolchainPreflightResult = null;
981
+ let orientationBundle = null;
982
+ {
983
+ const stepStarted = performance.now();
984
+ if (options.orientation === null) {
985
+ // Explicit skip (tests): still honour legacy preflight-only path when set.
986
+ if (options.toolchainPreflight === null) {
987
+ stepTimings.push({ name: "orientation", duration_ms: 0, skipped: true });
988
+ stepTimings.push({ name: "toolchain_preflight", duration_ms: 0, skipped: true });
989
+ }
990
+ else if (options.toolchainPreflight !== undefined) {
991
+ toolchainPreflightResult = options.toolchainPreflight;
992
+ lines.push(...toolchainPreflightResult.lines);
993
+ stepTimings.push({
994
+ name: "toolchain_preflight",
995
+ duration_ms: elapsedMs(stepStarted),
996
+ });
997
+ }
998
+ else {
999
+ stepTimings.push({ name: "orientation", duration_ms: 0, skipped: true });
1000
+ }
1001
+ }
1002
+ else if (options.orientation !== undefined) {
1003
+ orientationBundle = options.orientation;
1004
+ toolchainPreflightResult = orientationBundle.preflight;
1005
+ lines.push(...orientationBundle.lines);
1006
+ for (const section of orientationBundle.sections) {
1007
+ stepTimings.push({
1008
+ name: section.name,
1009
+ duration_ms: section.durationMs,
1010
+ ...(section.status === "skipped" ? { skipped: true } : {}),
1011
+ });
1012
+ // Record gated ritual steps so verify:session-ritual can skip re-runs.
1013
+ // Do not overwrite explicit --defer (deferred_reason) records.
1014
+ if ((section.name === "doctor" || section.name === "cache_fresh") &&
1015
+ !gatedSteps[section.name]?.deferred_reason) {
1016
+ gatedSteps[section.name] = ritualStep({
1017
+ ok: section.ok,
1018
+ ts: instant,
1019
+ message: section.lines[0] ?? section.status,
1020
+ exitCode: section.exitCode,
1021
+ durationMs: section.durationMs,
1022
+ command: section.name === "doctor" ? ["doctor"] : ["verify:cache-fresh"],
1023
+ });
1024
+ }
1025
+ }
1026
+ stepTimings.push({ name: "orientation", duration_ms: elapsedMs(stepStarted) });
1027
+ }
1028
+ else {
1029
+ try {
1030
+ // Skip composed doctor/cache_fresh work when those gated steps are deferred.
1031
+ const doctorDeferred = Boolean(gatedSteps.doctor?.deferred_reason);
1032
+ const cacheDeferred = Boolean(gatedSteps.cache_fresh?.deferred_reason);
1033
+ orientationBundle = runOrientationCompression({
1034
+ projectRoot,
1035
+ frameworkRoot: options.frameworkRoot ?? projectRoot,
1036
+ compact: options.compact,
1037
+ env: options.env,
1038
+ now: instant,
1039
+ toolchainPreflight: options.toolchainPreflight,
1040
+ toolchainPreflightOptions: options.toolchainPreflightOptions,
1041
+ includeDoctor: !doctorDeferred,
1042
+ includeCacheFresh: !cacheDeferred,
1043
+ ...options.orientationOptions,
1044
+ });
1045
+ toolchainPreflightResult = orientationBundle.preflight;
1046
+ lines.push(...orientationBundle.lines);
1047
+ for (const section of orientationBundle.sections) {
1048
+ stepTimings.push({
1049
+ name: section.name,
1050
+ duration_ms: section.durationMs,
1051
+ ...(section.status === "skipped" ? { skipped: true } : {}),
1052
+ });
1053
+ if ((section.name === "doctor" || section.name === "cache_fresh") &&
1054
+ !gatedSteps[section.name]?.deferred_reason) {
1055
+ gatedSteps[section.name] = ritualStep({
1056
+ ok: section.ok,
1057
+ ts: instant,
1058
+ message: section.lines[0] ?? section.status,
1059
+ exitCode: section.exitCode,
1060
+ durationMs: section.durationMs,
1061
+ command: section.name === "doctor" ? ["doctor"] : ["verify:cache-fresh"],
1062
+ });
1063
+ }
1064
+ }
1065
+ stepTimings.push({ name: "orientation", duration_ms: elapsedMs(stepStarted) });
1066
+ }
1067
+ catch {
1068
+ // fail-open: orientation composition must not abort session:start —
1069
+ // fall back to preflight-only (#3282).
1070
+ try {
1071
+ if (options.toolchainPreflight !== null) {
1072
+ toolchainPreflightResult =
1073
+ options.toolchainPreflight ??
1074
+ runToolchainPreflight({
1075
+ projectRoot,
1076
+ frameworkRoot: options.frameworkRoot ?? projectRoot,
1077
+ ...options.toolchainPreflightOptions,
1078
+ });
1079
+ lines.push(...toolchainPreflightResult.lines);
1080
+ }
1081
+ }
1082
+ catch {
1083
+ // ignore
1084
+ }
1085
+ stepTimings.push({ name: "orientation", duration_ms: elapsedMs(stepStarted) });
1086
+ }
1087
+ }
1088
+ }
955
1089
  // #3214: rapid/minimal skip informational cold-path ceremony only.
956
1090
  if (!quickSteps.triage_welcome && !skipFatPath) {
957
1091
  const stepStarted = performance.now();
@@ -1065,20 +1199,12 @@ export function runSessionStart(projectRoot, options = {}) {
1065
1199
  if (consentPrompt.length > 0) {
1066
1200
  lines.push(consentPrompt.trimEnd());
1067
1201
  }
1068
- // #3189: skippable coverageDebt/checkResume project-decision nudge (fail-open; never blocks).
1069
- try {
1070
- const coverageNudge = maybeFormatCoverageCheckResumeNudge({ projectRoot });
1071
- if (coverageNudge.length > 0) {
1072
- lines.push(coverageNudge.trimEnd());
1073
- }
1074
- }
1075
- catch {
1076
- // best-effort operator advisory — session start must not abort
1077
- }
1078
1202
  const writeStarted = performance.now();
1079
1203
  const coldSessionId = (options.newSessionId ?? randomUUID)();
1080
1204
  const dialDict = {
1081
1205
  ...ceremonyDialToDict(ceremonyDialSelection),
1206
+ start_tier: ceremonyDialSelection.depth,
1207
+ start_tier_provenance: startTierProvenance,
1082
1208
  provisional: {
1083
1209
  taskSize: provisionalDial.taskSize,
1084
1210
  modelTier: provisionalDial.modelTier,
@@ -1086,6 +1212,7 @@ export function runSessionStart(projectRoot, options = {}) {
1086
1212
  reasons: [...provisionalDial.reasons],
1087
1213
  },
1088
1214
  };
1215
+ const preflightDict = toolchainPreflightResult !== null ? toolchainPreflightToDict(toolchainPreflightResult) : null;
1089
1216
  const payload = {
1090
1217
  ...newRitualStatePayload({
1091
1218
  sessionId: coldSessionId,
@@ -1097,6 +1224,8 @@ export function runSessionStart(projectRoot, options = {}) {
1097
1224
  }),
1098
1225
  // #3214: record dial choice on ritual-state for audit / later re-arm context.
1099
1226
  ceremony_dial: dialDict,
1227
+ // #3282: durable preflight snapshot for harness / later check degraded mode.
1228
+ ...(preflightDict !== null ? { toolchain_preflight: preflightDict } : {}),
1100
1229
  };
1101
1230
  let statePath;
1102
1231
  try {
@@ -1120,8 +1249,58 @@ export function runSessionStart(projectRoot, options = {}) {
1120
1249
  .filter(([, step]) => !step.ok && !step.deferred_reason)
1121
1250
  .map(([name]) => name);
1122
1251
  // verify_tools is recorded on quick_steps; failure makes ready=false.
1252
+ // #3282: toolchain preflight degraded mode does NOT flip ready=false by itself —
1253
+ // agents still proceed with a named skip report at check time.
1123
1254
  const code = failed.length > 0 ? 1 : 0;
1124
1255
  const totalMs = elapsedMs(overallStarted);
1256
+ // #3282 / #3286: event-driven run-summary (dial + preflight + orientation call
1257
+ // count for dual-path Later graduation trigger) — fail-open, silent when unset.
1258
+ if (options.emitRunSummary !== false) {
1259
+ try {
1260
+ const emitter = new RunSummaryEmitter({
1261
+ projectRoot,
1262
+ sessionId: coldSessionId,
1263
+ env: options.env,
1264
+ });
1265
+ emitter.emitSessionStart({
1266
+ ceremony_dial: dialDict,
1267
+ preflight: preflightDict ?? undefined,
1268
+ ceremony_tier: COLD_CEREMONY_TIER,
1269
+ ready: code === 0,
1270
+ exit_code: code,
1271
+ ...(orientationBundle !== null
1272
+ ? {
1273
+ orientation_call_count: orientationBundle.orientationCallCount,
1274
+ orientation_compact: orientationBundle.compact,
1275
+ deposit_sha: orientationBundle.depositSha,
1276
+ orientation_later_status: orientationBundle.later.status,
1277
+ orientation_sections: orientationBundle.sections.map((s) => ({
1278
+ name: s.name,
1279
+ status: s.status,
1280
+ ok: s.ok,
1281
+ sha_match: s.shaMatch,
1282
+ })),
1283
+ }
1284
+ : {}),
1285
+ });
1286
+ emitter.emitKnownToolTurnDenominator();
1287
+ // #3319: evaluate escalate-on-evidence only when #3274 is live. A pin
1288
+ // emits nothing so never-evaluated stays distinguishable from declined.
1289
+ if (!isCeremonyStartTierPinned(startTierProvenance)) {
1290
+ emitCeremonyDialEscalationEvaluation({
1291
+ projectRoot,
1292
+ sessionId: coldSessionId,
1293
+ env: options.env,
1294
+ evaluation: evaluateSessionStartCeremonyDialEscalation({
1295
+ selection: ceremonyDialSelection,
1296
+ }),
1297
+ });
1298
+ }
1299
+ }
1300
+ catch {
1301
+ // fail-open
1302
+ }
1303
+ }
1125
1304
  // #3117: bind live deposit generation when payload surfaces load (cold path).
1126
1305
  let freshnessBind = null;
1127
1306
  try {
@@ -1153,6 +1332,24 @@ export function runSessionStart(projectRoot, options = {}) {
1153
1332
  exit_code: code,
1154
1333
  ceremony_tier: COLD_CEREMONY_TIER,
1155
1334
  ceremony_dial: dialDict,
1335
+ ...(preflightDict !== null ? { toolchain_preflight: preflightDict } : {}),
1336
+ ...(orientationBundle !== null
1337
+ ? {
1338
+ orientation: {
1339
+ deposit_sha: orientationBundle.depositSha,
1340
+ compact: orientationBundle.compact,
1341
+ call_count: orientationBundle.orientationCallCount,
1342
+ later: orientationBundle.later,
1343
+ sections: orientationBundle.sections.map((s) => ({
1344
+ name: s.name,
1345
+ status: s.status,
1346
+ ok: s.ok,
1347
+ sha_match: s.shaMatch,
1348
+ exit_code: s.exitCode,
1349
+ })),
1350
+ },
1351
+ }
1352
+ : {}),
1156
1353
  state_path: statePath,
1157
1354
  ...(freshnessBind ? { freshness: freshnessBind } : {}),
1158
1355
  quick_steps: quickSteps,
@@ -0,0 +1,79 @@
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 { type CheckGateSpec } from "../check/gate-lists.js";
12
+ export declare const TOOLCHAIN_PREFLIGHT_TOOLS: readonly ["task", "pnpm", "node", "git"];
13
+ export type ToolchainPreflightTool = (typeof TOOLCHAIN_PREFLIGHT_TOOLS)[number];
14
+ export type ToolchainPreflightStatus = "ok" | "missing" | "degraded";
15
+ /** How a missing tool affects check dispatch (#3335). */
16
+ export type ToolchainPreflightImpact = "none" | "degraded";
17
+ export interface ToolchainPreflightFinding {
18
+ readonly tool: ToolchainPreflightTool | "cli_dist";
19
+ readonly present: boolean;
20
+ /** Machine-stable cause code (never embeds env values). */
21
+ readonly cause: string | null;
22
+ /** Operator/agent remedy command or instruction. */
23
+ readonly remedy: string | null;
24
+ /**
25
+ * Missing-tool impact. `none` = present false but no install remedy and
26
+ * check does not skip CLI-native gates (#3335).
27
+ */
28
+ readonly impact?: ToolchainPreflightImpact | null;
29
+ }
30
+ export interface ToolchainPreflightResult {
31
+ readonly status: ToolchainPreflightStatus;
32
+ readonly ok: boolean;
33
+ /** True when missing tools would force check into degraded/skip mode. */
34
+ readonly degraded: boolean;
35
+ readonly findings: readonly ToolchainPreflightFinding[];
36
+ /** Human lines for session:start stdout (named cause + remedy). */
37
+ readonly lines: readonly string[];
38
+ /**
39
+ * Gate ids that check should skip when this preflight is degraded.
40
+ * Product-ordering of remaining gates is owned by #3284.
41
+ */
42
+ readonly skipGateIds: readonly string[];
43
+ }
44
+ export interface ToolchainPreflightOptions {
45
+ readonly projectRoot?: string;
46
+ readonly frameworkRoot?: string;
47
+ /** which(name) seam for tests. */
48
+ readonly which?: (name: string) => string | null;
49
+ /** existsSync seam for tests. */
50
+ readonly exists?: (path: string) => boolean;
51
+ /**
52
+ * When true (default), also probe for a built CLI dist under frameworkRoot
53
+ * (framework-source dogfood). Consumers without a local dist are fine when
54
+ * a global `deft`/`directive` is on PATH.
55
+ */
56
+ readonly probeCliDist?: boolean;
57
+ /** Active check composition. Defaults from consumer vs framework context. */
58
+ readonly composedGates?: readonly CheckGateSpec[];
59
+ /**
60
+ * Consumer deposit (not framework source). When omitted, inferred from
61
+ * frameworkRoot vs projectRoot (#3335 / #3324).
62
+ */
63
+ readonly consumerDeposit?: boolean;
64
+ }
65
+ /**
66
+ * Gates that specifically need a package manager / built dist.
67
+ * When task/node is missing, the orchestrator skips *all* scheduled gates
68
+ * (dynamic from gate-lists), not this static list — avoids hardcode drift.
69
+ */
70
+ export declare const PNPM_DEPENDENT_GATE_IDS: readonly string[];
71
+ /** Sentinel skip id meaning "every gate in the active check composition". */
72
+ export declare const SKIP_ALL_GATES = "*";
73
+ /**
74
+ * Run done-gate toolchain preflight. Pure probe — never installs or builds.
75
+ */
76
+ export declare function runToolchainPreflight(options?: ToolchainPreflightOptions): ToolchainPreflightResult;
77
+ /** Serialize preflight for run-summary / ritual-state (no env values). */
78
+ export declare function toolchainPreflightToDict(result: ToolchainPreflightResult): Record<string, unknown>;
79
+ //# sourceMappingURL=toolchain-preflight.d.ts.map
@@ -0,0 +1,211 @@
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 { allGatesCliDispatchable, GLOBAL_CLI_REMEDY, isCliNativeGate, } from "../check/cli-native-gates.js";
14
+ import { isFrameworkRepoRoot, isFrameworkSourceContext } from "../check/context.js";
15
+ import { CONSUMER_CHECK_GATES, checkGateId, FRAMEWORK_CHECK_GATES, } from "../check/gate-lists.js";
16
+ import { defaultWhich } from "../doctor/which.js";
17
+ export const TOOLCHAIN_PREFLIGHT_TOOLS = ["task", "pnpm", "node", "git"];
18
+ const REMEDY = {
19
+ task: "Install go-task: https://taskfile.dev/installation/ (e.g. winget install Task.Task / brew install go-task)",
20
+ pnpm: "Enable pnpm via corepack: corepack enable && corepack prepare pnpm@latest --activate",
21
+ node: "Install Node 20+ (see .nvmrc); then corepack enable",
22
+ git: "Install Git: https://git-scm.com/downloads",
23
+ };
24
+ const CAUSE = {
25
+ task: "go-task binary not found on PATH",
26
+ pnpm: "pnpm binary not found on PATH",
27
+ node: "node binary not found on PATH",
28
+ git: "git binary not found on PATH",
29
+ };
30
+ /**
31
+ * Gates that specifically need a package manager / built dist.
32
+ * When task/node is missing, the orchestrator skips *all* scheduled gates
33
+ * (dynamic from gate-lists), not this static list — avoids hardcode drift.
34
+ */
35
+ export const PNPM_DEPENDENT_GATE_IDS = [
36
+ "toolchain:check",
37
+ "toolchain:check-consumer",
38
+ "ts:check-lane",
39
+ ];
40
+ /** Sentinel skip id meaning "every gate in the active check composition". */
41
+ export const SKIP_ALL_GATES = "*";
42
+ function probeTool(tool, which) {
43
+ const present = which(tool) !== null;
44
+ return {
45
+ tool,
46
+ present,
47
+ cause: present ? null : CAUSE[tool],
48
+ remedy: present ? null : REMEDY[tool],
49
+ };
50
+ }
51
+ function probeCliDist(frameworkRoot, which, exists) {
52
+ const globalCli = which("deft") !== null || which("directive") !== null;
53
+ if (globalCli) {
54
+ return { tool: "cli_dist", present: true, cause: null, remedy: null };
55
+ }
56
+ if (frameworkRoot) {
57
+ const distBin = join(frameworkRoot, "packages", "cli", "dist", "bin.js");
58
+ if (exists(distBin)) {
59
+ return { tool: "cli_dist", present: true, cause: null, remedy: null };
60
+ }
61
+ return {
62
+ tool: "cli_dist",
63
+ present: false,
64
+ cause: "CLI dist missing and no global deft/directive on PATH",
65
+ remedy: "Run `task build` (framework source) or `npm i -g @deftai/directive@latest`",
66
+ };
67
+ }
68
+ return {
69
+ tool: "cli_dist",
70
+ present: false,
71
+ cause: "no global deft/directive on PATH",
72
+ remedy: "Install: npm i -g @deftai/directive@latest",
73
+ };
74
+ }
75
+ function formatFindingLine(finding) {
76
+ if (finding.present) {
77
+ return `[deft preflight] ${finding.tool}: ok`;
78
+ }
79
+ if (finding.impact === "none") {
80
+ const cause = finding.cause ?? "absent";
81
+ return `[deft preflight] ${finding.tool}: absent (impact: none) — ${cause}`;
82
+ }
83
+ const cause = finding.cause ?? "missing";
84
+ const remedy = finding.remedy ?? "install the tool";
85
+ return `[deft preflight] ${finding.tool}: MISSING — cause: ${cause}; remedy: ${remedy}`;
86
+ }
87
+ function inferConsumerDeposit(options) {
88
+ if (options.consumerDeposit !== undefined)
89
+ return options.consumerDeposit;
90
+ const project = options.projectRoot;
91
+ if (project === undefined)
92
+ return false;
93
+ // Same-path roots are framework-source only when the tree is the source repo.
94
+ if (isFrameworkRepoRoot(project))
95
+ return false;
96
+ const framework = options.frameworkRoot ?? project;
97
+ return !isFrameworkSourceContext(framework, project);
98
+ }
99
+ /**
100
+ * Run done-gate toolchain preflight. Pure probe — never installs or builds.
101
+ */
102
+ export function runToolchainPreflight(options = {}) {
103
+ const which = options.which ?? defaultWhich;
104
+ const exists = options.exists ?? existsSync;
105
+ const consumerDeposit = inferConsumerDeposit(options);
106
+ const composed = options.composedGates ?? (consumerDeposit ? CONSUMER_CHECK_GATES : FRAMEWORK_CHECK_GATES);
107
+ const allCli = allGatesCliDispatchable(composed);
108
+ const findings = [];
109
+ for (const tool of TOOLCHAIN_PREFLIGHT_TOOLS) {
110
+ findings.push(probeTool(tool, which));
111
+ }
112
+ if (options.probeCliDist !== false) {
113
+ findings.push(probeCliDist(options.frameworkRoot ?? options.projectRoot, which, exists));
114
+ }
115
+ const cliPresent = findings.some((f) => f.tool === "cli_dist" && f.present);
116
+ const taskMissing = findings.some((f) => f.tool === "task" && !f.present);
117
+ const pnpmMissing = findings.some((f) => f.tool === "pnpm" && !f.present);
118
+ const nodeMissing = findings.some((f) => f.tool === "node" && !f.present);
119
+ // #3335: in a deposit whose composed gates are all CLI-dispatchable, a
120
+ // missing task binary is impact none — no install-go-task remedy.
121
+ if (taskMissing && allCli) {
122
+ const idx = findings.findIndex((f) => f.tool === "task");
123
+ const prior = findings[idx];
124
+ if (idx >= 0 && prior !== undefined) {
125
+ findings[idx] = {
126
+ ...prior,
127
+ impact: "none",
128
+ remedy: cliPresent || nodeMissing ? null : GLOBAL_CLI_REMEDY,
129
+ cause: cliPresent
130
+ ? "go-task absent; CLI-native gates dispatch via global deft/directive (#3335)"
131
+ : "go-task absent and no global deft/directive CLI",
132
+ };
133
+ }
134
+ }
135
+ const missingCritical = findings.filter((f) => {
136
+ if (f.present || f.impact === "none")
137
+ return false;
138
+ return f.tool === "task" || f.tool === "pnpm" || f.tool === "node";
139
+ });
140
+ const skip = new Set();
141
+ if (nodeMissing) {
142
+ skip.add(SKIP_ALL_GATES);
143
+ }
144
+ else if (taskMissing && !allCli) {
145
+ if (!cliPresent) {
146
+ skip.add(SKIP_ALL_GATES);
147
+ }
148
+ else {
149
+ for (const spec of composed) {
150
+ const id = checkGateId(spec);
151
+ if (!isCliNativeGate(id))
152
+ skip.add(id);
153
+ }
154
+ }
155
+ }
156
+ else if (taskMissing && allCli && !cliPresent) {
157
+ skip.add(SKIP_ALL_GATES);
158
+ }
159
+ else if (pnpmMissing) {
160
+ for (const id of PNPM_DEPENDENT_GATE_IDS) {
161
+ skip.add(id);
162
+ }
163
+ }
164
+ const degraded = missingCritical.length > 0 || (taskMissing && allCli && !cliPresent);
165
+ const ok = !degraded;
166
+ const status = ok ? "ok" : "degraded";
167
+ const lines = [];
168
+ lines.push(`[deft preflight] toolchain status: ${status}`);
169
+ for (const finding of findings) {
170
+ if (!finding.present) {
171
+ lines.push(formatFindingLine(finding));
172
+ }
173
+ }
174
+ if (ok) {
175
+ lines.push(taskMissing && allCli
176
+ ? "[deft preflight] done-gate toolchain ready (CLI dispatch; go-task not required) (#3335)"
177
+ : "[deft preflight] done-gate toolchain ready (task, pnpm, node)");
178
+ }
179
+ else {
180
+ lines.push("[deft preflight] degraded mode: directive check will skip toolchain-dependent gates " +
181
+ "with a named skip report rather than bootstrapping product-unneeded tooling (#3282)");
182
+ if (skip.size > 0) {
183
+ lines.push(`[deft preflight] gates subject to skip when degraded: ${[...skip].sort().join(", ")}`);
184
+ }
185
+ }
186
+ return {
187
+ status,
188
+ ok,
189
+ degraded,
190
+ findings,
191
+ lines,
192
+ skipGateIds: [...skip].sort(),
193
+ };
194
+ }
195
+ /** Serialize preflight for run-summary / ritual-state (no env values). */
196
+ export function toolchainPreflightToDict(result) {
197
+ return {
198
+ status: result.status,
199
+ ok: result.ok,
200
+ degraded: result.degraded,
201
+ findings: result.findings.map((f) => ({
202
+ tool: f.tool,
203
+ present: f.present,
204
+ cause: f.cause,
205
+ remedy: f.remedy,
206
+ ...(f.impact !== undefined ? { impact: f.impact } : {}),
207
+ })),
208
+ skip_gate_ids: [...result.skipGateIds],
209
+ };
210
+ }
211
+ //# sourceMappingURL=toolchain-preflight.js.map
@@ -8,7 +8,7 @@ export declare function gitignoreTriageCacheEntries(projectRoot: string): readon
8
8
  export declare function gitattributesTriageCacheGlob(projectRoot: string): string;
9
9
  export declare const GITATTRIBUTES_EVAL_RULE = "vbrief/.triage-cache/*.jsonl merge=union";
10
10
  export declare const FORBIDDEN_BLANKET_EVAL_LINES: readonly string[];
11
- export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/` \u2014 triage working-set files\n\nThis directory holds JSON-lines logs and scratch files that Deft triage and\nslicing workflows emit. Deft configures your repo's `.gitignore` and\n`.gitattributes` so some files stay local while team-shared records can be\ncommitted.\n\n## What lives here\n\n| File | Committed? | Notes |\n| --- | --- | --- |\n| `slices.jsonl` | Yes | Team-shared cohort records from slicing skills. New teammates use prior cohort outputs to spot orphans and avoid re-slicing the same scope. |\n| `candidates.jsonl` | No | Your local triage accept / defer / reject stream. Re-create on a fresh clone with `deft triage:bootstrap`. |\n| `summary-history.jsonl` | No | Local history of `deft triage:summary` output; not required for day-to-day work. |\n| `scope-lifecycle.jsonl` | No | Local audit trail for scope demotions (`deft scope:demote`). Each operator's stream stays on their machine. |\n| `decompositions/` | No | Draft story-decomposition scratch. Produced child story xBRIEFs live in lifecycle folders via `deft scope:decompose`. |\n| `doctor-state.json` | No | Per-clone throttle state for `deft doctor` re-probe timing. |\n| `staleness-tickler-state.json` | No | Per-clone upgrade-tickler throttle state. |\n| `release-availability-state.json` | No | Per-clone release-availability probe throttle state. |\n\nPaths listed as \"No\" above are added to `.gitignore` during bootstrap; anything\nnot listed remains committable by default. The selective ignore entries live in\nthe repo-root `.gitignore` (`vbrief/.triage-cache/candidates.jsonl`,\n`vbrief/.triage-cache/summary-history.jsonl`, `vbrief/.triage-cache/scope-lifecycle.jsonl`,\n`vbrief/.triage-cache/decompositions/`, `vbrief/.triage-cache/doctor-state.json`,\n`vbrief/.triage-cache/staleness-tickler-state.json`, and\n`vbrief/.triage-cache/release-availability-state.json`).\n\n## Fresh clone\n\nIf `candidates.jsonl` is missing, run:\n\n```\ndeft triage:bootstrap\n```\n\nBootstrap rebuilds the local candidates log without altering committed\n`slices.jsonl`.\n\n## Merge behavior for `*.jsonl`\n\nThe repo-root `.gitattributes` may declare:\n\n```\nvbrief/.triage-cache/*.jsonl merge=union\n```\n\nThe `union` merge driver concatenates both sides' appended lines on auto-merge,\nso parallel append-only edits to the same JSON-lines file rebase without manual\nconflict surgery. It does not dedupe semantically similar records \u2014 downstream\nreaders should tolerate duplicate-looking entries.\n\n## See also\n\n- `.gitignore` \u2014 selective ignore rules for operator-private files\n- `.gitattributes` \u2014 merge driver for committed JSON-lines logs\n";
11
+ export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/` \u2014 triage working-set files\n\nThis directory holds JSON-lines logs and scratch files that Deft triage and\nslicing workflows emit. Deft configures your repo's `.gitignore` and\n`.gitattributes` so some files stay local while team-shared records can be\ncommitted.\n\n## What lives here\n\n| File | Committed? | Notes |\n| --- | --- | --- |\n| `slices.jsonl` | Yes | Team-shared cohort records from slicing skills. New teammates use prior cohort outputs to spot orphans and avoid re-slicing the same scope. |\n| `candidates.jsonl` | No | Your local triage accept / defer / reject stream. Re-create on a fresh clone with `deft triage:bootstrap`. |\n| `summary-history.jsonl` | No | Local history of `deft triage:summary` output; not required for day-to-day work. |\n| `scope-lifecycle.jsonl` | No | Local audit trail for scope demotions (`deft scope:demote`). Each operator's stream stays on their machine. |\n| `decompositions/` | No | Draft story-decomposition scratch. Produced child story xBRIEFs live in lifecycle folders via `deft scope:decompose`. |\n| `doctor-state.json` | No | Per-clone throttle state for `deft doctor` re-probe timing. |\n| `staleness-tickler-state.json` | No | Per-clone upgrade-tickler throttle state. |\n| `release-availability-state.json` | No | Per-clone release-availability probe throttle state. |\n| `scm-label-mirror-discovery-state.json` | No | Per-clone SCM label-mirror discovery tip throttle (#3124). |\n\nPaths listed as \"No\" above are added to `.gitignore` during bootstrap; anything\nnot listed remains committable by default. The selective ignore entries live in\nthe repo-root `.gitignore` (`vbrief/.triage-cache/candidates.jsonl`,\n`vbrief/.triage-cache/summary-history.jsonl`, `vbrief/.triage-cache/scope-lifecycle.jsonl`,\n`vbrief/.triage-cache/decompositions/`, `vbrief/.triage-cache/doctor-state.json`,\n`vbrief/.triage-cache/staleness-tickler-state.json`,\n`vbrief/.triage-cache/release-availability-state.json`, and\n`vbrief/.triage-cache/scm-label-mirror-discovery-state.json`).\n\n## Fresh clone\n\nIf `candidates.jsonl` is missing, run:\n\n```\ndeft triage:bootstrap\n```\n\nBootstrap rebuilds the local candidates log without altering committed\n`slices.jsonl`.\n\n## Merge behavior for `*.jsonl`\n\nThe repo-root `.gitattributes` may declare:\n\n```\nvbrief/.triage-cache/*.jsonl merge=union\n```\n\nThe `union` merge driver concatenates both sides' appended lines on auto-merge,\nso parallel append-only edits to the same JSON-lines file rebase without manual\nconflict surgery. It does not dedupe semantically similar records \u2014 downstream\nreaders should tolerate duplicate-looking entries.\n\n## See also\n\n- `.gitignore` \u2014 selective ignore rules for operator-private files\n- `.gitattributes` \u2014 merge driver for committed JSON-lines logs\n";
12
12
  /** Layout-aware triage-cache README body for the active lifecycle tree (#2344 / #2349). */
13
13
  export declare function generateTriageCacheReadmeBody(projectRoot: string): string;
14
14
  /** Strip an inline `# ...` comment from a gitignore line. */