@deftai/directive-core 0.98.1 → 0.100.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 (144) 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 +401 -75
  4. package/dist/authz/decompose-apply.d.ts +67 -0
  5. package/dist/authz/decompose-apply.js +302 -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/consumer-check-contract/evaluate.d.ts +40 -0
  13. package/dist/consumer-check-contract/evaluate.js +188 -3
  14. package/dist/consumer-check-contract/index.d.ts +1 -1
  15. package/dist/consumer-check-contract/index.js +1 -1
  16. package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
  17. package/dist/content-contracts/skills/greptile-detector.js +202 -4
  18. package/dist/decision/index.d.ts +17 -0
  19. package/dist/decision/index.js +35 -0
  20. package/dist/decision/list.d.ts +47 -0
  21. package/dist/decision/list.js +250 -0
  22. package/dist/decision/schema.d.ts +88 -0
  23. package/dist/decision/schema.js +293 -0
  24. package/dist/decision/write.d.ts +82 -0
  25. package/dist/decision/write.js +427 -0
  26. package/dist/doctor/checks.d.ts +44 -0
  27. package/dist/doctor/checks.js +428 -2
  28. package/dist/doctor/main.d.ts +6 -7
  29. package/dist/doctor/main.js +72 -79
  30. package/dist/doctor/which.d.ts +20 -1
  31. package/dist/doctor/which.js +67 -1
  32. package/dist/eval/report.d.ts +29 -0
  33. package/dist/eval/report.js +69 -0
  34. package/dist/eval/run.d.ts +9 -0
  35. package/dist/eval/run.js +40 -4
  36. package/dist/eval/version-pin.d.ts +99 -0
  37. package/dist/eval/version-pin.js +181 -0
  38. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  39. package/dist/finish-loop/pr-finish-loop.js +116 -5
  40. package/dist/finish-loop/types.d.ts +1 -1
  41. package/dist/hooks/dispatcher.d.ts +9 -5
  42. package/dist/hooks/dispatcher.js +23 -15
  43. package/dist/hooks/readonly.d.ts +6 -3
  44. package/dist/hooks/readonly.js +44 -25
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +2 -0
  47. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  48. package/dist/init-deposit/agent-hooks.js +51 -31
  49. package/dist/intake/issue-ingest.js +29 -0
  50. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  51. package/dist/lifecycle/completed-consistency.js +443 -0
  52. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  53. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  54. package/dist/lifecycle/index.d.ts +2 -0
  55. package/dist/lifecycle/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +52 -0
  57. package/dist/literal-acceptance/capture.js +658 -0
  58. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  59. package/dist/literal-acceptance/evaluate.js +193 -0
  60. package/dist/literal-acceptance/index.d.ts +12 -0
  61. package/dist/literal-acceptance/index.js +12 -0
  62. package/dist/literal-acceptance/run.d.ts +38 -0
  63. package/dist/literal-acceptance/run.js +216 -0
  64. package/dist/literal-acceptance/safety.d.ts +24 -0
  65. package/dist/literal-acceptance/safety.js +251 -0
  66. package/dist/literal-acceptance/types.d.ts +82 -0
  67. package/dist/literal-acceptance/types.js +26 -0
  68. package/dist/platform/host-content-surface.d.ts +74 -0
  69. package/dist/platform/host-content-surface.js +214 -0
  70. package/dist/platform/index.d.ts +1 -0
  71. package/dist/platform/index.js +1 -0
  72. package/dist/policy/ceremony-dial.d.ts +289 -0
  73. package/dist/policy/ceremony-dial.js +980 -0
  74. package/dist/policy/deft-directive-disable.js +12 -2
  75. package/dist/policy/index.d.ts +2 -0
  76. package/dist/policy/index.js +16 -1
  77. package/dist/policy/merge-approval-head.d.ts +129 -0
  78. package/dist/policy/merge-approval-head.js +461 -0
  79. package/dist/policy/require-human-merge.d.ts +5 -0
  80. package/dist/policy/require-human-merge.js +5 -0
  81. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  82. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  83. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  84. package/dist/pr-merge-readiness/compute.js +99 -14
  85. package/dist/pr-merge-readiness/evaluate.js +10 -0
  86. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  87. package/dist/pr-merge-readiness/gh.js +268 -0
  88. package/dist/pr-merge-readiness/index.d.ts +2 -2
  89. package/dist/pr-merge-readiness/index.js +2 -2
  90. package/dist/pr-merge-readiness/mergeability.js +5 -0
  91. package/dist/pr-merge-readiness/output.js +2 -0
  92. package/dist/pr-merge-readiness/parse.js +4 -0
  93. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  94. package/dist/pr-merge-readiness/types.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  96. package/dist/pr-wait-mergeable/cascade.js +115 -1
  97. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  98. package/dist/pr-wait-mergeable/main.js +4 -0
  99. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  100. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  101. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  102. package/dist/preflight/evaluate.d.ts +15 -1
  103. package/dist/preflight/evaluate.js +33 -2
  104. package/dist/render/framework-commands.js +4 -0
  105. package/dist/scope/acceptance-evidence.d.ts +76 -0
  106. package/dist/scope/acceptance-evidence.js +348 -0
  107. package/dist/scope/coverage-map.d.ts +98 -0
  108. package/dist/scope/coverage-map.js +558 -0
  109. package/dist/scope/decompose.js +125 -11
  110. package/dist/scope/effort-activate-gate.d.ts +28 -0
  111. package/dist/scope/effort-activate-gate.js +64 -0
  112. package/dist/scope/index.d.ts +4 -0
  113. package/dist/scope/index.js +4 -0
  114. package/dist/scope/parent-lineage.d.ts +125 -0
  115. package/dist/scope/parent-lineage.js +626 -0
  116. package/dist/scope/transition.d.ts +8 -0
  117. package/dist/scope/transition.js +69 -2
  118. package/dist/session/active-cli.d.ts +79 -0
  119. package/dist/session/active-cli.js +382 -0
  120. package/dist/session/effort-budget.d.ts +130 -0
  121. package/dist/session/effort-budget.js +334 -0
  122. package/dist/session/index.d.ts +2 -0
  123. package/dist/session/index.js +2 -0
  124. package/dist/session/session-start.d.ts +30 -1
  125. package/dist/session/session-start.js +234 -26
  126. package/dist/session/verify-session-ritual.d.ts +14 -0
  127. package/dist/session/verify-session-ritual.js +33 -0
  128. package/dist/story-ready/evaluate.d.ts +11 -0
  129. package/dist/story-ready/evaluate.js +41 -3
  130. package/dist/swarm/index.d.ts +2 -0
  131. package/dist/swarm/index.js +2 -0
  132. package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
  133. package/dist/swarm/pre-dispatch-cli.js +143 -0
  134. package/dist/swarm/pre-dispatch.d.ts +87 -0
  135. package/dist/swarm/pre-dispatch.js +373 -0
  136. package/dist/triage/actions/candidates-log.d.ts +1 -2
  137. package/dist/triage/actions/candidates-log.js +37 -6
  138. package/dist/vbrief-activate/activate.js +6 -0
  139. package/dist/vbrief-validate/constants.d.ts +2 -0
  140. package/dist/vbrief-validate/constants.js +2 -0
  141. package/dist/vbrief-validate/schema.js +4 -1
  142. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  143. package/dist/xbrief-migrate/migrate-project.js +92 -3
  144. package/package.json +19 -3
@@ -4,7 +4,9 @@ import { emitSessionEvalReadback } from "../eval/readback.js";
4
4
  import { bindSessionGeneration } from "../freshness/bind.js";
5
5
  import { readLiveGeneration } from "../freshness/generation.js";
6
6
  import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
7
+ import { hostContentSurfaceToDict, maybeFormatHostContentSurfaceLines, } from "../platform/host-content-surface.js";
7
8
  import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
9
+ import { ceremonyDialToDict, formatCeremonyDialStatusLine, mergeCeremonyDialDeferrals, resolveCeremonyDial, resolveSessionCeremonyDialInputs, } from "../policy/ceremony-dial.js";
8
10
  import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
9
11
  import { disclosureLine } from "../policy/disclosure.js";
10
12
  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";
@@ -19,6 +21,7 @@ import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
19
21
  import { emitSessionValueReadback } from "../value/readback.js";
20
22
  import { verifyRequiredTools } from "../verify-env/verify-tools.js";
21
23
  import { maybeFormatCoverageCheckResumeNudge } from "./coverage-check-resume-nudge.js";
24
+ import { effortBudgetToDict, maybeFormatEffortBudgetLines, resolveProductionHostEffortDescriptor, } from "./effort-budget.js";
22
25
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
23
26
  import { emitSessionStartProcessCost } from "./process-cost.js";
24
27
  import { probeSessionReleaseAvailability, } from "./release-availability.js";
@@ -33,7 +36,14 @@ export const READ_ONLY_RESULT_MESSAGE = "read-only session posture (alignment on
33
36
  export const SESSION_CEREMONY_TIERS = ["cold", "rearm"];
34
37
  export const COLD_CEREMONY_TIER = "cold";
35
38
  export const REARM_CEREMONY_TIER = "rearm";
36
- export const QUICK_STEPS = ["alignment", "branch_policy", "triage_welcome"];
39
+ // verify_tools is mutation readiness recorded on cold path (#3214 / #3156) —
40
+ // included so re-arm refuses after a tools-failed cold start.
41
+ export const QUICK_STEPS = [
42
+ "alignment",
43
+ "branch_policy",
44
+ "triage_welcome",
45
+ "verify_tools",
46
+ ];
37
47
  export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
38
48
  /** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
39
49
  export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
@@ -105,7 +115,13 @@ export function assessRearmEligibility(projectRoot, options = {}) {
105
115
  }
106
116
  }
107
117
  for (const stepName of QUICK_STEPS) {
108
- if (!stepPassesForRearm(state.quickSteps[stepName])) {
118
+ const step = state.quickSteps[stepName];
119
+ // Legacy ritual-state (pre-#3214 tools persistence) omits verify_tools —
120
+ // treat missing as pass; explicit failure still refuses re-arm.
121
+ if (stepName === "verify_tools" && (step === undefined || step === null)) {
122
+ continue;
123
+ }
124
+ if (!stepPassesForRearm(step)) {
109
125
  return {
110
126
  eligible: false,
111
127
  reason: `quick step '${stepName}' is missing or failed (full cold session:start required)`,
@@ -340,6 +356,66 @@ function resolveSessionScmReadiness(options, allowOptionalNetwork) {
340
356
  };
341
357
  }
342
358
  }
359
+ function resolveHostContentSurface(projectRoot, options, runtimeMode) {
360
+ try {
361
+ const seams = options.hostContentSurfaceSeams ?? {};
362
+ return maybeFormatHostContentSurfaceLines(projectRoot, {
363
+ ...seams,
364
+ environ: seams.environ ?? options.env,
365
+ runtimeMode: seams.runtimeMode ?? runtimeMode ?? null,
366
+ });
367
+ }
368
+ catch {
369
+ // best-effort — session start must not abort on host-surface probe failures (#3162)
370
+ return {
371
+ report: {
372
+ contentClass: "unknown",
373
+ classSource: "probe-error",
374
+ signals: [],
375
+ managedSection: {
376
+ state: "unknown",
377
+ embeddedSha: null,
378
+ bodyHash: null,
379
+ path: `${projectRoot}/AGENTS.md`,
380
+ },
381
+ runtimeMode: null,
382
+ },
383
+ lines: [],
384
+ };
385
+ }
386
+ }
387
+ function resolveEffortBudget(options) {
388
+ try {
389
+ const seams = options.effortBudgetSeams ?? {};
390
+ const environ = seams.environ ?? options.env ?? process.env;
391
+ // Production host adapter (#3266 / #1461): always resolve a descriptor from env
392
+ // (and CLI-forwarded seams) so host-native max-turns/budget signals are not lost.
393
+ const productionHost = resolveProductionHostEffortDescriptor(environ);
394
+ const hostDescriptor = seams.hostDescriptor !== undefined && seams.hostDescriptor !== null
395
+ ? { ...productionHost, ...seams.hostDescriptor }
396
+ : productionHost;
397
+ return maybeFormatEffortBudgetLines({
398
+ environ,
399
+ hostDescriptor,
400
+ });
401
+ }
402
+ catch {
403
+ // best-effort — session start must not abort on effort-budget probe failures (#3266)
404
+ return {
405
+ budget: {
406
+ detected: false,
407
+ posture: "unbounded",
408
+ kind: "none",
409
+ maxTurns: null,
410
+ maxBudget: null,
411
+ remainingTurns: null,
412
+ remainingBudget: null,
413
+ sources: [],
414
+ },
415
+ lines: [],
416
+ };
417
+ }
418
+ }
343
419
  function runReadOnlySessionStart(projectRoot, options, instant, environment) {
344
420
  const lines = [];
345
421
  const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
@@ -351,10 +427,16 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
351
427
  : safeDiagnostic;
352
428
  // #2275: report SCM availability even on read-only alignment (shallow; no network).
353
429
  const scm = resolveSessionScmReadiness(options, false);
430
+ // #3162: host content-surface class + managed drift (advisory).
431
+ const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
432
+ // #3266: hard effort-budget detection (advisory; bank-the-pass guidance).
433
+ const effortBudget = resolveEffortBudget(options);
354
434
  lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
355
435
  lines.push(userMdLine);
356
436
  lines.push(formatEnvironmentContext(environment));
357
437
  lines.push(...formatScmReadinessLines(scm));
438
+ lines.push(...hostSurface.lines);
439
+ lines.push(...effortBudget.lines);
358
440
  const resultPayload = {
359
441
  ready: true,
360
442
  exit_code: 0,
@@ -376,6 +458,8 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
376
458
  },
377
459
  environment: environmentContextToDict(environment),
378
460
  scm: scmReadinessToDict(scm),
461
+ host_content_surface: hostContentSurfaceToDict(hostSurface.report),
462
+ effort_budget: effortBudgetToDict(effortBudget.budget),
379
463
  message: READ_ONLY_RESULT_MESSAGE,
380
464
  };
381
465
  return { code: 0, payload: resultPayload, lines };
@@ -418,11 +502,17 @@ function runSessionRearm(projectRoot, options, instant, environment) {
418
502
  const alignmentMessage = `${READ_ONLY_ALIGNMENT_MESSAGE} ${userMdLine}`;
419
503
  // #2275: re-arm still reports SCM state (shallow; no network).
420
504
  const scm = resolveSessionScmReadiness(options, false);
505
+ // #3162: host content-surface class + managed drift (advisory).
506
+ const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
507
+ // #3266: hard effort-budget detection (advisory).
508
+ const effortBudget = resolveEffortBudget(options);
421
509
  const lines = [
422
510
  READ_ONLY_ALIGNMENT_MESSAGE,
423
511
  userMdLine,
424
512
  formatEnvironmentContext(environment),
425
513
  ...formatScmReadinessLines(scm),
514
+ ...hostSurface.lines,
515
+ ...effortBudget.lines,
426
516
  REARM_SKIPPED_FAT_PATH_MESSAGE,
427
517
  ];
428
518
  // Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
@@ -446,6 +536,41 @@ function runSessionRearm(projectRoot, options, instant, environment) {
446
536
  }
447
537
  const priorQuick = eligibility.state.quickSteps;
448
538
  const priorTriage = priorQuick.triage_welcome ?? ritualStep({ ok: true, ts: instant });
539
+ // Greptile P1: legacy ritual-state without verify_tools must re-run tools —
540
+ // never invent ok:true. When prior exists, preserve without re-run (#2992).
541
+ let toolsStep;
542
+ if (priorQuick.verify_tools && typeof priorQuick.verify_tools === "object") {
543
+ const priorTools = priorQuick.verify_tools;
544
+ toolsStep = {
545
+ ...priorTools,
546
+ ts: ritualStep({ ok: priorTools.ok === true, ts: instant }).ts,
547
+ message: typeof priorTools.message === "string"
548
+ ? priorTools.message
549
+ : "verify:tools preserved on re-arm",
550
+ };
551
+ }
552
+ else {
553
+ const verifyToolsFn = options.verifyTools ??
554
+ ((output) => {
555
+ const toolLines = [];
556
+ const result = verifyRequiredTools({ outputFn: (line) => toolLines.push(line) });
557
+ for (const line of toolLines) {
558
+ output(line);
559
+ }
560
+ return { exitCode: result.exitCode };
561
+ });
562
+ const toolsOutcome = verifyToolsFn((line) => lines.push(line));
563
+ const toolsOk = toolsOutcome.exitCode === 0;
564
+ toolsStep = ritualStep({
565
+ ok: toolsOk,
566
+ ts: instant,
567
+ message: toolsOk
568
+ ? "verify:tools re-run on re-arm (legacy ritual lacked tools step)"
569
+ : `verify:tools failed on re-arm (exit ${toolsOutcome.exitCode})`,
570
+ exitCode: toolsOutcome.exitCode,
571
+ durationMs: 0,
572
+ });
573
+ }
449
574
  const policyOk = policyResult.error === null || policyResult.source === "default-fail-closed";
450
575
  const quickSteps = {
451
576
  alignment: ritualStep({
@@ -461,6 +586,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
461
586
  exitCode: policyOk ? 0 : 2,
462
587
  durationMs: 0,
463
588
  }),
589
+ verify_tools: toolsStep,
464
590
  // Preserve prior triage outcome; do not re-run welcome / self-heal on re-arm.
465
591
  triage_welcome: {
466
592
  ...priorTriage,
@@ -578,6 +704,8 @@ function runSessionRearm(projectRoot, options, instant, environment) {
578
704
  },
579
705
  environment: environmentContextToDict(environment),
580
706
  scm: scmReadinessToDict(scm),
707
+ host_content_surface: hostContentSurfaceToDict(hostSurface.report),
708
+ effort_budget: effortBudgetToDict(effortBudget.budget),
581
709
  message: code === 0 ? "session ritual re-armed" : "session ritual re-arm failed",
582
710
  },
583
711
  lines,
@@ -659,12 +787,27 @@ export function runSessionStart(projectRoot, options = {}) {
659
787
  const overallStarted = performance.now();
660
788
  const stepTimings = [];
661
789
  const allowOptionalNetwork = resolveSessionStartOptionalNetwork(options);
790
+ // #3214 / #3156: select ritual (ceremony) depth before building deferral maps.
791
+ // Rapid/minimal auto-defer informational cold steps only; mutation readiness
792
+ // (doctor, cache_fresh, agent_hooks, verify_tools) stays constant.
793
+ // Two-stage + provisional intake (#3214 / #3263 / #1581 ordering): fill
794
+ // missing size/tier/shape from env/verb/files/deposit BEFORE resolve — never
795
+ // block on plan-item effort (post-planning only). Cold incomplete size is
796
+ // tier-conditional (#3263): mid/low → standard; frontier/unknown → rapid.
797
+ const { inputs: resolvedDialInputs, provisional: provisionalDial } = resolveSessionCeremonyDialInputs(projectRoot, options.ceremonyDialInputs, {
798
+ ...options.ceremonyDialHints,
799
+ env: options.env,
800
+ });
801
+ const ceremonyDialSelection = options.ceremonyDial ?? resolveCeremonyDial(projectRoot, { inputs: resolvedDialInputs });
802
+ const effectiveDeferrals = mergeCeremonyDialDeferrals(deferrals, ceremonyDialSelection);
803
+ const skipFatPath = ceremonyDialSelection.profile.skipFatPath;
662
804
  const { head: gitHeadValue, error: gitError } = gitHead(projectRoot, runGit);
663
805
  if (gitHeadValue === null) {
664
806
  const payload = {
665
807
  ready: false,
666
808
  exit_code: 2,
667
809
  ceremony_tier: COLD_CEREMONY_TIER,
810
+ ceremony_dial: ceremonyDialToDict(ceremonyDialSelection),
668
811
  environment: environmentContextToDict(environment),
669
812
  message: gitError ?? "could not resolve git HEAD",
670
813
  };
@@ -682,9 +825,13 @@ export function runSessionStart(projectRoot, options = {}) {
682
825
  lines: [formatEnvironmentContext(environment), payload.message],
683
826
  };
684
827
  }
685
- const quickSteps = recordDeferredSteps(QUICK_STEPS, deferrals, instant);
686
- const gatedSteps = recordDeferredSteps(GATED_STEPS, deferrals, instant);
828
+ const quickSteps = recordDeferredSteps(QUICK_STEPS, effectiveDeferrals, instant);
829
+ const gatedSteps = recordDeferredSteps(GATED_STEPS, effectiveDeferrals, instant);
687
830
  const lines = [];
831
+ lines.push(formatCeremonyDialStatusLine(ceremonyDialSelection));
832
+ if (provisionalDial.reasons.length > 0 && options.ceremonyDial === undefined) {
833
+ lines.push(`[deft ceremony-dial] provisional: ${provisionalDial.reasons.join("; ")}`);
834
+ }
688
835
  // Resolve USER.md via the shared first-hit-wins resolver so the alignment
689
836
  // step finds preferences automatically in mismatched / headless sandboxes
690
837
  // with zero manual DEFT_USER_PATH (#2271 / #2124). Never throws: an absent
@@ -724,6 +871,22 @@ export function runSessionStart(projectRoot, options = {}) {
724
871
  const scm = resolveSessionScmReadiness(options, allowOptionalNetwork);
725
872
  lines.push(...formatScmReadinessLines(scm));
726
873
  stepTimings.push({ name: "scm_readiness", duration_ms: elapsedMs(scmStepStarted) });
874
+ // #3162: host content-surface class + managed AGENTS drift (advisory; never blocks).
875
+ const hostSurfaceStepStarted = performance.now();
876
+ const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
877
+ lines.push(...hostSurface.lines);
878
+ stepTimings.push({
879
+ name: "host_content_surface",
880
+ duration_ms: elapsedMs(hostSurfaceStepStarted),
881
+ });
882
+ // #3266: hard effort-budget detection (advisory; bank-the-pass guidance).
883
+ const effortBudgetStepStarted = performance.now();
884
+ const effortBudget = resolveEffortBudget(options);
885
+ lines.push(...effortBudget.lines);
886
+ stepTimings.push({
887
+ name: "effort_budget",
888
+ duration_ms: elapsedMs(effortBudgetStepStarted),
889
+ });
727
890
  if (!quickSteps.branch_policy) {
728
891
  const stepStarted = performance.now();
729
892
  const result = resolvePolicy(projectRoot);
@@ -753,6 +916,11 @@ export function runSessionStart(projectRoot, options = {}) {
753
916
  else {
754
917
  stepTimings.push({ name: "branch_policy", duration_ms: 0, skipped: true });
755
918
  }
919
+ // #3214 / #3156: verify_tools is mutation readiness — always run, even under
920
+ // rapid/minimal. Dial skipFatPath only lightens *ceremony* (triage welcome,
921
+ // optional network, staleness tickler), never readiness gates.
922
+ // Persist outcome into quick_steps so ritual-state records failure (not only
923
+ // process exit) — re-arm / later readers must not see a green cold start.
756
924
  {
757
925
  const stepStarted = performance.now();
758
926
  const verifyToolsFn = options.verifyTools ??
@@ -764,10 +932,28 @@ export function runSessionStart(projectRoot, options = {}) {
764
932
  }
765
933
  return { exitCode: result.exitCode };
766
934
  });
767
- verifyToolsFn((line) => lines.push(line));
768
- stepTimings.push({ name: "verify_tools", duration_ms: elapsedMs(stepStarted) });
935
+ const toolsOutcome = verifyToolsFn((line) => lines.push(line));
936
+ const durationMs = elapsedMs(stepStarted);
937
+ const toolsOk = toolsOutcome.exitCode === 0;
938
+ const toolsMessage = toolsOk
939
+ ? "verify:tools ok"
940
+ : `verify:tools failed (exit ${toolsOutcome.exitCode}); session not ready.`;
941
+ if (!toolsOk) {
942
+ lines.push(`[deft session] ${toolsMessage}`);
943
+ }
944
+ // Record on quick_steps (durable ritual-state) in addition to step timings.
945
+ quickSteps.verify_tools = ritualStep({
946
+ ok: toolsOk,
947
+ ts: instant,
948
+ message: toolsMessage,
949
+ exitCode: toolsOutcome.exitCode,
950
+ command: ["verify:tools"],
951
+ durationMs,
952
+ });
953
+ stepTimings.push({ name: "verify_tools", duration_ms: durationMs });
769
954
  }
770
- if (!quickSteps.triage_welcome) {
955
+ // #3214: rapid/minimal skip informational cold-path ceremony only.
956
+ if (!quickSteps.triage_welcome && !skipFatPath) {
771
957
  const stepStarted = performance.now();
772
958
  const captured = [];
773
959
  const triageCommand = ["triage_welcome.run_default_mode", "--project-root", projectRoot];
@@ -825,7 +1011,8 @@ export function runSessionStart(projectRoot, options = {}) {
825
1011
  stepTimings.push({ name: "triage_welcome", duration_ms: 0, skipped: true });
826
1012
  }
827
1013
  // #2991: npm release probe is optional network — off by default so ritual write is not blocked.
828
- if (allowOptionalNetwork) {
1014
+ // #3214: also skipped under rapid/minimal dial (lifecycleWrites light/minimal).
1015
+ if (allowOptionalNetwork && !skipFatPath) {
829
1016
  const stepStarted = performance.now();
830
1017
  try {
831
1018
  const releaseAvailability = (options.probeReleaseAvailability ?? probeSessionReleaseAvailability)(projectRoot, { now: instant });
@@ -838,16 +1025,20 @@ export function runSessionStart(projectRoot, options = {}) {
838
1025
  }
839
1026
  else {
840
1027
  stepTimings.push({ name: "release_probe", duration_ms: 0, skipped: true });
841
- lines.push(OPTIONAL_NETWORK_SKIPPED_MESSAGE);
842
- }
843
- try {
844
- const tickler = (options.runStalenessTickler ?? maybeRunStalenessTickler)(projectRoot, {
845
- now: instant,
846
- });
847
- lines.push(...tickler.lines);
1028
+ if (!skipFatPath) {
1029
+ lines.push(OPTIONAL_NETWORK_SKIPPED_MESSAGE);
1030
+ }
848
1031
  }
849
- catch {
850
- // Staleness tickler is best-effort and must never block session start.
1032
+ if (!skipFatPath) {
1033
+ try {
1034
+ const tickler = (options.runStalenessTickler ?? maybeRunStalenessTickler)(projectRoot, {
1035
+ now: instant,
1036
+ });
1037
+ lines.push(...tickler.lines);
1038
+ }
1039
+ catch {
1040
+ // Staleness tickler is best-effort and must never block session start.
1041
+ }
851
1042
  }
852
1043
  if (!runningInsideDeftRepo(projectRoot) && shouldEmitMigrateNudge(projectRoot)) {
853
1044
  lines.push(MIGRATE_COMPLETION_NUDGE);
@@ -886,14 +1077,27 @@ export function runSessionStart(projectRoot, options = {}) {
886
1077
  }
887
1078
  const writeStarted = performance.now();
888
1079
  const coldSessionId = (options.newSessionId ?? randomUUID)();
889
- const payload = newRitualStatePayload({
890
- sessionId: coldSessionId,
891
- gitHead: gitHeadValue,
892
- worktreePath: worktreePath(projectRoot, runGit),
893
- startedAt: instant,
894
- quickSteps,
895
- gatedSteps,
896
- });
1080
+ const dialDict = {
1081
+ ...ceremonyDialToDict(ceremonyDialSelection),
1082
+ provisional: {
1083
+ taskSize: provisionalDial.taskSize,
1084
+ modelTier: provisionalDial.modelTier,
1085
+ projectShape: provisionalDial.projectShape,
1086
+ reasons: [...provisionalDial.reasons],
1087
+ },
1088
+ };
1089
+ const payload = {
1090
+ ...newRitualStatePayload({
1091
+ sessionId: coldSessionId,
1092
+ gitHead: gitHeadValue,
1093
+ worktreePath: worktreePath(projectRoot, runGit),
1094
+ startedAt: instant,
1095
+ quickSteps,
1096
+ gatedSteps,
1097
+ }),
1098
+ // #3214: record dial choice on ritual-state for audit / later re-arm context.
1099
+ ceremony_dial: dialDict,
1100
+ };
897
1101
  let statePath;
898
1102
  try {
899
1103
  statePath = writeRitualState(projectRoot, payload);
@@ -915,6 +1119,7 @@ export function runSessionStart(projectRoot, options = {}) {
915
1119
  const failed = Object.entries(quickSteps)
916
1120
  .filter(([, step]) => !step.ok && !step.deferred_reason)
917
1121
  .map(([name]) => name);
1122
+ // verify_tools is recorded on quick_steps; failure makes ready=false.
918
1123
  const code = failed.length > 0 ? 1 : 0;
919
1124
  const totalMs = elapsedMs(overallStarted);
920
1125
  // #3117: bind live deposit generation when payload surfaces load (cold path).
@@ -947,13 +1152,14 @@ export function runSessionStart(projectRoot, options = {}) {
947
1152
  ready: code === 0,
948
1153
  exit_code: code,
949
1154
  ceremony_tier: COLD_CEREMONY_TIER,
1155
+ ceremony_dial: dialDict,
950
1156
  state_path: statePath,
951
1157
  ...(freshnessBind ? { freshness: freshnessBind } : {}),
952
1158
  quick_steps: quickSteps,
953
1159
  gated_steps: gatedSteps,
954
1160
  steps: stepTimings,
955
1161
  duration_ms: totalMs,
956
- optional_network: allowOptionalNetwork,
1162
+ optional_network: allowOptionalNetwork && !skipFatPath,
957
1163
  user_md: {
958
1164
  path: userMd.path,
959
1165
  rung: userMd.rung,
@@ -962,6 +1168,8 @@ export function runSessionStart(projectRoot, options = {}) {
962
1168
  },
963
1169
  environment: environmentContextToDict(environment),
964
1170
  scm: scmReadinessToDict(scm),
1171
+ host_content_surface: hostContentSurfaceToDict(hostSurface.report),
1172
+ effort_budget: effortBudgetToDict(effortBudget.budget),
965
1173
  message: code === 0 ? "session ritual recorded" : "session ritual failed",
966
1174
  };
967
1175
  // #2994: local process-cost event (best-effort; never blocks ceremony).
@@ -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
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
3
4
  import { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND, VALID_DISPATCH_KINDS, } from "./allocation.js";
4
5
  export const ACTIVE_FOLDER = "active";
5
6
  export const ELIGIBLE_STATUS = "running";
@@ -66,7 +67,7 @@ function checkVbrief(vbriefPath) {
66
67
  "is eligible for a story start",
67
68
  };
68
69
  }
69
- return { ok: true };
70
+ return { ok: true, path, payload: payload };
70
71
  }
71
72
  function readyMessage(treeNote, suffix) {
72
73
  return `OK: ready to start -- ${treeNote}, vBRIEF active+running, ${suffix}`;
@@ -116,6 +117,9 @@ function classifyAllocation(fields, treeNote) {
116
117
  /**
117
118
  * Pure evaluator — returns exit code + human message. Faithful to
118
119
  * `scripts/preflight_story_start.evaluate`.
120
+ *
121
+ * #3241: after structural active+running checks, re-check parent requirement
122
+ * lineage (coverage map + behavioral deltas) when the parent authors IDs.
119
123
  */
120
124
  export function evaluate(vbriefPath, options = {}) {
121
125
  const gitStatus = options.gitStatus ?? null;
@@ -147,15 +151,49 @@ export function evaluate(vbriefPath, options = {}) {
147
151
  message: `not ready: ${vbriefCheck.reason}.`,
148
152
  };
149
153
  }
154
+ // #3241 parent lineage (fail closed when parent authors IDs and child lacks coverage).
155
+ const lineage = evaluateParentLineage({
156
+ child: vbriefCheck.payload,
157
+ childPath: vbriefCheck.path,
158
+ projectRoot: options.projectRoot,
159
+ skip: options.skipParentLineage === true,
160
+ });
161
+ if (!lineage.ok) {
162
+ return {
163
+ exitCode: 1,
164
+ dispatchKind: null,
165
+ parentLineage: lineage,
166
+ message: `not ready: ${lineage.message}` +
167
+ (lineage.defect_class !== null ? ` [defect_class=${lineage.defect_class}]` : "") +
168
+ `\n${formatParentLineageLine(lineage)}`,
169
+ };
170
+ }
171
+ const lineageNote = lineage.applicable
172
+ ? `parent lineage OK (${lineage.parent_requirement_ids.length} req IDs` +
173
+ (lineage.negative_invariant_ids.length > 0
174
+ ? `, ${lineage.negative_invariant_ids.length} negative invariants`
175
+ : "") +
176
+ ")"
177
+ : null;
150
178
  const [found, fields] = options.parsed ?? parseAllocationSection(options.allocationContext ?? null);
151
179
  if (!found) {
180
+ const base = readyMessage(treeNote, "no `## Allocation context` section (solo path, #1371 carve-out).");
152
181
  return {
153
182
  exitCode: 0,
154
183
  dispatchKind: null,
155
- message: readyMessage(treeNote, "no `## Allocation context` section (solo path, #1371 carve-out)."),
184
+ parentLineage: lineage,
185
+ message: lineageNote !== null ? `${base.replace(/\.$/, "")}; ${lineageNote}.` : base,
156
186
  };
157
187
  }
158
- return classifyAllocation(fields, treeNote);
188
+ const classified = classifyAllocation(fields, treeNote);
189
+ return {
190
+ ...classified,
191
+ parentLineage: lineage,
192
+ message: classified.exitCode === 0 && lineageNote !== null
193
+ ? `${classified.message.replace(/\.$/, "")}; ${lineageNote}.`
194
+ : classified.message,
195
+ };
159
196
  }
197
+ export { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
160
198
  export { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND };
161
199
  //# sourceMappingURL=evaluate.js.map
@@ -5,6 +5,8 @@ export * from "./finalize-cohort.js";
5
5
  export { finalizeCohortMain, parseFinalizeCohortArgv } from "./finalize-cohort-cli.js";
6
6
  export * from "./launch.js";
7
7
  export { launchMain, parseLaunchArgv } from "./launch-cli.js";
8
+ export * from "./pre-dispatch.js";
9
+ export { parsePreDispatchArgv, preDispatchMain } from "./pre-dispatch-cli.js";
8
10
  export * from "./readiness.js";
9
11
  export { readinessMain } from "./readiness-cli.js";
10
12
  export * from "./routing.js";
@@ -5,6 +5,8 @@ export * from "./finalize-cohort.js";
5
5
  export { finalizeCohortMain, parseFinalizeCohortArgv } from "./finalize-cohort-cli.js";
6
6
  export * from "./launch.js";
7
7
  export { launchMain, parseLaunchArgv } from "./launch-cli.js";
8
+ export * from "./pre-dispatch.js";
9
+ export { parsePreDispatchArgv, preDispatchMain } from "./pre-dispatch-cli.js";
8
10
  export * from "./readiness.js";
9
11
  export { readinessMain } from "./readiness-cli.js";
10
12
  export * from "./routing.js";
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { type CompleteStatus, type PreDispatchAction } from "./pre-dispatch.js";
3
+ export interface ParsedPreDispatchArgv {
4
+ projectRoot: string;
5
+ scopeId: string | null;
6
+ targetId: string | null;
7
+ workflowId: string | null;
8
+ action: PreDispatchAction | null;
9
+ sourceRevision: string | null;
10
+ attemptId: string | null;
11
+ status: CompleteStatus | null;
12
+ workerId: string | null;
13
+ externalRunId: string | null;
14
+ json: boolean;
15
+ help: boolean;
16
+ }
17
+ export declare function parsePreDispatchArgv(argv: string[]): ParsedPreDispatchArgv;
18
+ export declare function preDispatchMain(argv?: string[]): number;
19
+ //# sourceMappingURL=pre-dispatch-cli.d.ts.map