@deftai/directive-core 0.107.0 → 0.109.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 (271) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/cache/operations.js +1 -1
  3. package/dist/check/cached-orchestrator.js +28 -3
  4. package/dist/check/gate-lists.d.ts +14 -0
  5. package/dist/check/gate-lists.js +26 -3
  6. package/dist/check/named-cause.js +18 -3
  7. package/dist/check/session-completed-ac.d.ts +1 -1
  8. package/dist/check/session-completed-ac.js +1 -1
  9. package/dist/codebase/provider.js +7 -1
  10. package/dist/consumer-check-contract/evaluate.d.ts +47 -0
  11. package/dist/consumer-check-contract/evaluate.js +181 -15
  12. package/dist/delivery-attempt/evaluate.d.ts +9 -1
  13. package/dist/delivery-attempt/evaluate.js +69 -0
  14. package/dist/delivery-attempt/handoff.js +1 -1
  15. package/dist/delivery-attempt/index.d.ts +1 -1
  16. package/dist/delivery-attempt/index.js +1 -1
  17. package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
  18. package/dist/deposit/live-procedure-exclusions.js +110 -0
  19. package/dist/deposit/live-procedure-targets.d.ts +45 -0
  20. package/dist/deposit/live-procedure-targets.js +274 -0
  21. package/dist/deposit/python-free.d.ts +6 -0
  22. package/dist/deposit/python-free.js +15 -0
  23. package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
  24. package/dist/deposit/rewrite-deposit-links.js +148 -0
  25. package/dist/deposit/run-stage-content-pack.d.ts +2 -0
  26. package/dist/deposit/run-stage-content-pack.js +3 -0
  27. package/dist/deposit/stage-content-pack.d.ts +17 -0
  28. package/dist/deposit/stage-content-pack.js +91 -0
  29. package/dist/design-critique/citation-grammar.d.ts +60 -0
  30. package/dist/design-critique/citation-grammar.js +303 -0
  31. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  32. package/dist/design-critique/completed-arc-record.js +277 -0
  33. package/dist/freshness/bind.d.ts +32 -3
  34. package/dist/freshness/bind.js +58 -12
  35. package/dist/hooks/classify/host-session-identity.d.ts +89 -0
  36. package/dist/hooks/classify/host-session-identity.js +467 -0
  37. package/dist/hooks/classify/index.d.ts +3 -2
  38. package/dist/hooks/classify/index.js +3 -2
  39. package/dist/hooks/classify/paths.d.ts +9 -0
  40. package/dist/hooks/classify/paths.js +39 -0
  41. package/dist/hooks/classify/stdin.d.ts +5 -0
  42. package/dist/hooks/classify/stdin.js +94 -1
  43. package/dist/hooks/dest-form.d.ts +20 -1
  44. package/dist/hooks/dest-form.js +158 -21
  45. package/dist/hooks/dispatcher.d.ts +58 -3
  46. package/dist/hooks/dispatcher.js +762 -73
  47. package/dist/hooks/fixtures/cases.d.ts +22 -2
  48. package/dist/hooks/fixtures/cases.js +228 -0
  49. package/dist/hooks/git-destructive-log.d.ts +32 -0
  50. package/dist/hooks/git-destructive-log.js +46 -0
  51. package/dist/hooks/index.d.ts +2 -0
  52. package/dist/hooks/index.js +2 -0
  53. package/dist/hooks/scope.js +3 -1
  54. package/dist/hooks/shell-write-targets.d.ts +10 -0
  55. package/dist/hooks/shell-write-targets.js +274 -0
  56. package/dist/hooks/tools.d.ts +20 -2
  57. package/dist/hooks/tools.js +30 -1
  58. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  59. package/dist/init-deposit/agent-hooks.js +8 -1
  60. package/dist/init-deposit/gitignore.d.ts +7 -0
  61. package/dist/init-deposit/gitignore.js +24 -0
  62. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  63. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  64. package/dist/init-deposit/init-deposit.js +3 -0
  65. package/dist/init-deposit/refresh.js +7 -0
  66. package/dist/init-deposit/runtime-writers.d.ts +14 -0
  67. package/dist/init-deposit/runtime-writers.js +33 -0
  68. package/dist/intake/clause-derivation.js +9 -3
  69. package/dist/intake/issue-ingest.d.ts +6 -1
  70. package/dist/intake/issue-ingest.js +83 -4
  71. package/dist/intake/platform-capabilities.d.ts +9 -2
  72. package/dist/intake/platform-capabilities.js +38 -9
  73. package/dist/intake/reconcile-issues.js +13 -13
  74. package/dist/lifecycle/brief-envelope.d.ts +25 -0
  75. package/dist/lifecycle/brief-envelope.js +42 -0
  76. package/dist/lifecycle/index.d.ts +1 -0
  77. package/dist/lifecycle/index.js +1 -0
  78. package/dist/literal-acceptance/evaluate.js +14 -5
  79. package/dist/literal-acceptance/index.d.ts +1 -1
  80. package/dist/literal-acceptance/index.js +1 -1
  81. package/dist/literal-acceptance/run.d.ts +2 -0
  82. package/dist/literal-acceptance/run.js +19 -1
  83. package/dist/orphan-active/candidate-scope.d.ts +53 -0
  84. package/dist/orphan-active/candidate-scope.js +157 -0
  85. package/dist/orphan-active/evaluate.d.ts +52 -0
  86. package/dist/orphan-active/evaluate.js +250 -128
  87. package/dist/orphan-active/index.d.ts +2 -0
  88. package/dist/orphan-active/index.js +2 -0
  89. package/dist/orphan-active/issue-state.d.ts +121 -0
  90. package/dist/orphan-active/issue-state.js +278 -0
  91. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  92. package/dist/platform/cursor-managed-runtime.js +241 -0
  93. package/dist/platform/index.d.ts +1 -0
  94. package/dist/platform/index.js +1 -0
  95. package/dist/platform/platform-capabilities.d.ts +23 -1
  96. package/dist/platform/platform-capabilities.js +55 -15
  97. package/dist/policy/ceremony-dial.js +5 -9
  98. package/dist/policy/host-hooks.js +4 -9
  99. package/dist/policy/merge-approval-head.js +7 -6
  100. package/dist/policy/org-force-on-migration.js +5 -10
  101. package/dist/policy/plan-extensions.d.ts +14 -2
  102. package/dist/policy/plan-extensions.js +24 -3
  103. package/dist/policy/product-signal.js +5 -10
  104. package/dist/policy/require-human-merge.js +5 -9
  105. package/dist/policy/resolve.js +77 -16
  106. package/dist/policy/value-feedback.js +9 -18
  107. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  108. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  109. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  110. package/dist/pr-closeout-attestable/index.js +2 -0
  111. package/dist/pr-closing-keywords/gh.js +32 -9
  112. package/dist/pr-closing-keywords/main.d.ts +1 -0
  113. package/dist/pr-closing-keywords/main.js +68 -3
  114. package/dist/pr-closing-keywords/types.d.ts +2 -0
  115. package/dist/pr-merge-readiness/gh.js +32 -9
  116. package/dist/pr-protected-issues/gh.d.ts +6 -2
  117. package/dist/pr-protected-issues/gh.js +42 -11
  118. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  119. package/dist/pr-wait-mergeable/cascade.js +21 -1
  120. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  121. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  122. package/dist/pr-wait-mergeable/wrappers.js +14 -3
  123. package/dist/preflight/evaluate.d.ts +15 -0
  124. package/dist/preflight/evaluate.js +29 -3
  125. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  126. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  127. package/dist/product-first-done-gate/evaluate.js +23 -9
  128. package/dist/product-first-done-gate/types.js +2 -0
  129. package/dist/release/cli-drift-report.d.ts +68 -0
  130. package/dist/release/cli-drift-report.js +189 -0
  131. package/dist/release/consumer-hard-stops.d.ts +48 -0
  132. package/dist/release/consumer-hard-stops.js +140 -0
  133. package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
  134. package/dist/release/consumer-readiness-disclosure.js +51 -0
  135. package/dist/release/index.d.ts +3 -0
  136. package/dist/release/index.js +3 -0
  137. package/dist/release/issue-state-fetch.d.ts +5 -3
  138. package/dist/release/issue-state-fetch.js +86 -21
  139. package/dist/release/native-steps.js +1 -0
  140. package/dist/release/pipeline.js +74 -0
  141. package/dist/release/run-consumer-readiness.d.ts +15 -0
  142. package/dist/release/run-consumer-readiness.js +24 -0
  143. package/dist/release/types.d.ts +20 -0
  144. package/dist/render/constants.d.ts +4 -0
  145. package/dist/render/constants.js +11 -8
  146. package/dist/render/export-spec.js +31 -5
  147. package/dist/render/index.d.ts +1 -1
  148. package/dist/render/index.js +1 -1
  149. package/dist/render/prd-render.d.ts +4 -1
  150. package/dist/render/prd-render.js +82 -23
  151. package/dist/render/project-render.js +14 -7
  152. package/dist/render/scope-outlook.d.ts +2 -0
  153. package/dist/render/scope-outlook.js +3 -0
  154. package/dist/render/spec-render.js +2 -2
  155. package/dist/resolution/package-manager.d.ts +33 -4
  156. package/dist/resolution/package-manager.js +210 -9
  157. package/dist/review-monitor/constants.d.ts +7 -0
  158. package/dist/review-monitor/constants.js +7 -0
  159. package/dist/review-monitor/github-lease.d.ts +98 -1
  160. package/dist/review-monitor/github-lease.js +186 -4
  161. package/dist/review-monitor/lease-comment.d.ts +36 -0
  162. package/dist/review-monitor/lease-comment.js +103 -2
  163. package/dist/run-summary/types.d.ts +2 -2
  164. package/dist/scm/build-command.d.ts +2 -2
  165. package/dist/scm/build-command.js +2 -2
  166. package/dist/scm/call-shape.d.ts +25 -0
  167. package/dist/scm/call-shape.js +59 -0
  168. package/dist/scm/call.d.ts +7 -4
  169. package/dist/scm/call.js +48 -9
  170. package/dist/scm/design-critique-chip.d.ts +1 -0
  171. package/dist/scm/design-critique-chip.js +22 -6
  172. package/dist/scm/gh-rest.d.ts +34 -13
  173. package/dist/scm/gh-rest.js +168 -15
  174. package/dist/scm/index.d.ts +2 -0
  175. package/dist/scm/index.js +2 -0
  176. package/dist/scm/readiness.d.ts +2 -0
  177. package/dist/scm/readiness.js +28 -4
  178. package/dist/scm/spawn-status.d.ts +33 -0
  179. package/dist/scm/spawn-status.js +53 -0
  180. package/dist/scope/acceptance-evidence.d.ts +1 -1
  181. package/dist/scope/project-definition-sync.js +14 -7
  182. package/dist/scope/transition.js +4 -14
  183. package/dist/session/ac-pass-banking.d.ts +2 -2
  184. package/dist/session/ac-pass-banking.js +2 -2
  185. package/dist/session/git.d.ts +24 -0
  186. package/dist/session/git.js +138 -2
  187. package/dist/session/host-session-owner.d.ts +53 -0
  188. package/dist/session/host-session-owner.js +89 -0
  189. package/dist/session/index.d.ts +1 -0
  190. package/dist/session/index.js +1 -0
  191. package/dist/session/occupancy.d.ts +259 -13
  192. package/dist/session/occupancy.js +877 -33
  193. package/dist/session/ritual-sentinel.d.ts +5 -0
  194. package/dist/session/ritual-sentinel.js +5 -0
  195. package/dist/session/session-ready.d.ts +5 -1
  196. package/dist/session/session-ready.js +98 -8
  197. package/dist/session/session-start.d.ts +5 -1
  198. package/dist/session/session-start.js +32 -17
  199. package/dist/session/toolchain-preflight.d.ts +13 -3
  200. package/dist/session/toolchain-preflight.js +93 -18
  201. package/dist/session/verify-ac-session-cache.d.ts +2 -2
  202. package/dist/session/verify-ac-session-cache.js +2 -2
  203. package/dist/session/verify-session-ritual.d.ts +11 -0
  204. package/dist/session/verify-session-ritual.js +60 -15
  205. package/dist/slice/constants.d.ts +1 -1
  206. package/dist/spec-authority/constants.d.ts +10 -2
  207. package/dist/spec-authority/constants.js +53 -8
  208. package/dist/spec-authority/resolver.d.ts +3 -0
  209. package/dist/spec-authority/resolver.js +55 -7
  210. package/dist/subprocess/max-buffer.d.ts +14 -0
  211. package/dist/subprocess/max-buffer.js +15 -0
  212. package/dist/swarm/complete-cohort.d.ts +2 -0
  213. package/dist/swarm/complete-cohort.js +21 -13
  214. package/dist/swarm/index.d.ts +1 -0
  215. package/dist/swarm/index.js +1 -0
  216. package/dist/swarm/launch-cli.js +53 -33
  217. package/dist/swarm/launch.d.ts +8 -0
  218. package/dist/swarm/launch.js +63 -43
  219. package/dist/swarm/pre-dispatch-cli.js +2 -1
  220. package/dist/swarm/pre-dispatch.js +10 -1
  221. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  222. package/dist/swarm/subagent-status-dir.js +42 -0
  223. package/dist/swarm/worktrees.d.ts +17 -3
  224. package/dist/swarm/worktrees.js +48 -4
  225. package/dist/triage/evaluate/worktrees.js +153 -6
  226. package/dist/triage/scope/mutations-core.d.ts +2 -6
  227. package/dist/triage/scope/mutations-core.js +23 -55
  228. package/dist/triage/scope-drift/add-ignore.js +5 -39
  229. package/dist/triage/subscribe/index.d.ts +2 -6
  230. package/dist/triage/subscribe/index.js +15 -65
  231. package/dist/triage/welcome/writers.js +15 -74
  232. package/dist/umbrella-current-shape/index.d.ts +51 -3
  233. package/dist/umbrella-current-shape/index.js +106 -18
  234. package/dist/validate-content/deposit-required.d.ts +39 -0
  235. package/dist/validate-content/deposit-required.js +147 -0
  236. package/dist/validate-content/index.d.ts +1 -0
  237. package/dist/validate-content/index.js +1 -0
  238. package/dist/validate-content/validate-links.d.ts +2 -3
  239. package/dist/validate-content/validate-links.js +29 -2
  240. package/dist/vbrief-activate/activate.d.ts +7 -2
  241. package/dist/vbrief-activate/activate.js +26 -13
  242. package/dist/vbrief-build/index.d.ts +2 -1
  243. package/dist/vbrief-build/index.js +2 -1
  244. package/dist/vbrief-build/parity-scenarios.js +5 -5
  245. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  246. package/dist/vbrief-build/project-definition-io.js +357 -31
  247. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  248. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  249. package/dist/vbrief-validate/conformance.d.ts +1 -0
  250. package/dist/vbrief-validate/conformance.js +85 -8
  251. package/dist/vbrief-validate/main.js +2 -0
  252. package/dist/vbrief-validate/precutover.js +5 -10
  253. package/dist/verify-ac/clauses.d.ts +62 -2
  254. package/dist/verify-ac/clauses.js +183 -111
  255. package/dist/verify-ac/evaluate.d.ts +9 -0
  256. package/dist/verify-ac/evaluate.js +32 -9
  257. package/dist/verify-ac/index.d.ts +1 -1
  258. package/dist/verify-ac/index.js +1 -1
  259. package/dist/verify-env/node-runtime.d.ts +8 -4
  260. package/dist/verify-env/node-runtime.js +9 -6
  261. package/dist/verify-env/toolchain-check.d.ts +35 -3
  262. package/dist/verify-env/toolchain-check.js +155 -36
  263. package/dist/verify-source/deposit-closure.d.ts +23 -0
  264. package/dist/verify-source/deposit-closure.js +162 -0
  265. package/dist/verify-source/index.d.ts +2 -0
  266. package/dist/verify-source/index.js +2 -0
  267. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  268. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  269. package/dist/verify-source/semantic-single-source.d.ts +36 -0
  270. package/dist/verify-source/semantic-single-source.js +349 -0
  271. package/package.json +7 -3
@@ -1,12 +1,12 @@
1
1
  /** Host spellings that install-time matchers and runtime classification share. */
2
- export declare const DIRECT_WRITE_TOOL_NAMES: readonly ["Edit", "Write", "WriteFile", "CreateFile", "MultiEdit", "NotebookEdit", "StrReplace", "SearchReplace", "Delete", "DeleteFile", "ApplyPatch", "apply_patch"];
2
+ export declare const DIRECT_WRITE_TOOL_NAMES: readonly ["Edit", "Write", "WriteFile", "CreateFile", "MultiEdit", "NotebookEdit", "StrReplace", "SearchReplace", "Delete", "DeleteFile", "ApplyPatch", "apply_patch", "write", "search_replace"];
3
3
  /** PreToolUse spawn / sub-agent dispatch tools (#1185 / #2437). */
4
4
  export declare const SPAWN_TOOL_NAMES: readonly ["Task", "SubagentStart", "spawn_subagent", "start_agent", "CreateAgent"];
5
5
  /**
6
6
  * Host spellings for Shell/Bash execution tools (#2711).
7
7
  * Used for runtimeAuthority scopes.push / scopes.merge classification.
8
8
  */
9
- export declare const SHELL_TOOL_NAMES: readonly ["Shell", "Bash", "BashTool", "shell", "bash"];
9
+ export declare const SHELL_TOOL_NAMES: readonly ["Shell", "Bash", "BashTool", "shell", "bash", "run_terminal_command"];
10
10
  /** Env override forcing hook-level read-only write denial (#1185). */
11
11
  export declare const READ_ONLY_HOOK_ENV = "DEFT_HOOK_READ_ONLY";
12
12
  export declare function isDirectWriteTool(toolName: string): boolean;
@@ -32,6 +32,24 @@ export declare const MCP_PUSH_MERGE_BARE_NAMES: readonly ["merge_pull_request",
32
32
  export declare const DIRECT_WRITE_HOOK_MATCHER: string;
33
33
  export declare const SPAWN_HOOK_MATCHER: string;
34
34
  export declare const SHELL_HOOK_MATCHER: string;
35
+ /** Known mutation tool names per host. Deposited matchers must cover each name (#3987). */
36
+ export declare const GROK_MUTATION_TOOL_CATALOG: {
37
+ readonly directWrite: readonly ["write", "search_replace"];
38
+ readonly shell: readonly ["run_terminal_command"];
39
+ readonly spawn: readonly ["spawn_subagent"];
40
+ };
41
+ /** Tools on this host that are intentionally not mutation-gated. */
42
+ export declare const GROK_NON_MUTATION_TOOLS: {
43
+ readonly read_file: "read";
44
+ readonly grep: "read";
45
+ readonly list_dir: "read";
46
+ readonly todo_write: "session-local non-product scratch";
47
+ readonly get_command_or_subagent_output: "poll, not a mutation; elapsed bound is evaluateInFlight";
48
+ readonly kill_command_or_subagent: "process control";
49
+ readonly search_tool: "read";
50
+ readonly web_search: "read";
51
+ };
52
+ export declare function matcherHasLiteralToken(matcher: string, toolName: string): boolean;
35
53
  /**
36
54
  * PreToolUse matcher for MCP-class push/merge tools (#2711).
37
55
  * Regex-friendly for Claude/nested hosts. Prefer broad install match +
@@ -12,6 +12,8 @@ export const DIRECT_WRITE_TOOL_NAMES = [
12
12
  "DeleteFile",
13
13
  "ApplyPatch",
14
14
  "apply_patch",
15
+ "write",
16
+ "search_replace",
15
17
  ];
16
18
  /** PreToolUse spawn / sub-agent dispatch tools (#1185 / #2437). */
17
19
  export const SPAWN_TOOL_NAMES = [
@@ -25,7 +27,14 @@ export const SPAWN_TOOL_NAMES = [
25
27
  * Host spellings for Shell/Bash execution tools (#2711).
26
28
  * Used for runtimeAuthority scopes.push / scopes.merge classification.
27
29
  */
28
- export const SHELL_TOOL_NAMES = ["Shell", "Bash", "BashTool", "shell", "bash"];
30
+ export const SHELL_TOOL_NAMES = [
31
+ "Shell",
32
+ "Bash",
33
+ "BashTool",
34
+ "shell",
35
+ "bash",
36
+ "run_terminal_command",
37
+ ];
29
38
  /** Env override forcing hook-level read-only write denial (#1185). */
30
39
  export const READ_ONLY_HOOK_ENV = "DEFT_HOOK_READ_ONLY";
31
40
  function normalizedToolName(toolName) {
@@ -87,6 +96,26 @@ export const MCP_PUSH_MERGE_BARE_NAMES = [
87
96
  export const DIRECT_WRITE_HOOK_MATCHER = DIRECT_WRITE_TOOL_NAMES.join("|");
88
97
  export const SPAWN_HOOK_MATCHER = SPAWN_TOOL_NAMES.join("|");
89
98
  export const SHELL_HOOK_MATCHER = SHELL_TOOL_NAMES.join("|");
99
+ /** Known mutation tool names per host. Deposited matchers must cover each name (#3987). */
100
+ export const GROK_MUTATION_TOOL_CATALOG = {
101
+ directWrite: ["write", "search_replace"],
102
+ shell: ["run_terminal_command"],
103
+ spawn: ["spawn_subagent"],
104
+ };
105
+ /** Tools on this host that are intentionally not mutation-gated. */
106
+ export const GROK_NON_MUTATION_TOOLS = {
107
+ read_file: "read",
108
+ grep: "read",
109
+ list_dir: "read",
110
+ todo_write: "session-local non-product scratch",
111
+ get_command_or_subagent_output: "poll, not a mutation; elapsed bound is evaluateInFlight",
112
+ kill_command_or_subagent: "process control",
113
+ search_tool: "read",
114
+ web_search: "read",
115
+ };
116
+ export function matcherHasLiteralToken(matcher, toolName) {
117
+ return matcher.split("|").includes(toolName);
118
+ }
90
119
  /**
91
120
  * PreToolUse matcher for MCP-class push/merge tools (#2711).
92
121
  * Regex-friendly for Claude/nested hosts. Prefer broad install match +
@@ -1,6 +1,7 @@
1
1
  import type { HookHost } from "../hooks/dispatcher.js";
2
2
  import { type HostHooksPolicy } from "../policy/host-hooks.js";
3
3
  import type { InitDepositIo } from "./constants.js";
4
+ import { type HookRuntimeTravelSeams } from "./hook-runtime-travel.js";
4
5
  export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
5
6
  export declare const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
6
7
  export declare const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
@@ -41,7 +42,7 @@ export interface AgentHookDepositResult {
41
42
  readonly changedPaths: AgentHookPath[];
42
43
  }
43
44
  /** Merge Directive-owned project hook entries without replacing user configuration. */
44
- export declare function writeAgentHookDeposit(projectRoot: string, io?: InitDepositIo, hostHooksPolicy?: HostHooksPolicy): AgentHookDepositResult;
45
+ export declare function writeAgentHookDeposit(projectRoot: string, io?: InitDepositIo, hostHooksPolicy?: HostHooksPolicy, travelSeams?: HookRuntimeTravelSeams): AgentHookDepositResult;
45
46
  /** Read-only registration probe shared by verify and doctor. */
46
47
  export declare function inspectAgentHookDeposit(projectRoot: string, hostHooksPolicy?: HostHooksPolicy): AgentHookInspection[];
47
48
  //# sourceMappingURL=agent-hooks.d.ts.map
@@ -4,6 +4,7 @@ import { assertDepositContained } from "../deposit/contain.js";
4
4
  import { containedRemove, containedRename, containedWrite } from "../fs/contained-write.js";
5
5
  import { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
6
6
  import { isHostHookDepositEnabled, loadHostHooksPolicyFromProject, } from "../policy/host-hooks.js";
7
+ import { inspectHookRuntimeTravel } from "./hook-runtime-travel.js";
7
8
  export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
8
9
  export const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
9
10
  export const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
@@ -252,7 +253,7 @@ function writeJsonIfChanged(projectRoot, path, payload, kind) {
252
253
  return true;
253
254
  }
254
255
  /** Merge Directive-owned project hook entries without replacing user configuration. */
255
- export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefined }, hostHooksPolicy = loadHostHooksPolicyFromProject(projectRoot)) {
256
+ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefined }, hostHooksPolicy = loadHostHooksPolicyFromProject(projectRoot), travelSeams = {}) {
256
257
  const changedPaths = [];
257
258
  const strippedPaths = [];
258
259
  const definitions = [
@@ -332,6 +333,12 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
332
333
  if (changedPaths.length === 0 && strippedPaths.length === 0 && adaptersRemoved === 0) {
333
334
  io.printf("Directive agent hooks already current.\n");
334
335
  }
336
+ // #3785: the registration is trackable and the deposit that implements it is
337
+ // born-ignored, so warn when a clone would inherit the fence without a way to
338
+ // obtain the runtime it names.
339
+ const travel = inspectHookRuntimeTravel(projectRoot, definitions.map((definition) => ({ host: definition.host, path: definition.path })), hostHooksPolicy, travelSeams);
340
+ if (travel.warning !== null)
341
+ io.printf(`${travel.warning}\n`);
335
342
  return {
336
343
  changed: changedPaths.length + strippedPaths.length + adaptersRemoved > 0,
337
344
  changedPaths: [...changedPaths, ...strippedPaths],
@@ -25,6 +25,13 @@ export interface ReconstituteDepositResult {
25
25
  readonly reconstituted: boolean;
26
26
  }
27
27
  export type GitLsFiles = (projectDir: string, paths: readonly string[]) => string | null;
28
+ /**
29
+ * Strip leading/trailing slashes so `.deft/cache/` and `.deft/cache` match.
30
+ * Globs are not expanded: a writer covered only by a glob fails closed (#3612).
31
+ */
32
+ export declare function normalizeGitignorePath(p: string): string;
33
+ export declare function gitignoreRuleCoversPath(rule: string, relPath: string): boolean;
34
+ export declare function ignoreSetCoversPath(rules: readonly string[], relPath: string): boolean;
28
35
  /**
29
36
  * Reports whether `.deft/core` is tracked in git. Returns `null` when git is
30
37
  * unavailable or the tree is not a repository (treated as greenfield).
@@ -31,6 +31,13 @@ export const CANONICAL_GITIGNORE_BASELINE = [
31
31
  ".deft/last-session.json",
32
32
  ".deft/occupancy.json",
33
33
  ".deft/routing.local.json",
34
+ // #3612: remaining runtime writer roots kept at historical names (on-disk
35
+ // contracts). Relocating them would break existing consumer state.
36
+ ".deft/authz/",
37
+ ".deft/delivery-attempts/",
38
+ ".deft/metrics/",
39
+ ".deft/escalations/",
40
+ ".deft/approved-scope/",
34
41
  // Agent-host working state (#3502). Selective, NEVER blanket `.claude/`:
35
42
  // `.claude/settings.json`, `.claude/skills/` and `.claude/commands/` are
36
43
  // MANAGED DEPOSITS (agent-hooks.ts / skill-discovery-hosts.ts) and must stay
@@ -105,6 +112,23 @@ function gitignoreCoversLine(present, line) {
105
112
  }
106
113
  return false;
107
114
  }
115
+ /**
116
+ * Strip leading/trailing slashes so `.deft/cache/` and `.deft/cache` match.
117
+ * Globs are not expanded: a writer covered only by a glob fails closed (#3612).
118
+ */
119
+ export function normalizeGitignorePath(p) {
120
+ return p.replaceAll("\\", "/").replace(/^\/+/, "").replace(/\/+$/, "");
121
+ }
122
+ export function gitignoreRuleCoversPath(rule, relPath) {
123
+ const r = normalizeGitignorePath(stripGitignoreInlineComment(rule));
124
+ const p = normalizeGitignorePath(relPath);
125
+ if (!r || r.startsWith("#"))
126
+ return false;
127
+ return r === p || p.startsWith(`${r}/`);
128
+ }
129
+ export function ignoreSetCoversPath(rules, relPath) {
130
+ return rules.some((rule) => gitignoreRuleCoversPath(rule, relPath));
131
+ }
108
132
  function collectPresentGitignoreLines(existing) {
109
133
  const present = new Set();
110
134
  for (const raw of existing.split("\n")) {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Hook-runtime travel warning (#3785).
3
+ *
4
+ * Directive keeps the agent-hook registration files trackable by design and
5
+ * born-ignores the deposit that implements them. When a registration travels
6
+ * with the tree and nothing in that tree lets a clone obtain `deft-hook`, the
7
+ * registration arrives and the runtime does not. A fresh clone, CI runner, or
8
+ * container without a global install then fail-closes every mutation on an
9
+ * opaque exit 127, and no Directive code runs to explain it.
10
+ *
11
+ * "Travels" means tracked OR trackable-and-not-ignored. A first `deft init`
12
+ * writes the registration unstaged, so a tracked-only probe would go quiet on
13
+ * exactly the run that precedes the commit carrying the fence into every clone.
14
+ *
15
+ * The committed `package.json` dependency on `@deftai/directive` (#2264) is the
16
+ * existing reconstitution anchor. With it a clone can install the runtime the
17
+ * registration names; without it the registration names a command no clone can
18
+ * host. The anchor is read from the commit, not from the working tree or the
19
+ * index: a consumer can commit the generated registration and leave the
20
+ * manifest merely present, staged, or edited, and that is the case that strands
21
+ * the clone.
22
+ *
23
+ * Warn-only. Refusing `deft init` / `deft update` on this condition would be
24
+ * the same lockout from the other side, so the deposit still writes and
25
+ * reports. Absence of the runtime stays a deny — fail-open-on-absence was
26
+ * refuted as unimplementable and as a bypass primitive (#3156).
27
+ *
28
+ * Refs #3785, #3736, #3571, #2752, #2264, #3156.
29
+ */
30
+ import { type HookHost, type HostHooksPolicy } from "../policy/host-hooks.js";
31
+ import { type PinReadResult } from "../resolution/pin.js";
32
+ /**
33
+ * Hosts whose registration is deposited `failClosed: true`. Only these turn a
34
+ * missing runtime into a denial; the rest fail open and merely lose the gate.
35
+ */
36
+ export declare const FAIL_CLOSED_HOOK_HOSTS: readonly HookHost[];
37
+ /** Manifest carrying the committed runtime anchor. */
38
+ export declare const RUNTIME_ANCHOR_MANIFEST = "package.json";
39
+ /**
40
+ * Spec prefixes that name a location instead of a release. Each resolves
41
+ * against something outside the tree -- a path, a symlink target, a workspace
42
+ * member -- so the clone that receives the registration cannot install from it.
43
+ */
44
+ export declare const LOCATION_SPEC_PREFIXES: readonly ["file:", "link:", "portal:", "workspace:", "git+file:"];
45
+ export type GitLsFilesProbe = (projectDir: string, paths: readonly string[]) => string | null;
46
+ /** Contents of a path as committed at `HEAD`, or null when there is none. */
47
+ export type CommittedFileProbe = (projectDir: string, path: string) => string | null;
48
+ export interface HookRuntimeTravelSeams {
49
+ readonly gitLsFiles?: GitLsFilesProbe;
50
+ readonly readCommittedFile?: CommittedFileProbe;
51
+ readonly readPin?: (projectRoot: string) => PinReadResult;
52
+ }
53
+ /** One deposited registration considered by the probe. */
54
+ export interface HookRegistrationRef {
55
+ readonly host: HookHost;
56
+ readonly path: string;
57
+ }
58
+ export interface HookRuntimeTravelResult {
59
+ /** Enabled registrations that travel with this repository. */
60
+ readonly travelingRegistrations: readonly string[];
61
+ /** Subset of {@link travelingRegistrations} deposited fail-closed. */
62
+ readonly failClosedRegistrations: readonly string[];
63
+ /** A committed manifest declares `@deftai/directive`, so a clone can install it. */
64
+ readonly runtimeTravels: boolean;
65
+ /** Null when the pairing is safe or git could not answer. */
66
+ readonly warning: string | null;
67
+ }
68
+ /**
69
+ * Report whether any enabled agent-hook registration travels with this tree
70
+ * while no manifest travelling alongside it lets a clone install the runtime it
71
+ * names.
72
+ *
73
+ * Disabled hosts are excluded: their registration is stripped, so a leftover
74
+ * copy cannot deny anything.
75
+ */
76
+ export declare function inspectHookRuntimeTravel(projectRoot: string, registrations: readonly HookRegistrationRef[], hostHooksPolicy: HostHooksPolicy, seams?: HookRuntimeTravelSeams): HookRuntimeTravelResult;
77
+ //# sourceMappingURL=hook-runtime-travel.d.ts.map
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Hook-runtime travel warning (#3785).
3
+ *
4
+ * Directive keeps the agent-hook registration files trackable by design and
5
+ * born-ignores the deposit that implements them. When a registration travels
6
+ * with the tree and nothing in that tree lets a clone obtain `deft-hook`, the
7
+ * registration arrives and the runtime does not. A fresh clone, CI runner, or
8
+ * container without a global install then fail-closes every mutation on an
9
+ * opaque exit 127, and no Directive code runs to explain it.
10
+ *
11
+ * "Travels" means tracked OR trackable-and-not-ignored. A first `deft init`
12
+ * writes the registration unstaged, so a tracked-only probe would go quiet on
13
+ * exactly the run that precedes the commit carrying the fence into every clone.
14
+ *
15
+ * The committed `package.json` dependency on `@deftai/directive` (#2264) is the
16
+ * existing reconstitution anchor. With it a clone can install the runtime the
17
+ * registration names; without it the registration names a command no clone can
18
+ * host. The anchor is read from the commit, not from the working tree or the
19
+ * index: a consumer can commit the generated registration and leave the
20
+ * manifest merely present, staged, or edited, and that is the case that strands
21
+ * the clone.
22
+ *
23
+ * Warn-only. Refusing `deft init` / `deft update` on this condition would be
24
+ * the same lockout from the other side, so the deposit still writes and
25
+ * reports. Absence of the runtime stays a deny — fail-open-on-absence was
26
+ * refuted as unimplementable and as a bypass primitive (#3156).
27
+ *
28
+ * Refs #3785, #3736, #3571, #2752, #2264, #3156.
29
+ */
30
+ import { execFileSync } from "node:child_process";
31
+ import { disableHostHooksInvocation, isHostHookDepositEnabled, } from "../policy/host-hooks.js";
32
+ import { PIN_DEPENDENCY_NAME, readPin } from "../resolution/pin.js";
33
+ /**
34
+ * Hosts whose registration is deposited `failClosed: true`. Only these turn a
35
+ * missing runtime into a denial; the rest fail open and merely lose the gate.
36
+ */
37
+ export const FAIL_CLOSED_HOOK_HOSTS = ["cursor"];
38
+ /** Manifest carrying the committed runtime anchor. */
39
+ export const RUNTIME_ANCHOR_MANIFEST = "package.json";
40
+ /**
41
+ * Spec prefixes that name a location instead of a release. Each resolves
42
+ * against something outside the tree -- a path, a symlink target, a workspace
43
+ * member -- so the clone that receives the registration cannot install from it.
44
+ */
45
+ export const LOCATION_SPEC_PREFIXES = [
46
+ "file:",
47
+ "link:",
48
+ "portal:",
49
+ "workspace:",
50
+ "git+file:",
51
+ ];
52
+ /** Prefixes any clone can fetch: a registry alias or a remote repository. */
53
+ const REMOTE_SPEC_PREFIXES = [
54
+ "npm:",
55
+ "http:",
56
+ "https:",
57
+ "git:",
58
+ "git+http:",
59
+ "git+https:",
60
+ "git+ssh:",
61
+ "github:",
62
+ "gitlab:",
63
+ "bitbucket:",
64
+ ];
65
+ /**
66
+ * Whether a clone could install `deft-hook` from this spec.
67
+ *
68
+ * Allowlist, not denylist. Location forms are open-ended -- `file:`,
69
+ * `git+file:`, `../directive`, `/opt/directive`, `C:\src\directive`, `~/dev` --
70
+ * so enumerating them leaves the next shape uncovered, and an uncovered shape
71
+ * silences the warning. Anything that is not recognisably a registry release or
72
+ * a fetchable remote is therefore treated as not travelling. A bare
73
+ * `owner/repo` shorthand is refused on the same rule: it warns where the
74
+ * runtime might in fact arrive, which is the harmless direction for a
75
+ * warn-only probe.
76
+ */
77
+ function reconstitutesForAClone(rawSpec) {
78
+ const spec = rawSpec.trim();
79
+ if (spec.length === 0)
80
+ return false;
81
+ const lower = spec.toLowerCase();
82
+ if (LOCATION_SPEC_PREFIXES.some((prefix) => lower.startsWith(prefix)))
83
+ return false;
84
+ if (REMOTE_SPEC_PREFIXES.some((prefix) => lower.startsWith(prefix)))
85
+ return true;
86
+ // Bare paths: relative, home-anchored, POSIX-absolute, or a Windows drive.
87
+ if (/^[.~/\\]/.test(spec) || /^[a-z]:[\\/]/i.test(spec))
88
+ return false;
89
+ // Anything else with a separator is a path or a repository shorthand, not a
90
+ // version, a range, or a dist-tag.
91
+ return !(spec.includes("/") || spec.includes("\\"));
92
+ }
93
+ function defaultGitLsFiles(projectDir, paths) {
94
+ try {
95
+ // Ask git rather than looking for `.git`: a project deposited into a
96
+ // subdirectory of a repository has no `.git` of its own, yet its
97
+ // registration is trackable by the parent. `ls-files` answers from any
98
+ // depth, relative to this directory, and declines outside a repository.
99
+ //
100
+ // `--cached` reports what is in the index, `--others --exclude-standard`
101
+ // what a first `deft init` just wrote and the next `git add` would sweep
102
+ // in; ignored paths are omitted, since they cannot reach a clone.
103
+ return execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "--", ...paths], {
104
+ cwd: projectDir,
105
+ encoding: "utf8",
106
+ stdio: ["ignore", "pipe", "ignore"],
107
+ windowsHide: true,
108
+ });
109
+ }
110
+ catch {
111
+ // git absent, or not a repository: whether these paths travel has no answer
112
+ // here, so the caller stays silent rather than guessing.
113
+ return null;
114
+ }
115
+ }
116
+ /** Paths that travel, as reported by git, or null when git could not answer. */
117
+ function travelingPaths(output) {
118
+ if (output === null)
119
+ return null;
120
+ return new Set(output
121
+ .split("\n")
122
+ .map((line) => line.trim())
123
+ .filter((line) => line.length > 0));
124
+ }
125
+ function defaultReadCommittedFile(projectDir, path) {
126
+ try {
127
+ // `HEAD:./<path>` resolves relative to this directory, so a project nested
128
+ // in a repository reads its own manifest rather than the parent's.
129
+ return execFileSync("git", ["show", `HEAD:./${path}`], {
130
+ cwd: projectDir,
131
+ encoding: "utf8",
132
+ stdio: ["ignore", "pipe", "ignore"],
133
+ windowsHide: true,
134
+ });
135
+ }
136
+ catch {
137
+ // No commit, no such path in it, or no repository: nothing is committed
138
+ // here that a clone would receive.
139
+ return null;
140
+ }
141
+ }
142
+ /** The `@deftai/directive` spec as committed, or null when none is. */
143
+ function committedRuntimeSpec(manifest) {
144
+ if (manifest === null)
145
+ return null;
146
+ let parsed;
147
+ try {
148
+ parsed = JSON.parse(manifest);
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ if (typeof parsed !== "object" || parsed === null)
154
+ return null;
155
+ const pkg = parsed;
156
+ for (const block of ["devDependencies", "dependencies"]) {
157
+ const deps = pkg[block];
158
+ if (typeof deps !== "object" || deps === null || Array.isArray(deps))
159
+ continue;
160
+ const spec = deps[PIN_DEPENDENCY_NAME];
161
+ if (typeof spec === "string" && spec.trim().length > 0)
162
+ return spec.trim();
163
+ }
164
+ return null;
165
+ }
166
+ function isFailClosed(host) {
167
+ return FAIL_CLOSED_HOOK_HOSTS.includes(host);
168
+ }
169
+ function recoveryHosts(failClosed) {
170
+ const hosts = [...new Set(failClosed.map((entry) => entry.host))];
171
+ return hosts
172
+ .map((host) => `\`${disableHostHooksInvocation(` --host ${host} --confirm`)}\``)
173
+ .join(" / ");
174
+ }
175
+ function causeLine(anchor) {
176
+ if (anchor.kind === "uncommitted") {
177
+ return ` ${RUNTIME_ANCHOR_MANIFEST} declares ${PIN_DEPENDENCY_NAME} but is not committed, so a fresh`;
178
+ }
179
+ if (anchor.kind === "non-portable") {
180
+ return ` ${RUNTIME_ANCHOR_MANIFEST} pins ${PIN_DEPENDENCY_NAME} to \`${anchor.spec}\`, which no clone can install, so a fresh`;
181
+ }
182
+ return ` No ${RUNTIME_ANCHOR_MANIFEST} dependency on ${PIN_DEPENDENCY_NAME} travels with this tree, so a fresh`;
183
+ }
184
+ function fixLine(anchor) {
185
+ if (anchor.kind === "uncommitted") {
186
+ return ` Fix: commit ${RUNTIME_ANCHOR_MANIFEST} so the runtime travels with the registration.`;
187
+ }
188
+ if (anchor.kind === "non-portable") {
189
+ return ` Fix: pin ${PIN_DEPENDENCY_NAME} to a published version so a clone can install it.`;
190
+ }
191
+ return ` Fix: commit a ${RUNTIME_ANCHOR_MANIFEST} dependency on ${PIN_DEPENDENCY_NAME} so the runtime travels too.`;
192
+ }
193
+ /**
194
+ * Judge the anchor from the commit, not the working tree or the index.
195
+ *
196
+ * A clone receives commits. A manifest that is merely present, merely staged,
197
+ * or edited-but-uncommitted declares the runtime to this machine only, and the
198
+ * consumer is free to commit the generated registration without it.
199
+ */
200
+ function resolveAnchorState(committedSpec, workingSpec) {
201
+ if (committedSpec === null) {
202
+ return workingSpec === null ? { kind: "absent" } : { kind: "uncommitted" };
203
+ }
204
+ if (!reconstitutesForAClone(committedSpec)) {
205
+ return { kind: "non-portable", spec: committedSpec };
206
+ }
207
+ return { kind: "travels" };
208
+ }
209
+ function buildWarning(traveling, failClosed, anchor) {
210
+ const listed = traveling
211
+ .map((entry) => `${entry.path}${isFailClosed(entry.host) ? " (fail-closed)" : ""}`)
212
+ .join(", ");
213
+ const cause = causeLine(anchor);
214
+ const lines = [
215
+ `\u26a0 Hook registration travels without its runtime (#3785): ${listed}`,
216
+ cause,
217
+ " clone, CI runner, or container cannot obtain the `deft-hook` command these files name.",
218
+ ];
219
+ if (failClosed.length > 0) {
220
+ lines.push(" Fail-closed hosts then deny every mutation on an opaque exit 127, and no Directive", " code runs to say why. There is no in-session recovery on a host without Node.");
221
+ }
222
+ lines.push(fixLine(anchor));
223
+ if (failClosed.length > 0) {
224
+ lines.push(` Or, accepting the capability cost: ${recoveryHosts(failClosed)}.`);
225
+ }
226
+ lines.push(" \u2297 Do not hand-edit `failClosed` in the deposited file -- the next `deft update` restores it.", " Detail: .deft/core/docs/hook-runtime-unavailable.md");
227
+ return lines.join("\n");
228
+ }
229
+ /**
230
+ * Report whether any enabled agent-hook registration travels with this tree
231
+ * while no manifest travelling alongside it lets a clone install the runtime it
232
+ * names.
233
+ *
234
+ * Disabled hosts are excluded: their registration is stripped, so a leftover
235
+ * copy cannot deny anything.
236
+ */
237
+ export function inspectHookRuntimeTravel(projectRoot, registrations, hostHooksPolicy, seams = {}) {
238
+ const enabled = registrations.filter((entry) => isHostHookDepositEnabled(entry.host, hostHooksPolicy));
239
+ const probePaths = [...enabled.map((entry) => entry.path), RUNTIME_ANCHOR_MANIFEST];
240
+ const gitLsFiles = seams.gitLsFiles ?? defaultGitLsFiles;
241
+ const traveling = travelingPaths(gitLsFiles(projectRoot, probePaths));
242
+ if (traveling === null) {
243
+ return {
244
+ travelingRegistrations: [],
245
+ failClosedRegistrations: [],
246
+ runtimeTravels: false,
247
+ warning: null,
248
+ };
249
+ }
250
+ // The two sides are judged asymmetrically, deliberately. A registration is
251
+ // dangerous as soon as it is trackable: one `git add` sends it to every
252
+ // clone. An anchor counts only once a commit carries it, because a clone
253
+ // receives commits -- present, staged, and edited-but-uncommitted manifests
254
+ // all declare the runtime to this machine alone.
255
+ const readCommittedFile = seams.readCommittedFile ?? defaultReadCommittedFile;
256
+ const committedSpec = committedRuntimeSpec(readCommittedFile(projectRoot, RUNTIME_ANCHOR_MANIFEST));
257
+ const anchor = resolveAnchorState(committedSpec, (seams.readPin ?? readPin)(projectRoot).rawSpec);
258
+ const runtimeTravels = anchor.kind === "travels";
259
+ const travelingRegistrations = enabled.filter((entry) => traveling.has(entry.path));
260
+ const failClosedRegistrations = travelingRegistrations.filter((entry) => isFailClosed(entry.host));
261
+ const unsafe = travelingRegistrations.length > 0 && !runtimeTravels;
262
+ return {
263
+ travelingRegistrations: travelingRegistrations.map((entry) => entry.path),
264
+ failClosedRegistrations: failClosedRegistrations.map((entry) => entry.path),
265
+ runtimeTravels,
266
+ warning: unsafe ? buildWarning(travelingRegistrations, failClosedRegistrations, anchor) : null,
267
+ };
268
+ }
269
+ //# sourceMappingURL=hook-runtime-travel.js.map
@@ -10,6 +10,7 @@ import { homedir, platform } from "node:os";
10
10
  import { basename, join, resolve } from "node:path";
11
11
  import { assertDepositContained } from "../deposit/contain.js";
12
12
  import { copyTree } from "../deposit/copy-tree.js";
13
+ import { assertLiveProcedureDepositClean } from "../deposit/live-procedure-targets.js";
13
14
  import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
14
15
  import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
15
16
  import { readCorePackageVersion } from "../engine-version.js";
@@ -173,8 +174,10 @@ export async function runInitDeposit(args, io, seams = {}) {
173
174
  const resolveContent = seams.resolveContentRoot ?? resolveInstalledContentRoot;
174
175
  const copyContent = seams.copyContent ?? copyTree;
175
176
  const contentRoot = await resolveContent();
177
+ assertLiveProcedureDepositClean(contentRoot);
176
178
  await reconstituteDepositFromContent(contentRoot, deftDir, copyContent);
177
179
  await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
180
+ assertLiveProcedureDepositClean(deftDir);
178
181
  ensureInitGitignoreLines(projectDir, io);
179
182
  ensurePrettierIgnoreLines(projectDir, io);
180
183
  const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
@@ -13,6 +13,7 @@ import { platform as osPlatform } from "node:os";
13
13
  import { join, resolve } from "node:path";
14
14
  import { assertDepositContained } from "../deposit/contain.js";
15
15
  import { replaceTree } from "../deposit/copy-tree.js";
16
+ import { assertLiveProcedureDepositClean } from "../deposit/live-procedure-targets.js";
16
17
  import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
17
18
  import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
18
19
  import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
@@ -476,9 +477,12 @@ export async function runRefreshDeposit(args, io, seams = {}) {
476
477
  const strategy = alreadyCurrent ? "no-op" : "file-swap";
477
478
  if (alreadyCurrent) {
478
479
  io.printf("[deft update] Framework payload already current; skipping payload copy.\n");
480
+ // C3 even when VERSION matches: skip-copy is not skip-validation (#3602).
481
+ assertLiveProcedureDepositClean(contentRoot);
479
482
  // #2913: still fail-closed reconcile so dst-only leftovers cannot linger when
480
483
  // VERSION already matches (e.g. pre-#2804 additive deposits). Does not re-stamp.
481
484
  await reconcileDepositToContentPackage(deftDir, contentRoot, io);
485
+ assertLiveProcedureDepositClean(deftDir);
482
486
  migrateLegacyInstallManifest(projectDir, join(deftDir, "VERSION"));
483
487
  // #3117: ensure a readable live generation token exists without advancing
484
488
  // when the payload did not swap (already-current). stampLiveGeneration is a
@@ -504,8 +508,11 @@ export async function runRefreshDeposit(args, io, seams = {}) {
504
508
  }
505
509
  else {
506
510
  // Full-tree replace (or injected seam). Additive copy is no longer the default.
511
+ // C3 the incoming package BEFORE replace so a reject cannot leave a broken deposit.
512
+ assertLiveProcedureDepositClean(contentRoot);
507
513
  await copyContent(contentRoot, deftDir);
508
514
  await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
515
+ assertLiveProcedureDepositClean(deftDir);
509
516
  // #2913 / #2804 / #2347: fail-closed delete-not-in-source BEFORE VERSION stamp.
510
517
  // replaceTree already drops dst-only paths; reconcile verifies and covers
511
518
  // additive seams. Throws => no VERSION rewrite (refuse stamp until clean).
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Runtime writer paths that land in a consumer working tree (#3612).
3
+ *
4
+ * The ignore baselines and this list are coupled: every exported writer path
5
+ * constant MUST appear here, and a fail-closed test asserts the ignore set
6
+ * covers each entry on both shipped surfaces. New writers join this registry;
7
+ * they do not get a silent extra .deft/ name.
8
+ */
9
+ /**
10
+ * Imported writer path constants. Order is the lockstep contract with
11
+ * runtime-writer-paths.json (Go reads that file).
12
+ */
13
+ export declare const RUNTIME_WRITER_PATHS: readonly string[];
14
+ //# sourceMappingURL=runtime-writers.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Runtime writer paths that land in a consumer working tree (#3612).
3
+ *
4
+ * The ignore baselines and this list are coupled: every exported writer path
5
+ * constant MUST appear here, and a fail-closed test asserts the ignore set
6
+ * covers each entry on both shipped surfaces. New writers join this registry;
7
+ * they do not get a silent extra .deft/ name.
8
+ */
9
+ import { AUTHZ_DIR } from "../authz/types.js";
10
+ import { DEFAULT_TASK_CACHE_ROOT } from "../cache/task-cache/constants.js";
11
+ import { SESSION_COMPLETED_MARKER_REL } from "../check/session-completed-ac.js";
12
+ import { DELIVERY_ATTEMPT_DIR } from "../delivery-attempt/types.js";
13
+ import { ESCALATION_DIR } from "../escalation/types.js";
14
+ import { PROJECT_LOCAL_METRICS_DIR } from "../metrics/resolve-metrics-home.js";
15
+ import { APPROVED_SCOPE_DIR } from "../scope-provenance/digest.js";
16
+ import { AC_PASS_BANK_DIR } from "../session/ac-pass-banking.js";
17
+ import { VERIFY_AC_SESSION_CACHE_DIR } from "../session/verify-ac-session-cache.js";
18
+ /**
19
+ * Imported writer path constants. Order is the lockstep contract with
20
+ * runtime-writer-paths.json (Go reads that file).
21
+ */
22
+ export const RUNTIME_WRITER_PATHS = Object.freeze([
23
+ AC_PASS_BANK_DIR,
24
+ VERIFY_AC_SESSION_CACHE_DIR,
25
+ SESSION_COMPLETED_MARKER_REL.join("/"),
26
+ AUTHZ_DIR,
27
+ DELIVERY_ATTEMPT_DIR,
28
+ PROJECT_LOCAL_METRICS_DIR,
29
+ ESCALATION_DIR,
30
+ APPROVED_SCOPE_DIR,
31
+ DEFAULT_TASK_CACHE_ROOT,
32
+ ]);
33
+ //# sourceMappingURL=runtime-writers.js.map