@deftai/directive-core 0.88.0 → 0.90.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 (202) hide show
  1. package/dist/authz/actions.d.ts +55 -0
  2. package/dist/authz/actions.js +125 -0
  3. package/dist/authz/classify.d.ts +23 -0
  4. package/dist/authz/classify.js +217 -0
  5. package/dist/authz/closed-verb.d.ts +42 -0
  6. package/dist/authz/closed-verb.js +279 -0
  7. package/dist/authz/evaluate.d.ts +41 -0
  8. package/dist/authz/evaluate.js +298 -0
  9. package/dist/authz/index.d.ts +15 -0
  10. package/dist/authz/index.js +15 -0
  11. package/dist/authz/origin.d.ts +28 -0
  12. package/dist/authz/origin.js +64 -0
  13. package/dist/authz/paths.d.ts +6 -0
  14. package/dist/authz/paths.js +19 -0
  15. package/dist/authz/store.d.ts +40 -0
  16. package/dist/authz/store.js +317 -0
  17. package/dist/authz/templates.d.ts +139 -0
  18. package/dist/authz/templates.js +241 -0
  19. package/dist/authz/types.d.ts +111 -0
  20. package/dist/authz/types.js +41 -0
  21. package/dist/authz/verb-classification.d.ts +44 -0
  22. package/dist/authz/verb-classification.js +237 -0
  23. package/dist/branch/evaluate.js +6 -1
  24. package/dist/cache/fetch.js +10 -5
  25. package/dist/cache/io.d.ts +9 -2
  26. package/dist/cache/io.js +30 -10
  27. package/dist/cache/task-cache/store.js +11 -7
  28. package/dist/capacity/backfill.js +10 -4
  29. package/dist/category-b-namespace/index.js +10 -4
  30. package/dist/codebase/default-extractor.js +3 -0
  31. package/dist/codebase/map.js +11 -4
  32. package/dist/doctor/constants.d.ts +1 -1
  33. package/dist/doctor/constants.js +2 -0
  34. package/dist/doctor/doctor-state.js +28 -4
  35. package/dist/doctor/flags.js +21 -1
  36. package/dist/doctor/index.d.ts +1 -0
  37. package/dist/doctor/index.js +1 -0
  38. package/dist/doctor/main.d.ts +10 -0
  39. package/dist/doctor/main.js +182 -0
  40. package/dist/doctor/openclaw-skills.d.ts +109 -0
  41. package/dist/doctor/openclaw-skills.js +463 -0
  42. package/dist/doctor/types.d.ts +22 -0
  43. package/dist/escalation/actions.d.ts +63 -0
  44. package/dist/escalation/actions.js +137 -0
  45. package/dist/escalation/index.d.ts +8 -0
  46. package/dist/escalation/index.js +8 -0
  47. package/dist/escalation/paths.d.ts +3 -0
  48. package/dist/escalation/paths.js +10 -0
  49. package/dist/escalation/store.d.ts +17 -0
  50. package/dist/escalation/store.js +172 -0
  51. package/dist/escalation/types.d.ts +73 -0
  52. package/dist/escalation/types.js +43 -0
  53. package/dist/eval/crud-telemetry.js +30 -10
  54. package/dist/eval/health.js +22 -7
  55. package/dist/eval/readback.js +11 -10
  56. package/dist/eval/run.js +32 -16
  57. package/dist/events/attribution-enrichment.js +10 -4
  58. package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
  59. package/dist/finish-loop/directive-finish-loop.js +239 -0
  60. package/dist/finish-loop/grant-gate.d.ts +31 -0
  61. package/dist/finish-loop/grant-gate.js +169 -0
  62. package/dist/finish-loop/index.d.ts +11 -0
  63. package/dist/finish-loop/index.js +11 -0
  64. package/dist/finish-loop/main.d.ts +35 -0
  65. package/dist/finish-loop/main.js +365 -0
  66. package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
  67. package/dist/finish-loop/pr-finish-loop.js +217 -0
  68. package/dist/finish-loop/progress.d.ts +17 -0
  69. package/dist/finish-loop/progress.js +45 -0
  70. package/dist/finish-loop/queue.d.ts +16 -0
  71. package/dist/finish-loop/queue.js +65 -0
  72. package/dist/finish-loop/types.d.ts +52 -0
  73. package/dist/finish-loop/types.js +10 -0
  74. package/dist/fs/contained-write.d.ts +101 -0
  75. package/dist/fs/contained-write.js +281 -0
  76. package/dist/hooks/classify/classify.d.ts +10 -0
  77. package/dist/hooks/classify/classify.js +37 -0
  78. package/dist/hooks/classify/index.d.ts +14 -0
  79. package/dist/hooks/classify/index.js +13 -0
  80. package/dist/hooks/classify/paths.d.ts +22 -0
  81. package/dist/hooks/classify/paths.js +72 -0
  82. package/dist/hooks/classify/payload.d.ts +16 -0
  83. package/dist/hooks/classify/payload.js +45 -0
  84. package/dist/hooks/classify/stdin.d.ts +12 -0
  85. package/dist/hooks/classify/stdin.js +63 -0
  86. package/dist/hooks/classify/tool-name.d.ts +18 -0
  87. package/dist/hooks/classify/tool-name.js +85 -0
  88. package/dist/hooks/classify/types.d.ts +41 -0
  89. package/dist/hooks/classify/types.js +7 -0
  90. package/dist/hooks/dispatcher.d.ts +21 -18
  91. package/dist/hooks/dispatcher.js +232 -168
  92. package/dist/hooks/fixtures/cases.d.ts +44 -0
  93. package/dist/hooks/fixtures/cases.js +595 -0
  94. package/dist/hooks/fixtures/index.d.ts +2 -0
  95. package/dist/hooks/fixtures/index.js +2 -0
  96. package/dist/hooks/index.d.ts +2 -0
  97. package/dist/hooks/index.js +2 -0
  98. package/dist/index.d.ts +5 -0
  99. package/dist/index.js +6 -0
  100. package/dist/init-deposit/agent-hooks.js +27 -8
  101. package/dist/init-deposit/gitignore.js +11 -3
  102. package/dist/init-deposit/headless-manifest.js +12 -4
  103. package/dist/init-deposit/migrate.d.ts +1 -1
  104. package/dist/init-deposit/migrate.js +13 -3
  105. package/dist/init-deposit/prettierignore.js +10 -3
  106. package/dist/init-deposit/scaffold.js +35 -26
  107. package/dist/init-deposit/xbrief-projections.js +11 -4
  108. package/dist/intake/candidates-log.js +46 -35
  109. package/dist/intake/github-body-cli.d.ts +6 -0
  110. package/dist/intake/github-body-cli.js +17 -0
  111. package/dist/intake/github-body.d.ts +46 -3
  112. package/dist/intake/github-body.js +191 -20
  113. package/dist/intake/issue-emit.d.ts +1 -0
  114. package/dist/intake/issue-emit.js +41 -23
  115. package/dist/lifecycle/event-detect.js +12 -4
  116. package/dist/lifecycle/events.js +31 -20
  117. package/dist/lifecycle/index.d.ts +1 -0
  118. package/dist/lifecycle/index.js +1 -0
  119. package/dist/lifecycle/lifecycle-hygiene.js +9 -3
  120. package/dist/lifecycle/stats.d.ts +53 -0
  121. package/dist/lifecycle/stats.js +286 -0
  122. package/dist/packs/pack-render.js +19 -6
  123. package/dist/plan-sequence/store.js +11 -4
  124. package/dist/policy/hotfix-criteria.d.ts +79 -0
  125. package/dist/policy/hotfix-criteria.js +197 -0
  126. package/dist/policy/index.d.ts +4 -0
  127. package/dist/policy/index.js +31 -1
  128. package/dist/policy/intent-ceiling.d.ts +79 -0
  129. package/dist/policy/intent-ceiling.js +181 -0
  130. package/dist/policy/no-deft-directive.js +10 -3
  131. package/dist/policy/org-force-on-migration.js +9 -5
  132. package/dist/policy/require-human-merge.d.ts +75 -0
  133. package/dist/policy/require-human-merge.js +324 -0
  134. package/dist/policy/resolve.js +17 -6
  135. package/dist/policy/runtime-authority.d.ts +15 -2
  136. package/dist/policy/runtime-authority.js +23 -3
  137. package/dist/policy/write-fence.d.ts +78 -0
  138. package/dist/policy/write-fence.js +164 -0
  139. package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
  140. package/dist/pr-wait-mergeable/cascade.js +28 -0
  141. package/dist/preflight/evaluate.js +10 -0
  142. package/dist/product-signal/consent.js +21 -5
  143. package/dist/product-signal/submit.js +22 -12
  144. package/dist/release/build-dist-runner.js +5 -2
  145. package/dist/release/build-dist.d.ts +19 -1
  146. package/dist/release/build-dist.js +50 -2
  147. package/dist/release/native-steps.js +7 -2
  148. package/dist/release/spawn.js +13 -0
  149. package/dist/release-e2e/git-ops.d.ts +7 -0
  150. package/dist/release-e2e/git-ops.js +35 -2
  151. package/dist/release-publish/pipeline.d.ts +13 -0
  152. package/dist/release-publish/pipeline.js +46 -1
  153. package/dist/scope/audit-log.js +19 -24
  154. package/dist/scope/decompose.js +10 -5
  155. package/dist/scope/decomposed-refs.js +18 -3
  156. package/dist/scope/demote.js +9 -2
  157. package/dist/scope/undo.js +9 -2
  158. package/dist/session/index.d.ts +2 -0
  159. package/dist/session/index.js +2 -0
  160. package/dist/session/process-cost-constants.d.ts +9 -0
  161. package/dist/session/process-cost-constants.js +11 -0
  162. package/dist/session/process-cost.d.ts +53 -0
  163. package/dist/session/process-cost.js +82 -0
  164. package/dist/session/release-availability.js +26 -6
  165. package/dist/session/ritual-sentinel.d.ts +5 -0
  166. package/dist/session/ritual-sentinel.js +31 -13
  167. package/dist/session/session-ready.d.ts +67 -0
  168. package/dist/session/session-ready.js +264 -0
  169. package/dist/session/session-start.d.ts +56 -1
  170. package/dist/session/session-start.js +384 -23
  171. package/dist/session/verify-session-ritual.d.ts +8 -0
  172. package/dist/session/verify-session-ritual.js +95 -35
  173. package/dist/staleness-tickler/state.js +26 -6
  174. package/dist/swarm/launch.js +13 -3
  175. package/dist/swarm/routing.js +9 -3
  176. package/dist/task-surface/index.js +24 -9
  177. package/dist/tool-events/classify.d.ts +20 -0
  178. package/dist/tool-events/classify.js +634 -0
  179. package/dist/tool-events/index.d.ts +10 -0
  180. package/dist/tool-events/index.js +10 -0
  181. package/dist/tool-events/summarize.d.ts +34 -0
  182. package/dist/tool-events/summarize.js +93 -0
  183. package/dist/tool-events/types.d.ts +52 -0
  184. package/dist/tool-events/types.js +13 -0
  185. package/dist/triage/bootstrap/gitignore.js +53 -10
  186. package/dist/triage/cache-path.js +10 -3
  187. package/dist/triage/welcome/summary.js +11 -4
  188. package/dist/triage/welcome/writers.js +45 -16
  189. package/dist/validate-content/validate-links.js +5 -0
  190. package/dist/value/readback.js +11 -6
  191. package/dist/vbrief-activate/activate.js +12 -4
  192. package/dist/vbrief-build/project-definition-io.js +14 -6
  193. package/dist/verify-source/contained-writes.d.ts +59 -0
  194. package/dist/verify-source/contained-writes.js +272 -0
  195. package/dist/verify-source/index.d.ts +1 -0
  196. package/dist/verify-source/index.js +1 -0
  197. package/dist/verify-source/openclaw-tier1.js +14 -1
  198. package/dist/verify-source/verify-stubs.js +3 -0
  199. package/dist/xbrief-migrate/migrate-project.js +26 -12
  200. package/dist/xbrief-migrate/transforms.d.ts +4 -2
  201. package/dist/xbrief-migrate/transforms.js +37 -14
  202. package/package.json +19 -3
@@ -4,8 +4,8 @@ import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js
4
4
  import { pythonJsonDump } from "./json.js";
5
5
  import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
6
6
  import { defaultRitualRunner } from "./ritual-entrypoint.js";
7
- import { readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
8
- import { GATED_STEPS, QUICK_STEPS } from "./session-start.js";
7
+ import { readRitualState, ritualStateMarksRearmNeeded, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
8
+ import { formatSessionStartRecoveryCommand, GATED_STEPS, QUICK_STEPS, } from "./session-start.js";
9
9
  import { resolveSessionRitualStalenessHours } from "./staleness.js";
10
10
  export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
11
11
  export const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
@@ -25,9 +25,10 @@ function stepPasses(step) {
25
25
  return step.ok === true;
26
26
  }
27
27
  function failedStepMessage(tierName, stepName, step) {
28
+ const coldCmd = formatSessionStartRecoveryCommand("cold");
28
29
  if (step === null || step === undefined) {
29
30
  return (`session ritual ${tierName} step '${stepName}' is missing. ` +
30
- `Run \`${formatFrameworkCommand(["session:start"])}\` before implementation dispatch.`);
31
+ `Run \`${coldCmd}\` before implementation dispatch.`);
31
32
  }
32
33
  if (typeof step === "object" &&
33
34
  step !== null &&
@@ -38,6 +39,19 @@ function failedStepMessage(tierName, stepName, step) {
38
39
  const suffix = typeof message === "string" && message.length > 0 ? `: ${message}` : "";
39
40
  return `session ritual ${tierName} step '${stepName}' failed${suffix}`;
40
41
  }
42
+ /** Human recovery line after a failed ritual probe (#2992 / #2993). */
43
+ export function formatRitualRecoveryInstruction(tier = "cold") {
44
+ const ready = formatFrameworkCommand(["session:ready"]);
45
+ const start = formatSessionStartRecoveryCommand(tier);
46
+ const verify = formatFrameworkCommand(["verify:session-ritual", "--", "--tier=gated"]);
47
+ if (tier === "rearm") {
48
+ return (`Recovery: run ${ready} (one-shot), or ${start} ` +
49
+ `(or full ${formatSessionStartRecoveryCommand("cold")} if worktree/HEAD changed), ` +
50
+ `then ${verify}.`);
51
+ }
52
+ return (`Recovery: run ${ready} ` +
53
+ `(one-shot: session:start + gated ritual + cache recovery as needed).`);
54
+ }
41
55
  function runGatedStep(projectRoot, payload, stepName, runner, now) {
42
56
  const command = [...(GATED_ENTRYPOINT_COMMANDS[stepName] ?? [])];
43
57
  const { code, stdout, stderr } = runner(command, projectRoot);
@@ -60,29 +74,42 @@ function runGatedStep(projectRoot, payload, stepName, runner, now) {
60
74
  return null;
61
75
  }
62
76
  function headDriftRecoveryMessage() {
77
+ const coldCmd = formatSessionStartRecoveryCommand("cold");
63
78
  return (`session ritual state is stale because git HEAD changed discontinuously. ` +
64
- `Run \`${formatFrameworkCommand(["session:start"])}\` again.`);
79
+ `Run \`${coldCmd}\` again (full cold ceremony required).`);
65
80
  }
66
81
  function evaluateLoadedState(projectRoot, state, input) {
67
82
  const runGit = input.runGit ?? defaultGitRunner;
83
+ const coldCmd = formatSessionStartRecoveryCommand("cold");
84
+ const rearmCmd = formatSessionStartRecoveryCommand("rearm");
68
85
  const { head: currentHead, error: headError } = gitHead(projectRoot, runGit);
69
86
  if (currentHead === null) {
70
- return [2, headError ?? "could not resolve git HEAD"];
87
+ return {
88
+ code: 2,
89
+ message: headError ?? "could not resolve git HEAD",
90
+ recoveryTier: "cold",
91
+ };
71
92
  }
72
93
  const currentWorktree = worktreePath(projectRoot, runGit);
73
94
  if (state.worktreePath !== currentWorktree) {
74
- return [
75
- 1,
76
- `session ritual state belongs to a different worktree (${state.worktreePath}); run \`${formatFrameworkCommand(["session:start"])}\` here.`,
77
- ];
95
+ return {
96
+ code: 1,
97
+ message: `session ritual state belongs to a different worktree (${state.worktreePath}); ` +
98
+ `run \`${coldCmd}\` here (full cold ceremony required).`,
99
+ recoveryTier: "cold",
100
+ };
78
101
  }
79
102
  if (state.gitHead !== currentHead) {
80
103
  const forward = gitIsAncestor(projectRoot, state.gitHead, currentHead, runGit);
81
104
  if (forward === null) {
82
- return [2, "could not verify git history for session ritual"];
105
+ return {
106
+ code: 2,
107
+ message: "could not verify git history for session ritual",
108
+ recoveryTier: "cold",
109
+ };
83
110
  }
84
111
  if (!forward) {
85
- return [1, headDriftRecoveryMessage()];
112
+ return { code: 1, message: headDriftRecoveryMessage(), recoveryTier: "cold" };
86
113
  }
87
114
  if (input.rebindForwardHead) {
88
115
  const payload = { ...state.raw, git_head: currentHead };
@@ -90,37 +117,63 @@ function evaluateLoadedState(projectRoot, state, input) {
90
117
  writeRitualState(projectRoot, payload);
91
118
  }
92
119
  catch (exc) {
93
- return [2, `could not rebind session ritual git HEAD: ${String(exc)}`];
120
+ return {
121
+ code: 2,
122
+ message: `could not rebind session ritual git HEAD: ${String(exc)}`,
123
+ recoveryTier: "cold",
124
+ };
94
125
  }
95
126
  }
96
127
  }
97
128
  const staleness = resolveSessionRitualStalenessHours(projectRoot);
98
129
  if (staleness.source === "default-on-error") {
99
- return [2, staleness.error ?? "session ritual staleness policy is invalid"];
130
+ return {
131
+ code: 2,
132
+ message: staleness.error ?? "session ritual staleness policy is invalid",
133
+ recoveryTier: "cold",
134
+ };
100
135
  }
101
136
  const maxAgeMs = staleness.hours * 60 * 60 * 1000;
102
137
  if (input.now.getTime() - state.startedAt.getTime() > maxAgeMs) {
103
- const startCommand = formatFrameworkCommand(["session:start"]);
104
- return [
105
- 1,
106
- `session ritual state is stale (older than ${staleness.hours}h). Run \`${startCommand}\` again.`,
107
- ];
138
+ // Age / compact stale on same worktree + continuous HEAD → prefer re-arm (#2992).
139
+ // compact_resume_at / rearm_needed amplify the same recovery path.
140
+ const compactNote = ritualStateMarksRearmNeeded(state)
141
+ ? " Compact/resume marked re-arm needed."
142
+ : "";
143
+ return {
144
+ code: 1,
145
+ message: `session ritual state is stale (older than ${staleness.hours}h).${compactNote} ` +
146
+ `Run \`${rearmCmd}\` to re-arm (or \`${coldCmd}\` for a full cold ceremony).`,
147
+ recoveryTier: "rearm",
148
+ };
108
149
  }
109
150
  for (const stepName of QUICK_STEPS) {
110
151
  const step = state.quickSteps[stepName];
111
152
  if (!stepPasses(step)) {
112
- return [1, failedStepMessage("quick", stepName, step)];
153
+ return {
154
+ code: 1,
155
+ message: failedStepMessage("quick", stepName, step),
156
+ recoveryTier: "cold",
157
+ };
113
158
  }
114
159
  }
115
160
  if (input.tier === "gated") {
116
161
  for (const stepName of GATED_STEPS) {
117
162
  const step = state.gatedSteps[stepName];
118
163
  if (!stepPasses(step)) {
119
- return [1, failedStepMessage("gated", stepName, step)];
164
+ return {
165
+ code: 1,
166
+ message: failedStepMessage("gated", stepName, step),
167
+ recoveryTier: "cold",
168
+ };
120
169
  }
121
170
  }
122
171
  }
123
- return [0, `OK session ritual ${input.tier} tier is fresh.`];
172
+ return {
173
+ code: 0,
174
+ message: `OK session ritual ${input.tier} tier is fresh.`,
175
+ recoveryTier: null,
176
+ };
124
177
  }
125
178
  /**
126
179
  * Read-only ritual-state inspection for host hooks.
@@ -155,7 +208,7 @@ export function inspectSessionRitual(projectRoot, options = {}) {
155
208
  const [state, err] = readRitualState(projectRoot);
156
209
  if (state === null) {
157
210
  const code = missingStateFile ? 1 : 2;
158
- const startCommand = formatFrameworkCommand(["session:start"]);
211
+ const startCommand = formatSessionStartRecoveryCommand("cold");
159
212
  return {
160
213
  code,
161
214
  message: code === 1
@@ -167,23 +220,25 @@ export function inspectSessionRitual(projectRoot, options = {}) {
167
220
  wouldFailCode: null,
168
221
  posture,
169
222
  ritualStateRequired,
223
+ recoveryTier: "cold",
170
224
  };
171
225
  }
172
- const [code, message] = evaluateLoadedState(projectRoot, state, {
226
+ const evaluated = evaluateLoadedState(projectRoot, state, {
173
227
  tier,
174
228
  now: options.now ?? new Date(),
175
229
  runGit: options.runGit,
176
230
  rebindForwardHead: false,
177
231
  });
178
232
  return {
179
- code,
180
- message,
233
+ code: evaluated.code,
234
+ message: evaluated.message,
181
235
  tier,
182
236
  statePath,
183
237
  bypassed: false,
184
238
  wouldFailCode: null,
185
239
  posture,
186
240
  ritualStateRequired,
241
+ recoveryTier: evaluated.recoveryTier,
187
242
  };
188
243
  }
189
244
  export function verifySessionRitual(projectRoot, options = {}) {
@@ -231,7 +286,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
231
286
  let [state, err] = readRitualState(projectRoot);
232
287
  if (state === null) {
233
288
  const code = missingStateFile ? 1 : 2;
234
- const startCommand = formatFrameworkCommand(["session:start"]);
289
+ const startCommand = formatSessionStartRecoveryCommand("cold");
235
290
  const message = code === 1
236
291
  ? `${err}. Run \`${startCommand}\` before implementation dispatch.`
237
292
  : (err ?? "ritual state invalid");
@@ -245,6 +300,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
245
300
  wouldFailCode: code,
246
301
  posture,
247
302
  ritualStateRequired,
303
+ recoveryTier: "cold",
248
304
  };
249
305
  }
250
306
  return {
@@ -256,25 +312,27 @@ export function verifySessionRitual(projectRoot, options = {}) {
256
312
  wouldFailCode: null,
257
313
  posture,
258
314
  ritualStateRequired,
315
+ recoveryTier: "cold",
259
316
  };
260
317
  }
261
318
  if (tier === "gated" && !isBypassed) {
262
- const [precheckCode, precheckMessage] = evaluateLoadedState(projectRoot, state, {
319
+ const precheck = evaluateLoadedState(projectRoot, state, {
263
320
  tier: "quick",
264
321
  now: instant,
265
322
  runGit: options.runGit,
266
323
  rebindForwardHead: true,
267
324
  });
268
- if (precheckCode !== 0) {
325
+ if (precheck.code !== 0) {
269
326
  return {
270
- code: precheckCode,
271
- message: precheckMessage,
327
+ code: precheck.code,
328
+ message: precheck.message,
272
329
  tier,
273
330
  statePath,
274
331
  bypassed: false,
275
332
  wouldFailCode: null,
276
333
  posture,
277
334
  ritualStateRequired,
335
+ recoveryTier: precheck.recoveryTier,
278
336
  };
279
337
  }
280
338
  const reloadedAfterPrecheck = readRitualState(projectRoot);
@@ -332,7 +390,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
332
390
  };
333
391
  }
334
392
  }
335
- const [code, message] = evaluateLoadedState(projectRoot, state, {
393
+ const evaluated = evaluateLoadedState(projectRoot, state, {
336
394
  tier,
337
395
  now: instant,
338
396
  runGit: options.runGit,
@@ -341,24 +399,26 @@ export function verifySessionRitual(projectRoot, options = {}) {
341
399
  if (isBypassed) {
342
400
  return {
343
401
  code: 0,
344
- message,
402
+ message: evaluated.message,
345
403
  tier,
346
404
  statePath,
347
405
  bypassed: true,
348
- wouldFailCode: code === 0 ? null : code,
406
+ wouldFailCode: evaluated.code === 0 ? null : evaluated.code,
349
407
  posture,
350
408
  ritualStateRequired,
409
+ recoveryTier: evaluated.recoveryTier,
351
410
  };
352
411
  }
353
412
  return {
354
- code,
355
- message,
413
+ code: evaluated.code,
414
+ message: evaluated.message,
356
415
  tier,
357
416
  statePath,
358
417
  bypassed: false,
359
418
  wouldFailCode: null,
360
419
  posture,
361
420
  ritualStateRequired,
421
+ recoveryTier: evaluated.recoveryTier,
362
422
  };
363
423
  }
364
424
  export function emitVerifyJson(result) {
@@ -1,5 +1,6 @@
1
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
1
+ import { mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { containedWrite } from "../fs/contained-write.js";
3
4
  import { resolveTriageCachePath } from "../triage/cache-path.js";
4
5
  export const STATE_RELATIVE_PATH = join("xbrief", ".triage-cache", "staleness-tickler-state.json");
5
6
  const STATE_FILE_NAME = "staleness-tickler-state.json";
@@ -15,10 +16,29 @@ function defaultReadText(path) {
15
16
  }
16
17
  }
17
18
  function defaultWriteText(path, content) {
18
- mkdirSync(dirname(path), { recursive: true });
19
- const temporary = `${path}.${process.pid}.tmp`;
20
- writeFileSync(temporary, content, "utf8");
21
- renameSync(temporary, path);
19
+ const dir = dirname(path);
20
+ mkdirSync(dir, { recursive: true });
21
+ const tmpBase = `${basename(path)}.${process.pid}.tmp`;
22
+ const temporary = join(dir, tmpBase);
23
+ try {
24
+ // #2980 wave D: product write sink routes through containedWrite.
25
+ containedWrite({
26
+ root: resolve(dir),
27
+ target: tmpBase,
28
+ data: content,
29
+ mode: "create",
30
+ });
31
+ renameSync(temporary, path);
32
+ }
33
+ catch (err) {
34
+ try {
35
+ rmSync(temporary, { force: true });
36
+ }
37
+ catch {
38
+ /* best-effort cleanup */
39
+ }
40
+ throw err;
41
+ }
22
42
  }
23
43
  export function parseStalenessTicklerState(text) {
24
44
  if (text === null) {
@@ -1,5 +1,6 @@
1
- import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { basename, isAbsolute, join, resolve } from "node:path";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
2
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
+ import { containedWrite } from "../fs/contained-write.js";
3
4
  import { inferGithubAuthMode } from "../intake/github-auth-modes.js";
4
5
  import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
5
6
  import { hasArtifactSuffix, resolveLifecycleFolder, stripArtifactSuffix, } from "../layout/resolve.js";
@@ -581,7 +582,16 @@ export function swarmLaunch(args) {
581
582
  const rendered = `${JSON.stringify(manifest, null, 2)}\n`;
582
583
  if (args.output !== undefined && args.output !== null) {
583
584
  try {
584
- writeFileSync(args.output, rendered, "utf8");
585
+ // #2980 wave D: product write sink routes through containedWrite.
586
+ const abs = resolve(args.output);
587
+ const dir = dirname(abs);
588
+ mkdirSync(dir, { recursive: true });
589
+ containedWrite({
590
+ root: dir,
591
+ target: basename(abs),
592
+ data: rendered,
593
+ mode: "replace",
594
+ });
585
595
  }
586
596
  catch (exc) {
587
597
  return {
@@ -14,8 +14,9 @@
14
14
  * explicit default.
15
15
  */
16
16
  import { execFileSync } from "node:child_process";
17
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { existsSync, readFileSync } from "node:fs";
18
18
  import { dirname, isAbsolute, join, resolve } from "node:path";
19
+ import { containedWrite } from "../fs/contained-write.js";
19
20
  import { assertWriteTargetSafe } from "../fs/projection-containment.js";
20
21
  /**
21
22
  * The fixed worker-role vocabulary (reused from #1531). No separate tier
@@ -234,7 +235,12 @@ export function writeModelDecision(projectRoot, path, provider, role, decision)
234
235
  decidedAt: decision.decidedAt ?? new Date().toISOString(),
235
236
  };
236
237
  file[provider] = block;
237
- mkdirSync(dirname(path), { recursive: true });
238
- writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, "utf8");
238
+ // #2980 wave D: product write sink routes through containedWrite.
239
+ containedWrite({
240
+ root: resolve(projectRoot),
241
+ target: path,
242
+ data: `${JSON.stringify(file, null, 2)}\n`,
243
+ mode: "replace",
244
+ });
239
245
  }
240
246
  //# sourceMappingURL=routing.js.map
@@ -1,8 +1,9 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { LEGACY_VBRIEF_VERSION } from "@deftai/directive-types";
5
- import { assertProjectionContained, assertWriteTargetSafe, ProjectionContainmentError, } from "../fs/projection-containment.js";
5
+ import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
6
+ import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
6
7
  const UNRELEASED_RE = /## \[Unreleased\][ \t]*\n([\s\S]*?)(?=\n## \[|$)/;
7
8
  const CHANGE_NAME_RE = /^[\w][\w-]*$/;
8
9
  const COMMIT_TYPES = "feat|fix|docs|chore|refactor|test|style|perf|ci|build|revert";
@@ -14,7 +15,7 @@ function projectionTarget(projectDir, ...relSegments) {
14
15
  return target;
15
16
  }
16
17
  function containmentExitCode(io, err) {
17
- if (err instanceof ProjectionContainmentError) {
18
+ if (err instanceof ProjectionContainmentError || err instanceof ContainedWriteError) {
18
19
  io.writeErr(`FAIL: ${err.message}\n`);
19
20
  return 2;
20
21
  }
@@ -93,10 +94,19 @@ export function runChangeInit(projectRoot, name, io) {
93
94
  mkdirSync(specsDir, { recursive: true });
94
95
  const proposalPath = join(base, "proposal.xbrief.json");
95
96
  const tasksPath = join(base, "tasks.xbrief.json");
96
- assertWriteTargetSafe(projectAbs, proposalPath);
97
- assertWriteTargetSafe(projectAbs, tasksPath);
98
- writeFileSync(proposalPath, `${JSON.stringify(proposalTemplate(trimmed), null, 2)}\n`, "utf8");
99
- writeFileSync(tasksPath, `${JSON.stringify(tasksTemplate(trimmed), null, 2)}\n`, "utf8");
97
+ // #2980 wave D: product write sinks route through containedWrite.
98
+ containedWrite({
99
+ root: projectAbs,
100
+ target: proposalPath,
101
+ data: `${JSON.stringify(proposalTemplate(trimmed), null, 2)}\n`,
102
+ mode: "create",
103
+ });
104
+ containedWrite({
105
+ root: projectAbs,
106
+ target: tasksPath,
107
+ data: `${JSON.stringify(tasksTemplate(trimmed), null, 2)}\n`,
108
+ mode: "create",
109
+ });
100
110
  io.writeOut(`OK: Created change proposal at ${base}/\n`);
101
111
  for (const file of ["proposal.xbrief.json", "tasks.xbrief.json", "specs/"]) {
102
112
  io.writeOut(` - ${file}\n`);
@@ -150,8 +160,13 @@ export function runInstallUninstall(projectRoot, io) {
150
160
  const next = filtered.join("");
151
161
  if (next !== original) {
152
162
  try {
153
- assertWriteTargetSafe(resolve(projectRoot), path);
154
- writeFileSync(path, next, "utf8");
163
+ // #2980 wave D: product write sink routes through containedWrite.
164
+ containedWrite({
165
+ root: resolve(projectRoot),
166
+ target: path,
167
+ data: next,
168
+ mode: "replace",
169
+ });
155
170
  io.writeOut("Removed deft entry from AGENTS.md\n");
156
171
  }
157
172
  catch (err) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Deterministic, rule-first tool-event classifier (#2967).
3
+ *
4
+ * Pure: no I/O, no LLM. Misclassification policy: prefer `unknown` over a
5
+ * wrong `verify` (false-positive verify is worse than residual unknown).
6
+ *
7
+ * Distinct from packages/core/src/hooks/classify/ (#2950 write-intent/payload).
8
+ */
9
+ import type { ClassifyToolEventResult, ToolEventInput } from "./types.js";
10
+ /**
11
+ * Classify one tool event into explore | commit | verify | coordinate | unknown.
12
+ *
13
+ * Pure and deterministic. When verify cannot be proven honestly, returns unknown.
14
+ */
15
+ export declare function classifyToolEvent(event: ToolEventInput): ClassifyToolEventResult;
16
+ /** Classify many events; order preserved. */
17
+ export declare function classifyToolEvents(events: readonly ToolEventInput[]): ClassifyToolEventResult[];
18
+ /** Exported for unit tests of the shell path without wrapping Shell tool names. */
19
+ export declare function classifyShellCommandForTest(command: string): ClassifyToolEventResult;
20
+ //# sourceMappingURL=classify.d.ts.map