@deftai/directive-core 0.104.0 → 0.106.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 (287) hide show
  1. package/dist/authz/classify.js +1108 -67
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +34 -4
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/forward-coverage/diff-coverage.d.ts +39 -0
  17. package/dist/forward-coverage/diff-coverage.js +112 -0
  18. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  19. package/dist/forward-coverage/diff-lines.js +173 -0
  20. package/dist/forward-coverage/evaluate.d.ts +24 -9
  21. package/dist/forward-coverage/evaluate.js +125 -19
  22. package/dist/fs/contained-write.d.ts +115 -1
  23. package/dist/fs/contained-write.js +241 -5
  24. package/dist/fs/mutation-ledger.d.ts +54 -0
  25. package/dist/fs/mutation-ledger.js +133 -0
  26. package/dist/fs/non-product-dirs.d.ts +79 -0
  27. package/dist/fs/non-product-dirs.js +96 -0
  28. package/dist/hooks/dispatcher.d.ts +3 -1
  29. package/dist/hooks/dispatcher.js +14 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +2 -0
  32. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  33. package/dist/init-deposit/agent-hooks.js +18 -16
  34. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  35. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  36. package/dist/init-deposit/gitignore.js +9 -0
  37. package/dist/init-deposit/hygiene.d.ts +47 -11
  38. package/dist/init-deposit/hygiene.js +172 -37
  39. package/dist/init-deposit/legacy-detect.js +6 -2
  40. package/dist/init-deposit/refresh.d.ts +21 -1
  41. package/dist/init-deposit/refresh.js +106 -9
  42. package/dist/init-deposit/scaffold.js +28 -279
  43. package/dist/init-deposit/slash-deposit.js +13 -7
  44. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  45. package/dist/init-deposit/xbrief-projections.js +24 -11
  46. package/dist/intake/clause-derivation.d.ts +47 -1
  47. package/dist/intake/clause-derivation.js +238 -9
  48. package/dist/intake/issue-ingest.d.ts +3 -1
  49. package/dist/intake/issue-ingest.js +35 -6
  50. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  51. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  52. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  53. package/dist/lifecycle-visible/evaluate.js +710 -0
  54. package/dist/lifecycle-visible/index.d.ts +2 -0
  55. package/dist/lifecycle-visible/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +10 -0
  57. package/dist/literal-acceptance/capture.js +226 -77
  58. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  59. package/dist/literal-acceptance/evaluate.js +70 -2
  60. package/dist/literal-acceptance/index.d.ts +3 -3
  61. package/dist/literal-acceptance/index.js +3 -3
  62. package/dist/literal-acceptance/safety.d.ts +41 -0
  63. package/dist/literal-acceptance/safety.js +126 -2
  64. package/dist/literal-acceptance/types.d.ts +8 -0
  65. package/dist/orchestration/judgment-policy.d.ts +13 -0
  66. package/dist/orchestration/judgment-policy.js +15 -0
  67. package/dist/orchestration/probe-session.js +13 -8
  68. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  69. package/dist/orchestration/verify-judgment-gates.js +43 -11
  70. package/dist/orphan-active/evaluate.d.ts +7 -1
  71. package/dist/orphan-active/evaluate.js +102 -23
  72. package/dist/platform/platform-capabilities.js +2 -3
  73. package/dist/policy/base-branch.d.ts +37 -0
  74. package/dist/policy/base-branch.js +114 -0
  75. package/dist/policy/branch-sync.d.ts +80 -0
  76. package/dist/policy/branch-sync.js +211 -0
  77. package/dist/policy/ceremony-dial-escalation.js +1 -0
  78. package/dist/policy/ceremony-dial.js +11 -21
  79. package/dist/policy/delivery-branch.d.ts +5 -3
  80. package/dist/policy/delivery-branch.js +7 -3
  81. package/dist/policy/file-size-thresholds.d.ts +24 -0
  82. package/dist/policy/file-size-thresholds.js +23 -0
  83. package/dist/policy/forge-outage-retry.d.ts +47 -0
  84. package/dist/policy/forge-outage-retry.js +157 -0
  85. package/dist/policy/host-hooks.d.ts +20 -0
  86. package/dist/policy/host-hooks.js +121 -2
  87. package/dist/policy/index.d.ts +7 -0
  88. package/dist/policy/index.js +66 -7
  89. package/dist/policy/org-force-on-migration.js +2 -1
  90. package/dist/policy/plan-extensions.d.ts +1 -1
  91. package/dist/policy/plan-extensions.js +2 -0
  92. package/dist/policy/product-signal.js +6 -7
  93. package/dist/policy/project-invariants.d.ts +50 -0
  94. package/dist/policy/project-invariants.js +205 -0
  95. package/dist/policy/require-human-merge.js +8 -6
  96. package/dist/policy/resolve.d.ts +7 -2
  97. package/dist/policy/resolve.js +25 -8
  98. package/dist/policy/sync-default.d.ts +129 -0
  99. package/dist/policy/sync-default.js +452 -0
  100. package/dist/policy/sync-max-files.d.ts +93 -0
  101. package/dist/policy/sync-max-files.js +192 -0
  102. package/dist/policy/value-feedback.js +7 -8
  103. package/dist/pr-monitor/absent-required.d.ts +11 -0
  104. package/dist/pr-monitor/absent-required.js +61 -0
  105. package/dist/pr-monitor/constants.d.ts +7 -0
  106. package/dist/pr-monitor/constants.js +7 -0
  107. package/dist/pr-monitor/index.d.ts +2 -1
  108. package/dist/pr-monitor/index.js +2 -1
  109. package/dist/pr-monitor/main.js +4 -0
  110. package/dist/pr-monitor/monitor.js +24 -1
  111. package/dist/pr-wait-mergeable/classify.js +6 -0
  112. package/dist/preflight/evaluate.d.ts +4 -0
  113. package/dist/preflight/evaluate.js +35 -1
  114. package/dist/preflight/index.d.ts +4 -0
  115. package/dist/preflight/index.js +2 -0
  116. package/dist/preflight/intended-placement.d.ts +62 -0
  117. package/dist/preflight/intended-placement.js +275 -0
  118. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  119. package/dist/preflight/project-invariants-gate.js +82 -0
  120. package/dist/preflight-cache/evaluate.d.ts +12 -0
  121. package/dist/preflight-cache/evaluate.js +15 -1
  122. package/dist/preflight-cache/index.d.ts +1 -1
  123. package/dist/preflight-cache/index.js +1 -1
  124. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  125. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  126. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  127. package/dist/product-first-done-gate/acceptance.js +79 -8
  128. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  129. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  130. package/dist/product-first-done-gate/evaluate.d.ts +16 -0
  131. package/dist/product-first-done-gate/evaluate.js +298 -34
  132. package/dist/product-first-done-gate/index.d.ts +2 -0
  133. package/dist/product-first-done-gate/index.js +1 -0
  134. package/dist/release/build-dist.d.ts +101 -0
  135. package/dist/release/build-dist.js +428 -46
  136. package/dist/release/closed-verb-gate.d.ts +12 -0
  137. package/dist/release/closed-verb-gate.js +51 -0
  138. package/dist/release/index.d.ts +1 -0
  139. package/dist/release/index.js +1 -0
  140. package/dist/release/pipeline.js +7 -0
  141. package/dist/release/types.d.ts +12 -0
  142. package/dist/render/constants.d.ts +2 -1
  143. package/dist/render/constants.js +2 -1
  144. package/dist/render/framework-commands.js +4 -0
  145. package/dist/render/spec-validate.js +23 -12
  146. package/dist/run-summary/emit.d.ts +16 -10
  147. package/dist/run-summary/emit.js +26 -15
  148. package/dist/run-summary/index.d.ts +3 -2
  149. package/dist/run-summary/index.js +2 -1
  150. package/dist/run-summary/session-id.d.ts +21 -0
  151. package/dist/run-summary/session-id.js +92 -0
  152. package/dist/run-summary/types.d.ts +33 -4
  153. package/dist/run-summary/types.js +1 -0
  154. package/dist/scope/acceptance-evidence.d.ts +19 -7
  155. package/dist/scope/acceptance-evidence.js +38 -11
  156. package/dist/scope/delivery-evidence.d.ts +3 -3
  157. package/dist/scope/delivery-evidence.js +22 -27
  158. package/dist/scope/index.d.ts +1 -0
  159. package/dist/scope/index.js +1 -0
  160. package/dist/scope/main.js +15 -5
  161. package/dist/scope/parent-lineage.js +7 -6
  162. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  163. package/dist/scope/project-invariant-coverage.js +322 -0
  164. package/dist/scope/promote-from-issue.js +5 -5
  165. package/dist/scope/transition.js +1 -1
  166. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  167. package/dist/scope-provenance/compare-intent.js +233 -0
  168. package/dist/scope-provenance/digest.d.ts +18 -1
  169. package/dist/scope-provenance/digest.js +16 -11
  170. package/dist/scope-provenance/evaluate.d.ts +5 -1
  171. package/dist/scope-provenance/evaluate.js +67 -7
  172. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  173. package/dist/scope-provenance/extract-intent.js +262 -0
  174. package/dist/scope-provenance/index.d.ts +7 -1
  175. package/dist/scope-provenance/index.js +7 -1
  176. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  177. package/dist/scope-provenance/intent-digest.js +33 -0
  178. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  179. package/dist/scope-provenance/intent-evaluate.js +149 -0
  180. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  181. package/dist/scope-provenance/json-tokenizer.js +225 -0
  182. package/dist/scope-provenance/known-machine.d.ts +38 -0
  183. package/dist/scope-provenance/known-machine.js +157 -0
  184. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  185. package/dist/scope-provenance/mint-artifacts.js +425 -0
  186. package/dist/session/ac-pass-banking.d.ts +9 -36
  187. package/dist/session/ac-pass-banking.js +33 -79
  188. package/dist/session/ac-pass-reuse.d.ts +53 -0
  189. package/dist/session/ac-pass-reuse.js +95 -0
  190. package/dist/session/git.js +19 -4
  191. package/dist/session/index.d.ts +4 -0
  192. package/dist/session/index.js +4 -0
  193. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  194. package/dist/session/occupancy-stress-worker.js +36 -0
  195. package/dist/session/occupancy.d.ts +97 -0
  196. package/dist/session/occupancy.js +422 -0
  197. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  198. package/dist/session/process-cost.d.ts +32 -10
  199. package/dist/session/process-cost.js +189 -1
  200. package/dist/session/product-state-hash.d.ts +26 -0
  201. package/dist/session/product-state-hash.js +700 -0
  202. package/dist/session/ritual-entrypoint.js +16 -1
  203. package/dist/session/session-ready.d.ts +2 -0
  204. package/dist/session/session-ready.js +52 -30
  205. package/dist/session/session-start.d.ts +13 -0
  206. package/dist/session/session-start.js +103 -9
  207. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  208. package/dist/session/verify-ac-session-cache.js +126 -0
  209. package/dist/session/verify-session-ritual.d.ts +28 -0
  210. package/dist/session/verify-session-ritual.js +97 -5
  211. package/dist/slash/openclaw-deposit.js +13 -9
  212. package/dist/slice/constants.d.ts +2 -2
  213. package/dist/slice/constants.js +2 -2
  214. package/dist/slice/existing.js +9 -3
  215. package/dist/slice/lock.d.ts +16 -1
  216. package/dist/slice/lock.js +137 -11
  217. package/dist/story-ready/evaluate.d.ts +2 -0
  218. package/dist/story-ready/evaluate.js +13 -0
  219. package/dist/swarm/complete-cohort.d.ts +3 -0
  220. package/dist/swarm/complete-cohort.js +59 -11
  221. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  222. package/dist/swarm/finalize-cohort-cli.js +165 -35
  223. package/dist/swarm/finalize-cohort.d.ts +1 -0
  224. package/dist/swarm/finalize-cohort.js +47 -4
  225. package/dist/swarm/launch.d.ts +22 -0
  226. package/dist/swarm/launch.js +155 -0
  227. package/dist/swarm/routing-set-cli.d.ts +11 -1
  228. package/dist/swarm/routing-set-cli.js +15 -3
  229. package/dist/swarm/routing-verify.d.ts +13 -1
  230. package/dist/swarm/routing-verify.js +60 -21
  231. package/dist/swarm/routing.d.ts +14 -0
  232. package/dist/swarm/routing.js +53 -3
  233. package/dist/telemetry-coverage/fake-trial.js +10 -0
  234. package/dist/telemetry-coverage/kinds.js +2 -0
  235. package/dist/triage/actions/index.js +4 -1
  236. package/dist/triage/bootstrap/index.js +6 -6
  237. package/dist/triage/help/registry-data.d.ts +1 -1
  238. package/dist/triage/help/registry-data.js +1 -1
  239. package/dist/triage/welcome/writers.d.ts +1 -1
  240. package/dist/triage/welcome/writers.js +10 -5
  241. package/dist/ts-check-lane/index.d.ts +3 -0
  242. package/dist/ts-check-lane/index.js +2 -0
  243. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  244. package/dist/ts-check-lane/progress-reporter.js +38 -0
  245. package/dist/ts-check-lane/progress.d.ts +32 -0
  246. package/dist/ts-check-lane/progress.js +57 -0
  247. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  248. package/dist/ts-check-lane/run-lane.js +8 -6
  249. package/dist/validate-content/validate-links.d.ts +8 -0
  250. package/dist/validate-content/validate-links.js +9 -15
  251. package/dist/value/feedback-file.js +12 -2
  252. package/dist/value/readback.d.ts +4 -1
  253. package/dist/value/readback.js +51 -12
  254. package/dist/vbrief-build/index.d.ts +1 -0
  255. package/dist/vbrief-build/index.js +1 -0
  256. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  257. package/dist/vbrief-build/project-invariants-io.js +41 -0
  258. package/dist/vbrief-reconcile/graph.js +18 -1
  259. package/dist/vbrief-reconcile/index.d.ts +1 -1
  260. package/dist/vbrief-reconcile/index.js +1 -1
  261. package/dist/vbrief-reconcile/types.d.ts +13 -0
  262. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  263. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  264. package/dist/vbrief-validate/index.d.ts +1 -1
  265. package/dist/vbrief-validate/index.js +1 -1
  266. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  267. package/dist/vbrief-validate/plan-hooks.js +68 -0
  268. package/dist/vbrief-validate/schema.d.ts +4 -0
  269. package/dist/vbrief-validate/schema.js +1 -1
  270. package/dist/verify-ac/evaluate.d.ts +7 -1
  271. package/dist/verify-ac/evaluate.js +34 -26
  272. package/dist/verify-ac/flag.d.ts +4 -0
  273. package/dist/verify-ac/flag.js +16 -0
  274. package/dist/verify-ac/index.d.ts +2 -2
  275. package/dist/verify-ac/index.js +2 -2
  276. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  277. package/dist/verify-env/agent-hook-readiness.js +12 -8
  278. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  279. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  280. package/dist/verify-env/agent-hooks.js +3 -4
  281. package/dist/verify-source/contained-writes.js +2 -0
  282. package/dist/verify-source/verify-stubs.d.ts +15 -1
  283. package/dist/verify-source/verify-stubs.js +20 -16
  284. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  285. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  286. package/dist/xbrief-migrate/migrate-project.js +2 -5
  287. package/package.json +7 -3
@@ -1,5 +1,8 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
2
3
  import { readCorePackageVersion } from "../engine-version.js";
4
+ import { hasArtifactSuffix } from "../layout/resolve.js";
5
+ import { readPlanSequence } from "../plan-sequence/store.js";
3
6
  import { formatFrameworkCommand } from "../render/framework-commands.js";
4
7
  import { checkActiveCliAgainstTarget, } from "./active-cli.js";
5
8
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
@@ -17,6 +20,64 @@ export const GATED_ENTRYPOINT_COMMANDS = {
17
20
  doctor: ["doctor"],
18
21
  cache_fresh: ["verify:cache-fresh"],
19
22
  };
23
+ /** Explicit skip flag threaded through the gated `cache_fresh` argv (#3507). */
24
+ export const SKIP_DRIFT_PROBE_FLAG = "--skip-drift-probe";
25
+ /** Explicit work-selection flag threaded through the gated `cache_fresh` argv (#3507). */
26
+ export const WORK_SELECTION_FLAG = "--work-selection";
27
+ /** Distinct ritual-state field — never `deferred_reason` (#3507). */
28
+ export const DRIFT_PROBE_SKIPPED_NO_WORK_SELECTION = "skipped-no-work-selection";
29
+ function hasActiveStoryArtifact(projectRoot) {
30
+ for (const dirName of ["xbrief", "vbrief"]) {
31
+ const activeDir = join(projectRoot, dirName, "active");
32
+ try {
33
+ for (const entry of readdirSync(activeDir, { withFileTypes: true })) {
34
+ if (entry.isFile() && hasArtifactSuffix(entry.name))
35
+ return true;
36
+ }
37
+ }
38
+ catch {
39
+ /* missing or unreadable active folder is not work selection */
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ function hasUnexhaustedPlanSequence(projectRoot) {
45
+ try {
46
+ const sequence = readPlanSequence(projectRoot);
47
+ if (sequence === null || sequence.exhausted === true)
48
+ return null;
49
+ return sequence.sequence_kind === "triage" ? "triage-queue" : "ordered-plan";
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ /**
56
+ * Default work-selection detector for the gated `cache_fresh` seam (#3507).
57
+ * Active story xBRIEF (scope:activate residue), unexhausted ordered-plan /
58
+ * triage sequence. `--for-issue` is a positive evaluate override, not this
59
+ * detector — the gated argv never carries it.
60
+ */
61
+ export function detectWorkSelection(projectRoot) {
62
+ if (hasActiveStoryArtifact(projectRoot)) {
63
+ return { inPlay: true, kind: "active-story" };
64
+ }
65
+ const planKind = hasUnexhaustedPlanSequence(projectRoot);
66
+ if (planKind !== null) {
67
+ return { inPlay: true, kind: planKind };
68
+ }
69
+ return { inPlay: false, kind: "none" };
70
+ }
71
+ export function resolveGatedEntrypointCommand(stepName, projectRoot, detect = detectWorkSelection) {
72
+ const base = [...GATED_ENTRYPOINT_COMMANDS[stepName]];
73
+ if (stepName !== "cache_fresh")
74
+ return base;
75
+ const selection = detect(projectRoot);
76
+ if (selection.inPlay) {
77
+ return [...base, WORK_SELECTION_FLAG];
78
+ }
79
+ return [...base, SKIP_DRIFT_PROBE_FLAG];
80
+ }
20
81
  function truthy(raw) {
21
82
  return new Set(["1", "true", "yes", "on"]).has((raw ?? "").trim().toLowerCase());
22
83
  }
@@ -25,6 +86,7 @@ function stepPasses(step) {
25
86
  return false;
26
87
  if (step.deferred_reason)
27
88
  return true;
89
+ // `drift_probe` is a distinct diagnostic; it must never satisfy a step (#3507).
28
90
  return step.ok === true;
29
91
  }
30
92
  function failedStepMessage(tierName, stepName, step) {
@@ -50,8 +112,31 @@ export function formatRitualRecoveryInstruction(tier = "cold") {
50
112
  return (`Recovery: run ${ready} ` +
51
113
  `(one-shot: session:start + gated ritual + cache recovery as needed).`);
52
114
  }
53
- function runGatedStep(projectRoot, payload, stepName, runner, now) {
54
- const command = [...GATED_ENTRYPOINT_COMMANDS[stepName]];
115
+ /**
116
+ * Sanctioned audited defer copy for gated `cache_fresh` failure (#3506 / #3507).
117
+ * Independent of the #3507 work-selection argv seam — defer is still the
118
+ * operator-audited escape, not the drift-probe skip.
119
+ */
120
+ export function formatCacheFreshDeferSoftPath() {
121
+ const cmd = formatFrameworkCommand(["session:start", "--", "--defer", "cache_fresh=<reason>"]);
122
+ return `Soft path (audited): \`${cmd}\`.`;
123
+ }
124
+ function applyCacheFreshDriftProbeField(payload, command) {
125
+ if (command.includes(SKIP_DRIFT_PROBE_FLAG)) {
126
+ payload.drift_probe = DRIFT_PROBE_SKIPPED_NO_WORK_SELECTION;
127
+ return;
128
+ }
129
+ if (payload.drift_probe === DRIFT_PROBE_SKIPPED_NO_WORK_SELECTION) {
130
+ delete payload.drift_probe;
131
+ }
132
+ }
133
+ function shouldRearmDriftProbe(payload, projectRoot, detect) {
134
+ if (payload.drift_probe !== DRIFT_PROBE_SKIPPED_NO_WORK_SELECTION)
135
+ return false;
136
+ return detect(projectRoot).inPlay;
137
+ }
138
+ function runGatedStep(projectRoot, payload, stepName, runner, now, detect = detectWorkSelection) {
139
+ const command = resolveGatedEntrypointCommand(stepName, projectRoot, detect);
55
140
  const { code, stdout, stderr } = runner(command, projectRoot);
56
141
  const message = stdout.trim() || stderr.trim() || `${command[0]} exited ${code}`;
57
142
  const gated = payload.gated_steps ?? {};
@@ -63,6 +148,9 @@ function runGatedStep(projectRoot, payload, stepName, runner, now) {
63
148
  command,
64
149
  });
65
150
  payload.gated_steps = gated;
151
+ if (stepName === "cache_fresh") {
152
+ applyCacheFreshDriftProbeField(payload, command);
153
+ }
66
154
  try {
67
155
  writeRitualState(projectRoot, payload);
68
156
  }
@@ -353,6 +441,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
353
441
  payload.gated_steps = gated;
354
442
  const runCmd = options.runner ?? defaultRitualRunner;
355
443
  const forced = new Set(options.forceGatedSteps ?? []);
444
+ const detect = options.detectWorkSelection ?? detectWorkSelection;
356
445
  for (const stepName of GATED_STEPS) {
357
446
  const step = gated[stepName];
358
447
  if (step?.deferred_reason && stepName !== "agent_hooks")
@@ -360,9 +449,11 @@ export function verifySessionRitual(projectRoot, options = {}) {
360
449
  // Hook readiness is a live mutation prerequisite, not a cacheable doctor
361
450
  // result. Re-run it at every gated boundary so registration/shim drift
362
451
  // cannot remain hidden behind an earlier green ritual record.
363
- if (stepName !== "agent_hooks" && stepPasses(step) && !forced.has(stepName))
452
+ const rearmDrift = stepName === "cache_fresh" && shouldRearmDriftProbe(payload, projectRoot, detect);
453
+ if (stepName !== "agent_hooks" && stepPasses(step) && !forced.has(stepName) && !rearmDrift) {
364
454
  continue;
365
- const writeError = runGatedStep(projectRoot, payload, stepName, runCmd, instant);
455
+ }
456
+ const writeError = runGatedStep(projectRoot, payload, stepName, runCmd, instant, detect);
366
457
  if (writeError !== null) {
367
458
  return {
368
459
  code: 2,
@@ -465,6 +556,7 @@ export function emitVerifyJson(result) {
465
556
  would_fail_code: result.wouldFailCode,
466
557
  posture: result.posture,
467
558
  ritual_state_required: result.ritualStateRequired,
559
+ recovery_tier: result.recoveryTier ?? null,
468
560
  });
469
561
  }
470
562
  export function emitBypassWarning(result) {
@@ -10,11 +10,12 @@
10
10
  *
11
11
  * ⊗ Fake project `.openclaw/commands/` file emitter.
12
12
  */
13
- import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
13
+ import { existsSync, lstatSync, mkdirSync, readFileSync, statSync } from "node:fs";
14
14
  import { homedir } from "node:os";
15
15
  import { join, resolve } from "node:path";
16
16
  import { detectOpenClaw, isEscapingSkillSymlink, listInScopeSkillsDirs, } from "../doctor/openclaw-skills.js";
17
- import { containedWrite } from "../fs/contained-write.js";
17
+ import { containedMkdir, containedRemove, containedRename, containedWrite, } from "../fs/contained-write.js";
18
+ import { isPortRecordMode } from "../fs/mutation-ledger.js";
18
19
  import { readPlanPolicy } from "../policy/plan-extensions.js";
19
20
  import { loadProjectDefinition } from "../policy/resolve.js";
20
21
  import { generateOpenClawSkillArtifacts, isManagedOpenClawL2Skill, listOpenClawManagedSkillSlugs, } from "./openclaw-adapter.js";
@@ -122,7 +123,7 @@ function writeSkillArtifact(skillsDir, artifact) {
122
123
  return "preserved";
123
124
  }
124
125
  }
125
- mkdirSync(skillDir, { recursive: true });
126
+ containedMkdir({ root: skillsDir, target: skillDir });
126
127
  // Re-check after mkdir: concurrent symlink plant is fail-closed.
127
128
  if (skillDirLstatKind(skillDir) === "symlink" && isEscapingSkillSymlink(skillsDir, skillDir)) {
128
129
  return "preserved";
@@ -137,11 +138,11 @@ function writeSkillArtifact(skillsDir, artifact) {
137
138
  data: artifact.skillMarkdown,
138
139
  mode: "replace",
139
140
  });
140
- renameSync(tmp, skillFile);
141
+ containedRename({ root: skillsDir, from: tmp, to: skillFile, mutation: false });
141
142
  }
142
143
  catch (err) {
143
144
  try {
144
- rmSync(tmp, { force: true });
145
+ containedRemove({ root: skillsDir, target: tmp, mutation: false });
145
146
  }
146
147
  catch {
147
148
  /* best-effort */
@@ -172,8 +173,9 @@ function stripManagedSkills(skillsDir) {
172
173
  if (!isManagedOpenClawL2Skill(raw))
173
174
  continue;
174
175
  try {
175
- rmSync(skillDir, { recursive: true, force: true });
176
- removed.push(skillDir);
176
+ if (containedRemove({ root: skillsDir, target: skillDir, recursive: true }).removed) {
177
+ removed.push(skillDir);
178
+ }
177
179
  }
178
180
  catch {
179
181
  /* best-effort */
@@ -221,7 +223,8 @@ export function depositOpenClawL2ProductCommands(options = {}) {
221
223
  const preservedCustomPaths = [];
222
224
  if (!policy) {
223
225
  for (const skillsDir of skillsDirs) {
224
- mkdirSync(skillsDir, { recursive: true });
226
+ if (!isPortRecordMode())
227
+ mkdirSync(skillsDir, { recursive: true });
225
228
  removedPaths.push(...stripManagedSkills(skillsDir));
226
229
  }
227
230
  if (removedPaths.length > 0) {
@@ -242,7 +245,8 @@ export function depositOpenClawL2ProductCommands(options = {}) {
242
245
  }
243
246
  const artifacts = generateOpenClawSkillArtifacts();
244
247
  for (const skillsDir of skillsDirs) {
245
- mkdirSync(skillsDir, { recursive: true });
248
+ if (!isPortRecordMode())
249
+ mkdirSync(skillsDir, { recursive: true });
246
250
  for (const artifact of artifacts) {
247
251
  const outcome = writeSkillArtifact(skillsDir, artifact);
248
252
  const abs = join(skillsDir, artifact.slug);
@@ -1,5 +1,5 @@
1
- /** Default slices log path relative to project root. */
2
- export declare const DEFAULT_SLICES_LOG_REL_PATH = "vbrief/.eval/slices.jsonl";
1
+ /** Default slices log path relative to an xbrief-layout project root (#1703). */
2
+ export declare const DEFAULT_SLICES_LOG_REL_PATH = "xbrief/.triage-cache/slices.jsonl";
3
3
  export declare const ENV_PROJECT_ROOT = "DEFT_PROJECT_ROOT";
4
4
  export declare const ENV_PROJECT_REPO = "DEFT_PROJECT_REPO";
5
5
  export declare const DEFAULT_ACTOR = "manual:operator";
@@ -1,5 +1,5 @@
1
- /** Default slices log path relative to project root. */
2
- export const DEFAULT_SLICES_LOG_REL_PATH = "vbrief/.eval/slices.jsonl";
1
+ /** Default slices log path relative to an xbrief-layout project root (#1703). */
2
+ export const DEFAULT_SLICES_LOG_REL_PATH = "xbrief/.triage-cache/slices.jsonl";
3
3
  export const ENV_PROJECT_ROOT = "DEFT_PROJECT_ROOT";
4
4
  export const ENV_PROJECT_REPO = "DEFT_PROJECT_REPO";
5
5
  export const DEFAULT_ACTOR = "manual:operator";
@@ -1,6 +1,7 @@
1
1
  import { ContainedWriteError } from "../fs/contained-write.js";
2
2
  import { call as scmCall } from "../scm/call.js";
3
3
  import { pyRepr } from "../scm/py-format.js";
4
+ import { triageCacheRelPath } from "../triage/cache-path.js";
4
5
  import { DEFAULT_ACTOR, DEFAULT_EXPECTED_CLOSE_SIGNAL, DEFAULT_ROLE, WAVE_FLAG_RE, } from "./constants.js";
5
6
  import { IssueValidationError, SliceRecordError } from "./errors.js";
6
7
  import { pythonJsonPretty } from "./json.js";
@@ -171,6 +172,10 @@ export function summariseWaves(waveMap, totalChildren) {
171
172
  .map((waveN) => `wave-${waveN}=${placedByWave.get(waveN)}`);
172
173
  return `${parts.length} wave(s): ${parts.join(", ")}`;
173
174
  }
175
+ /** Layout-aware display path for the slices log actually read or written (#3386). */
176
+ function slicesLogDisplayPath(projectRoot) {
177
+ return triageCacheRelPath(projectRoot, "slices.jsonl");
178
+ }
174
179
  function duplicateMessage(umbrella, duplicate) {
175
180
  return (`slice:record-existing: umbrella #${umbrella} already has a matching record (slice_id=${String(duplicate.slice_id)}, ` +
176
181
  `actor=${String(duplicate.actor)}). Re-run with --force to write a second record.`);
@@ -295,7 +300,7 @@ export function runRecordExisting(args, waveMap, deps = {}) {
295
300
  const waveSummary = summariseWaves(waveMap, children.length);
296
301
  return {
297
302
  exitCode: 0,
298
- stdout: `Wrote vbrief/.eval/slices.jsonl entry for umbrella ` +
303
+ stdout: `Wrote ${slicesLogDisplayPath(resolved.projectRoot)} entry for umbrella ` +
299
304
  `#${args.umbrella} (${children.length} children, ${waveSummary}). ` +
300
305
  `slice_id=${outcome.sliceId}\n`,
301
306
  stderr: "",
@@ -320,6 +325,7 @@ export function runList(args) {
320
325
  if (resolved.exitCode !== 0) {
321
326
  return { exitCode: 2, stdout: "", stderr: `${formatMissingRootError()}\n` };
322
327
  }
328
+ const logDisplay = slicesLogDisplayPath(resolved.projectRoot);
323
329
  const records = readAll({ path: slicesPath(resolved.projectRoot) });
324
330
  if (args.asJson) {
325
331
  return { exitCode: 0, stdout: `${pythonJsonPretty(records)}\n`, stderr: "" };
@@ -327,11 +333,11 @@ export function runList(args) {
327
333
  if (records.length === 0) {
328
334
  return {
329
335
  exitCode: 0,
330
- stdout: "slice:list: no records found in vbrief/.eval/slices.jsonl (file absent or empty).\n",
336
+ stdout: `slice:list: no records found in ${logDisplay} (file absent or empty).\n`,
331
337
  stderr: "",
332
338
  };
333
339
  }
334
- const lines = [`slice:list: ${records.length} record(s) in vbrief/.eval/slices.jsonl`];
340
+ const lines = [`slice:list: ${records.length} record(s) in ${logDisplay}`];
335
341
  for (const record of records) {
336
342
  const umbrella = record.umbrella ?? "?";
337
343
  const actor = record.actor ?? "?";
@@ -1,9 +1,24 @@
1
+ /** 10× occupancy heartbeat TTL. Off-Linux PID reuse / stalled holder reclaim. */
2
+ export declare const STALE_LOCK_HARD_CAP_MS: number;
1
3
  export interface LockDeps {
2
4
  readonly sleepMs?: (ms: number) => void;
3
5
  readonly now?: () => number;
4
6
  }
7
+ export interface HeldLock {
8
+ readonly lockPath: string;
9
+ readonly pid: number;
10
+ readonly token: string;
11
+ readonly acquiredAt: number;
12
+ }
13
+ export interface LockRecord {
14
+ readonly pid: number | null;
15
+ readonly token: string;
16
+ readonly acquiredAt: number | null;
17
+ }
18
+ export declare function parseLockRecord(lockPath: string): LockRecord;
19
+ export declare function assertAppendLockOwned(held: HeldLock): void;
5
20
  /** Serialise appenders across threads AND processes (sidecar lock file). */
6
- export declare function withAppendLock<T>(logPath: string, fn: () => T, deps?: LockDeps): T;
21
+ export declare function withAppendLock<T>(logPath: string, fn: (held: HeldLock) => T, deps?: LockDeps): T;
7
22
  /** Public alias mirroring Python append_lock. */
8
23
  export declare const appendLock: typeof withAppendLock;
9
24
  //# sourceMappingURL=lock.d.ts.map
@@ -1,12 +1,127 @@
1
- import { closeSync, existsSync, mkdirSync, openSync, unlinkSync, writeSync } from "node:fs";
1
+ import { randomUUID } from "node:crypto";
2
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from "node:fs";
2
3
  import { dirname } from "node:path";
3
4
  const threadLocked = { held: false };
5
+ /** 10× occupancy heartbeat TTL. Off-Linux PID reuse / stalled holder reclaim. */
6
+ export const STALE_LOCK_HARD_CAP_MS = 20 * 60 * 1000 * 10;
4
7
  function defaultSleep(ms) {
5
8
  const end = Date.now() + ms;
6
9
  while (Date.now() < end) {
7
10
  /* spin */
8
11
  }
9
12
  }
13
+ export function parseLockRecord(lockPath) {
14
+ try {
15
+ const lines = readFileSync(lockPath, { encoding: "utf8" }).trim().split(/\r?\n/);
16
+ const pidRaw = Number(lines[0]);
17
+ const pid = Number.isInteger(pidRaw) && pidRaw > 0 ? pidRaw : null;
18
+ if (lines.length >= 3) {
19
+ const acquiredRaw = Number(lines[2]);
20
+ return {
21
+ pid,
22
+ token: lines[1] ?? "",
23
+ acquiredAt: Number.isInteger(acquiredRaw) && acquiredRaw > 0 ? acquiredRaw : null,
24
+ };
25
+ }
26
+ const acquiredRaw = Number(lines[1]);
27
+ return {
28
+ pid,
29
+ token: "",
30
+ acquiredAt: Number.isInteger(acquiredRaw) && acquiredRaw > 0 ? acquiredRaw : null,
31
+ };
32
+ }
33
+ catch {
34
+ return { pid: null, token: "", acquiredAt: null };
35
+ }
36
+ }
37
+ export function assertAppendLockOwned(held) {
38
+ const rec = parseLockRecord(held.lockPath);
39
+ if (rec.pid !== held.pid || rec.token !== held.token || rec.token.length === 0) {
40
+ throw new Error(`lock compromised: ${held.lockPath}`);
41
+ }
42
+ }
43
+ function processExists(pid) {
44
+ try {
45
+ process.kill(pid, 0);
46
+ return true;
47
+ }
48
+ catch (err) {
49
+ return err.code !== "ESRCH";
50
+ }
51
+ }
52
+ function linuxPidStartedAfter(pid, acquiredAt) {
53
+ try {
54
+ const stat = readFileSync(`/proc/${pid}/stat`, { encoding: "utf8" });
55
+ const closeParen = stat.lastIndexOf(")");
56
+ if (closeParen < 0)
57
+ return false;
58
+ const startTicks = Number(stat.slice(closeParen + 2).split(" ")[19]);
59
+ if (!Number.isFinite(startTicks))
60
+ return false;
61
+ const btime = /(?:^|\n)btime (\d+)/.exec(readFileSync("/proc/stat", { encoding: "utf8" }));
62
+ if (btime === null)
63
+ return false;
64
+ const startMs = Number(btime[1]) * 1000 + (startTicks * 1000) / 100;
65
+ return startMs > acquiredAt + 1000;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ function lockMtimeMs(lockPath) {
72
+ try {
73
+ return statSync(lockPath).mtimeMs;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ function isAbandoned(rec, now, lockPath) {
80
+ if (rec.pid === null) {
81
+ const mtime = lockMtimeMs(lockPath);
82
+ return mtime !== null && now - mtime > STALE_LOCK_HARD_CAP_MS;
83
+ }
84
+ if (!processExists(rec.pid))
85
+ return true;
86
+ if (rec.acquiredAt !== null && linuxPidStartedAfter(rec.pid, rec.acquiredAt))
87
+ return true;
88
+ if (rec.acquiredAt !== null && now - rec.acquiredAt > STALE_LOCK_HARD_CAP_MS)
89
+ return true;
90
+ return false;
91
+ }
92
+ function recordsMatch(a, b) {
93
+ return a.pid === b.pid && a.token === b.token && a.acquiredAt === b.acquiredAt;
94
+ }
95
+ /** Atomic rename reclaim: one waiter wins; losers see ENOENT. */
96
+ function tryReclaimAbandonedOwner(lockPath, now) {
97
+ const observed = parseLockRecord(lockPath);
98
+ if (!isAbandoned(observed, now, lockPath))
99
+ return false;
100
+ const quarantine = `${lockPath}.reclaim.${process.pid}.${randomUUID()}`;
101
+ try {
102
+ renameSync(lockPath, quarantine);
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ const quarantined = parseLockRecord(quarantine);
108
+ if (recordsMatch(observed, quarantined)) {
109
+ try {
110
+ unlinkSync(quarantine);
111
+ }
112
+ catch {
113
+ /* already gone */
114
+ }
115
+ return true;
116
+ }
117
+ try {
118
+ renameSync(quarantine, lockPath);
119
+ }
120
+ catch {
121
+ /* best-effort restore */
122
+ }
123
+ return false;
124
+ }
10
125
  /** Serialise appenders across threads AND processes (sidecar lock file). */
11
126
  export function withAppendLock(logPath, fn, deps = {}) {
12
127
  const sleepMs = deps.sleepMs ?? defaultSleep;
@@ -18,13 +133,18 @@ export function withAppendLock(logPath, fn, deps = {}) {
18
133
  }
19
134
  threadLocked.held = true;
20
135
  let fd;
136
+ let held;
137
+ let reclaimedStale = false;
21
138
  try {
22
139
  const deadline = now() + 30_000;
23
140
  while (true) {
24
141
  try {
25
142
  fd = openSync(lockPath, "wx");
26
- writeSync(fd, Buffer.from("\0"));
27
- break;
143
+ const token = randomUUID();
144
+ const acquiredAt = now();
145
+ writeSync(fd, Buffer.from(`${process.pid}\n${token}\n${acquiredAt}\n`));
146
+ held = { lockPath, pid: process.pid, token, acquiredAt };
147
+ return fn(held);
28
148
  }
29
149
  catch (err) {
30
150
  const code = err.code;
@@ -32,26 +152,32 @@ export function withAppendLock(logPath, fn, deps = {}) {
32
152
  throw err;
33
153
  }
34
154
  if (now() > deadline) {
155
+ if (!reclaimedStale && tryReclaimAbandonedOwner(lockPath, now())) {
156
+ reclaimedStale = true;
157
+ continue;
158
+ }
35
159
  throw new Error(`timed out acquiring lock for ${logPath}`);
36
160
  }
37
161
  sleepMs(20);
38
162
  }
39
163
  }
40
- return fn();
41
164
  }
42
165
  finally {
43
166
  if (fd !== undefined) {
44
167
  closeSync(fd);
45
168
  }
46
- threadLocked.held = false;
47
- try {
48
- if (existsSync(lockPath)) {
49
- unlinkSync(lockPath);
169
+ if (held !== undefined) {
170
+ try {
171
+ assertAppendLockOwned(held);
172
+ if (existsSync(lockPath)) {
173
+ unlinkSync(lockPath);
174
+ }
175
+ }
176
+ catch {
177
+ /* not ours anymore */
50
178
  }
51
179
  }
52
- catch {
53
- /* best-effort */
54
- }
180
+ threadLocked.held = false;
55
181
  }
56
182
  }
57
183
  /** Public alias mirroring Python append_lock. */
@@ -20,6 +20,8 @@ export interface EvaluateOptions {
20
20
  readonly projectRoot?: string;
21
21
  /** Skip parent-lineage check (tests / opt-out). Default false. */
22
22
  readonly skipParentLineage?: boolean;
23
+ /** Skip project-invariant coverage (#3425). Default false. */
24
+ readonly skipProjectInvariants?: boolean;
23
25
  }
24
26
  /**
25
27
  * Pure evaluator — returns exit code + human message. Faithful to
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { evaluateProjectInvariantsGate, resolveProjectRootForInvariants, } from "../preflight/project-invariants-gate.js";
3
4
  import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
4
5
  import { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND, VALID_DISPATCH_KINDS, } from "./allocation.js";
5
6
  export const ACTIVE_FOLDER = "active";
@@ -168,6 +169,18 @@ export function evaluate(vbriefPath, options = {}) {
168
169
  `\n${formatParentLineageLine(lineage)}`,
169
170
  };
170
171
  }
172
+ const invariants = evaluateProjectInvariantsGate(vbriefCheck.payload, {
173
+ projectRoot: resolveProjectRootForInvariants(vbriefCheck.path, options.projectRoot),
174
+ skip: options.skipProjectInvariants === true,
175
+ });
176
+ if (!invariants.ok) {
177
+ return {
178
+ exitCode: 1,
179
+ dispatchKind: null,
180
+ parentLineage: lineage,
181
+ message: `not ready: ${invariants.message}`,
182
+ };
183
+ }
171
184
  const lineageNote = lineage.applicable
172
185
  ? `parent lineage OK (${lineage.parent_requirement_ids.length} req IDs` +
173
186
  (lineage.negative_invariant_ids.length > 0
@@ -45,9 +45,12 @@ export declare function completeCohort(args: {
45
45
  emitJson?: boolean;
46
46
  /** Per-story delivery evidence; without it code-bearing stories fail closed (#3041). */
47
47
  delivery?: CohortDeliveryContext | null;
48
+ /** Optional launch allocation plan id so close-out reads this cohort's occupancy slot. */
49
+ allocationPlanId?: string | null;
48
50
  }): {
49
51
  exitCode: number;
50
52
  stdout: string;
51
53
  stderr: string;
54
+ sweep: SweepResult | null;
52
55
  };
53
56
  //# sourceMappingURL=complete-cohort.d.ts.map