@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
@@ -4,20 +4,27 @@
4
4
  * Cases are pure data: raw stdin or structured payload → expected classification.
5
5
  * Core tests and CLI hook-dispatch tests import this module (shared corpus collapse).
6
6
  */
7
+ import type { ExactLifecycleVerb, HookHostIdentityStatus } from "../classify/host-session-identity.js";
7
8
  import type { ClassifyHookHost, HookWriteIntent } from "../classify/types.js";
8
9
  export type FixtureOs = "win32" | "posix";
9
- export type FixtureToolFamily = "Write" | "ApplyPatch" | "Task" | "StrReplace" | "other";
10
+ export type FixtureToolFamily = "Write" | "ApplyPatch" | "Task" | "StrReplace" | "Shell" | "other";
10
11
  export interface HookFixtureCase {
11
12
  readonly id: string;
12
13
  readonly host: ClassifyHookHost;
13
14
  readonly os: FixtureOs;
14
15
  readonly tool: FixtureToolFamily;
15
- /** Closed-issue tags for the regression class this case freezes. */
16
+ /** Issue/regression tags for the behavior class this case freezes. */
16
17
  readonly regression: readonly string[];
17
18
  /** Structured payload when the host sends JSON. */
18
19
  readonly payload?: unknown;
19
20
  /** Raw stdin when testing BOM / free-form ApplyPatch parse. */
20
21
  readonly raw?: string;
22
+ /**
23
+ * Hook process environment for `host-env` identity providers (#3873).
24
+ * Identity assertions resolve against this instead of the ambient
25
+ * environment, so a corpus case cannot pass or fail by where it runs.
26
+ */
27
+ readonly environ?: Readonly<Record<string, string>>;
21
28
  readonly expected: {
22
29
  readonly toolName: string | null;
23
30
  readonly writeIntent: HookWriteIntent;
@@ -25,6 +32,19 @@ export interface HookFixtureCase {
25
32
  /** When raw is set: after parseHookStdin. */
26
33
  readonly stdinEmpty?: boolean;
27
34
  readonly parseFailed?: boolean;
35
+ /** Cooperative payload owner resolution expected for #3611 cases. */
36
+ readonly hostIdentity?: {
37
+ readonly status: HookHostIdentityStatus;
38
+ readonly sessionId: string | null;
39
+ };
40
+ /** Exact lifecycle classification/rewrite expected for #3611 shell cases. */
41
+ readonly lifecycle?: {
42
+ readonly verb: ExactLifecycleVerb | null;
43
+ readonly requestedSessionId: string;
44
+ readonly resultKind: "rewrite" | "conflict" | null;
45
+ readonly rewrittenCommand?: string;
46
+ readonly updatedInput?: Readonly<Record<string, unknown>>;
47
+ };
28
48
  };
29
49
  readonly notes?: string;
30
50
  }
@@ -527,6 +527,234 @@ export const HOOK_FIXTURE_CASES = [
527
527
  writeTargetPath: "C:\\Repos\\proj\\src\\edit-me.ts",
528
528
  },
529
529
  },
530
+ // --- Cooperative host-session identity and exact lifecycle rewrite (#3611) ---
531
+ {
532
+ id: "codex-posix-shell-lifecycle-rewrite",
533
+ host: "codex",
534
+ os: "posix",
535
+ tool: "Shell",
536
+ regression: ["#3611"],
537
+ payload: {
538
+ tool_name: "Bash",
539
+ session_id: "session-a",
540
+ tool_input: {
541
+ command: "deft session:start --rearm",
542
+ description: "Re-arm the mutation session",
543
+ },
544
+ },
545
+ expected: {
546
+ toolName: "Bash",
547
+ writeIntent: "shell",
548
+ writeTargetPath: null,
549
+ hostIdentity: {
550
+ status: "ok",
551
+ sessionId: "host:codex:v1:c2Vzc2lvbi1h",
552
+ },
553
+ lifecycle: {
554
+ verb: "session:start",
555
+ requestedSessionId: "host:codex:v1:c2Vzc2lvbi1h",
556
+ resultKind: "rewrite",
557
+ rewrittenCommand: "deft session:start --rearm --session-id=host:codex:v1:c2Vzc2lvbi1h",
558
+ updatedInput: {
559
+ command: "deft session:start --rearm --session-id=host:codex:v1:c2Vzc2lvbi1h",
560
+ description: "Re-arm the mutation session",
561
+ },
562
+ },
563
+ },
564
+ },
565
+ {
566
+ id: "codex-posix-shell-lifecycle-owner-conflict",
567
+ host: "codex",
568
+ os: "posix",
569
+ tool: "Shell",
570
+ regression: ["#3611"],
571
+ payload: {
572
+ tool_name: "Bash",
573
+ session_id: "session-a",
574
+ tool_input: {
575
+ command: "deft session:start --session-id=host:codex:v1:b3RoZXI",
576
+ },
577
+ },
578
+ expected: {
579
+ toolName: "Bash",
580
+ writeIntent: "shell",
581
+ writeTargetPath: null,
582
+ hostIdentity: {
583
+ status: "ok",
584
+ sessionId: "host:codex:v1:c2Vzc2lvbi1h",
585
+ },
586
+ lifecycle: {
587
+ verb: "session:start",
588
+ requestedSessionId: "host:codex:v1:c2Vzc2lvbi1h",
589
+ resultKind: "conflict",
590
+ },
591
+ },
592
+ },
593
+ {
594
+ id: "codex-posix-shell-lifecycle-ambiguous",
595
+ host: "codex",
596
+ os: "posix",
597
+ tool: "Shell",
598
+ regression: ["#3611"],
599
+ payload: {
600
+ tool_name: "Bash",
601
+ session_id: "session-a",
602
+ tool_input: { command: "deft session:start && echo unsafe" },
603
+ },
604
+ expected: {
605
+ toolName: "Bash",
606
+ writeIntent: "shell",
607
+ writeTargetPath: null,
608
+ hostIdentity: {
609
+ status: "ok",
610
+ sessionId: "host:codex:v1:c2Vzc2lvbi1h",
611
+ },
612
+ lifecycle: {
613
+ verb: null,
614
+ requestedSessionId: "host:codex:v1:c2Vzc2lvbi1h",
615
+ resultKind: null,
616
+ },
617
+ },
618
+ },
619
+ {
620
+ id: "cursor-posix-shell-identity-conflict",
621
+ host: "cursor",
622
+ os: "posix",
623
+ tool: "Shell",
624
+ regression: ["#3611"],
625
+ payload: {
626
+ tool_name: "Shell",
627
+ conversation_id: "conversation-a",
628
+ session_id: "conversation-b",
629
+ tool_input: { command: "task session:ready" },
630
+ },
631
+ expected: {
632
+ toolName: "Shell",
633
+ writeIntent: "shell",
634
+ writeTargetPath: null,
635
+ hostIdentity: { status: "conflict", sessionId: null },
636
+ },
637
+ },
638
+ {
639
+ // #3599 item 5: the refresh verb is only useful if the id it renews is the
640
+ // one later hook processes compare against. #3611's bridge supplies that.
641
+ id: "cursor-posix-shell-occupancy-heartbeat-rewrite",
642
+ host: "cursor",
643
+ os: "posix",
644
+ tool: "Shell",
645
+ regression: ["#3599", "#3611"],
646
+ payload: {
647
+ tool_name: "Bash",
648
+ conversation_id: "session-a",
649
+ tool_input: { command: "task occupancy:heartbeat" },
650
+ },
651
+ expected: {
652
+ toolName: "Bash",
653
+ writeIntent: "shell",
654
+ writeTargetPath: null,
655
+ hostIdentity: {
656
+ status: "ok",
657
+ sessionId: "host:cursor:v1:c2Vzc2lvbi1h",
658
+ },
659
+ lifecycle: {
660
+ verb: "occupancy:heartbeat",
661
+ requestedSessionId: "host:cursor:v1:c2Vzc2lvbi1h",
662
+ resultKind: "rewrite",
663
+ rewrittenCommand: "task occupancy:heartbeat -- --session-id=host:cursor:v1:c2Vzc2lvbi1h",
664
+ updatedInput: {
665
+ command: "task occupancy:heartbeat -- --session-id=host:cursor:v1:c2Vzc2lvbi1h",
666
+ },
667
+ },
668
+ },
669
+ },
670
+ {
671
+ id: "claude-posix-shell-identity-missing",
672
+ host: "claude",
673
+ os: "posix",
674
+ tool: "Shell",
675
+ regression: ["#3611"],
676
+ payload: {
677
+ tool_name: "Bash",
678
+ tool_input: { command: "deft occupancy:release" },
679
+ },
680
+ expected: {
681
+ toolName: "Bash",
682
+ writeIntent: "shell",
683
+ writeTargetPath: null,
684
+ hostIdentity: { status: "missing", sessionId: null },
685
+ },
686
+ },
687
+ {
688
+ id: "claude-posix-shell-identity-malformed",
689
+ host: "claude",
690
+ os: "posix",
691
+ tool: "Shell",
692
+ regression: ["#3611"],
693
+ payload: {
694
+ tool_name: "Bash",
695
+ session_id: " malformed ",
696
+ tool_input: { command: "deft occupancy:release" },
697
+ },
698
+ expected: {
699
+ toolName: "Bash",
700
+ writeIntent: "shell",
701
+ writeTargetPath: null,
702
+ hostIdentity: { status: "invalid", sessionId: null },
703
+ },
704
+ },
705
+ // --- Host-env session identity (#3873) ---
706
+ {
707
+ // The claim side of the loop: without this rewrite the lease is claimed
708
+ // under a minted UUID that the later write gate can never present.
709
+ id: "grok-posix-shell-host-env-lifecycle-rewrite",
710
+ host: "grok",
711
+ os: "posix",
712
+ tool: "Shell",
713
+ regression: ["#3873", "#3611"],
714
+ payload: {
715
+ tool_name: "Bash",
716
+ tool_input: { command: "deft session:start" },
717
+ },
718
+ environ: { GROK_SESSION_ID: "grok-session-a" },
719
+ expected: {
720
+ toolName: "Bash",
721
+ writeIntent: "shell",
722
+ writeTargetPath: null,
723
+ hostIdentity: {
724
+ status: "ok",
725
+ sessionId: "host:grok:v1:Z3Jvay1zZXNzaW9uLWE",
726
+ },
727
+ lifecycle: {
728
+ verb: "session:start",
729
+ requestedSessionId: "host:grok:v1:Z3Jvay1zZXNzaW9uLWE",
730
+ resultKind: "rewrite",
731
+ rewrittenCommand: "deft session:start --session-id=host:grok:v1:Z3Jvay1zZXNzaW9uLWE",
732
+ updatedInput: {
733
+ command: "deft session:start --session-id=host:grok:v1:Z3Jvay1zZXNzaW9uLWE",
734
+ },
735
+ },
736
+ },
737
+ },
738
+ {
739
+ id: "grok-posix-write-payload-session-id-untrusted",
740
+ host: "grok",
741
+ os: "posix",
742
+ tool: "Write",
743
+ regression: ["#3873"],
744
+ payload: {
745
+ tool_name: "Write",
746
+ session_id: "payload-owner",
747
+ tool_input: { content: "x", file_path: "src/app.ts" },
748
+ },
749
+ environ: {},
750
+ expected: {
751
+ toolName: "Write",
752
+ writeIntent: "direct-write",
753
+ writeTargetPath: "src/app.ts",
754
+ hostIdentity: { status: "missing", sessionId: null },
755
+ },
756
+ notes: "Grok's payload session_id contract is unverified; only the host variable is read",
757
+ },
530
758
  // --- Regression: empty / missing tool name ---
531
759
  {
532
760
  id: "cursor-posix-stdin-empty",
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Durable attribution log for tree-wide destructive git at the write fence (#3917).
3
+ *
4
+ * Lives outside the repository so a `reset --hard` or a worktree prune cannot
5
+ * erase it. This is detection, not a root-cause claim.
6
+ */
7
+ import type { GitDestructiveKind } from "./dest-form.js";
8
+ export declare const GIT_DESTRUCTIVE_LOG_ENV = "DEFT_GIT_DESTRUCTIVE_LOG";
9
+ export type GitDestructiveDisposition = "deny" | "allow-fixture";
10
+ export interface GitDestructiveRecord {
11
+ readonly ts: string;
12
+ readonly kind: GitDestructiveKind;
13
+ readonly disposition: GitDestructiveDisposition;
14
+ readonly command: string;
15
+ readonly projectRoot: string;
16
+ readonly host: string;
17
+ readonly toolName: string | null;
18
+ readonly actor: string;
19
+ readonly pid: number;
20
+ readonly relocators: readonly string[];
21
+ readonly unprovable: boolean;
22
+ }
23
+ export declare function resolveGitDestructiveLogPath(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, homeDir?: string): string;
24
+ /**
25
+ * Best-effort append. A log failure must not block the deny.
26
+ */
27
+ export declare function appendGitDestructiveRecord(record: GitDestructiveRecord, options?: {
28
+ readonly logPath?: string;
29
+ readonly env?: NodeJS.ProcessEnv;
30
+ readonly append?: (path: string, line: string) => void;
31
+ }): void;
32
+ //# sourceMappingURL=git-destructive-log.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Durable attribution log for tree-wide destructive git at the write fence (#3917).
3
+ *
4
+ * Lives outside the repository so a `reset --hard` or a worktree prune cannot
5
+ * erase it. This is detection, not a root-cause claim.
6
+ */
7
+ import { mkdirSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { containedWrite } from "../fs/contained-write.js";
11
+ import { platformUserConfigDir } from "../user-config/resolve-user-md.js";
12
+ export const GIT_DESTRUCTIVE_LOG_ENV = "DEFT_GIT_DESTRUCTIVE_LOG";
13
+ export function resolveGitDestructiveLogPath(env = process.env, platform = process.platform, homeDir = homedir()) {
14
+ const override = env[GIT_DESTRUCTIVE_LOG_ENV]?.trim();
15
+ if (override !== undefined && override.length > 0) {
16
+ return resolve(override);
17
+ }
18
+ return join(platformUserConfigDir(platform, env, homeDir), "logs", "git-destructive.jsonl");
19
+ }
20
+ function defaultAppend(path, line) {
21
+ const targetAbs = resolve(path);
22
+ const parent = dirname(targetAbs);
23
+ mkdirSync(parent, { recursive: true });
24
+ containedWrite({
25
+ root: parent,
26
+ target: targetAbs,
27
+ data: line,
28
+ mode: "append",
29
+ mkdir: false,
30
+ mutation: false,
31
+ });
32
+ }
33
+ /**
34
+ * Best-effort append. A log failure must not block the deny.
35
+ */
36
+ export function appendGitDestructiveRecord(record, options = {}) {
37
+ try {
38
+ const path = options.logPath ?? resolveGitDestructiveLogPath(options.env ?? process.env);
39
+ const line = `${JSON.stringify(record)}\n`;
40
+ (options.append ?? defaultAppend)(path, line);
41
+ }
42
+ catch {
43
+ // Attribution is best-effort; the fail-closed deny still stands.
44
+ }
45
+ }
46
+ //# sourceMappingURL=git-destructive-log.js.map
@@ -3,5 +3,7 @@ export * from "./cursor-hooks.js";
3
3
  export * from "./dest-form.js";
4
4
  export * from "./dispatcher.js";
5
5
  export * from "./fixtures/index.js";
6
+ export * from "./git-destructive-log.js";
6
7
  export * from "./scope.js";
8
+ export * from "./shell-write-targets.js";
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,7 @@ export * from "./cursor-hooks.js";
3
3
  export * from "./dest-form.js";
4
4
  export * from "./dispatcher.js";
5
5
  export * from "./fixtures/index.js";
6
+ export * from "./git-destructive-log.js";
6
7
  export * from "./scope.js";
8
+ export * from "./shell-write-targets.js";
7
9
  //# sourceMappingURL=index.js.map
@@ -27,7 +27,9 @@ export function inspectActiveScope(projectRoot) {
27
27
  candidates.sort();
28
28
  let firstRejection = null;
29
29
  for (const candidate of candidates) {
30
- const result = evaluate(candidate);
30
+ // #3736: origin freshness remains fail-closed at explicit xbrief:preflight.
31
+ // The host mutation path must stay local and render before its effective timeout.
32
+ const result = evaluate(candidate, { skipOriginFreshness: true });
31
33
  if (result.exitCode === 0) {
32
34
  return { ready: true, path: candidate, message: result.message };
33
35
  }
@@ -0,0 +1,10 @@
1
+ export declare const SHELL_WRITE_KINDS: readonly ["set-content", "out-file", "add-content", "python-pathlib", "io-writealltext"];
2
+ export type ShellWriteKind = (typeof SHELL_WRITE_KINDS)[number];
3
+ export interface ShellWriteTarget {
4
+ readonly kind: ShellWriteKind;
5
+ readonly path: string;
6
+ readonly unprovable?: boolean;
7
+ }
8
+ export declare function classifyShellWriteTargets(command: string): ShellWriteTarget[];
9
+ export declare function isInRepoShellWritePath(projectRoot: string, dest: string): boolean;
10
+ //# sourceMappingURL=shell-write-targets.d.ts.map
@@ -0,0 +1,274 @@
1
+ /** Recognized in-repo Shell file-write dests (#3983 / #3987). */
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+ export const SHELL_WRITE_KINDS = [
4
+ "set-content",
5
+ "out-file",
6
+ "add-content",
7
+ "python-pathlib",
8
+ "io-writealltext",
9
+ ];
10
+ const DQ = String.fromCharCode(34);
11
+ const AQ = String.fromCharCode(39);
12
+ function stripQuotes(token) {
13
+ const t = token.trim();
14
+ if (t.length < 2)
15
+ return t;
16
+ const a = t[0];
17
+ const b = t[t.length - 1];
18
+ if ((a === DQ && b === DQ) || (a === AQ && b === AQ))
19
+ return t.slice(1, -1);
20
+ return t;
21
+ }
22
+ function pushUnique(out, kind, raw) {
23
+ const path = stripQuotes(raw).trim();
24
+ if (path.length === 0)
25
+ return;
26
+ if (out.some((d) => d.kind === kind && d.path === path))
27
+ return;
28
+ out.push({ kind, path });
29
+ }
30
+ function extractQuoted(command, start) {
31
+ if (start >= command.length)
32
+ return { value: "", next: start };
33
+ const q = command[start];
34
+ if (q !== DQ && q !== AQ) {
35
+ let i = start;
36
+ while (i < command.length) {
37
+ const c = command[i];
38
+ if (c === undefined)
39
+ break;
40
+ if (c === " " || c === " " || c === "," || c === ")")
41
+ break;
42
+ i += 1;
43
+ }
44
+ return { value: command.slice(start, i), next: i };
45
+ }
46
+ let i = start + 1;
47
+ while (i < command.length && command[i] !== q)
48
+ i += 1;
49
+ return { value: command.slice(start + 1, i), next: i + 1 };
50
+ }
51
+ function skipWs(command, start) {
52
+ let i = start;
53
+ while (i < command.length) {
54
+ const code = command.charCodeAt(i);
55
+ if (code !== 32 && code !== 9 && code !== 10 && code !== 13)
56
+ break;
57
+ i += 1;
58
+ }
59
+ return i;
60
+ }
61
+ function startsWithVerb(segment, verb) {
62
+ const i = skipWs(segment, 0);
63
+ if (segment.slice(i, i + verb.length).toLowerCase() !== verb.toLowerCase())
64
+ return false;
65
+ const next = segment[i + verb.length];
66
+ return next === undefined || next === " " || next === " " || next === "-";
67
+ }
68
+ function splitWriteSegments(command) {
69
+ const segs = [];
70
+ let cur = "";
71
+ let quote = null;
72
+ for (let i = 0; i < command.length; i++) {
73
+ const c = command[i];
74
+ if (c === undefined)
75
+ break;
76
+ if (quote !== null) {
77
+ if (c === quote)
78
+ quote = null;
79
+ cur += c;
80
+ continue;
81
+ }
82
+ if (c === DQ || c === AQ) {
83
+ quote = c;
84
+ cur += c;
85
+ continue;
86
+ }
87
+ const code = command.charCodeAt(i);
88
+ const next = command.charCodeAt(i + 1);
89
+ if ((code === 38 && next === 38) || (code === 124 && next === 124)) {
90
+ segs.push(cur);
91
+ cur = "";
92
+ i += 1;
93
+ continue;
94
+ }
95
+ if (code === 124 || code === 38 || code === 59 || code === 10 || code === 13) {
96
+ segs.push(cur);
97
+ cur = "";
98
+ continue;
99
+ }
100
+ cur += c;
101
+ }
102
+ segs.push(cur);
103
+ return segs.map((seg) => seg.trim()).filter((seg) => seg.length > 0);
104
+ }
105
+ const PATH_PARAM_NAMES = ["path", "literalpath", "filepath"];
106
+ function uniqueNamedParam(flag, names) {
107
+ const f = flag.replace(/^-+/, "").toLowerCase();
108
+ if (f.length === 0)
109
+ return null;
110
+ const hits = names.filter((n) => n.startsWith(f));
111
+ return hits.length === 1 ? (hits[0] ?? null) : null;
112
+ }
113
+ function parseFlag(token) {
114
+ if (!token.startsWith("-"))
115
+ return null;
116
+ const colon = token.indexOf(":");
117
+ if (colon <= 0)
118
+ return { flag: token, attached: null };
119
+ return { flag: token.slice(0, colon), attached: token.slice(colon + 1) };
120
+ }
121
+ function extractAfterVerb(command, verb, kind, out) {
122
+ if (!startsWithVerb(command, verb))
123
+ return;
124
+ const i = skipWs(command, 0);
125
+ let j = skipWs(command, i + verb.length);
126
+ let named = null;
127
+ let positional = null;
128
+ while (j < command.length) {
129
+ const extracted = extractQuoted(command, j);
130
+ if (extracted.value.length === 0)
131
+ break;
132
+ j = skipWs(command, extracted.next);
133
+ const parsed = parseFlag(extracted.value);
134
+ if (parsed === null) {
135
+ if (positional === null)
136
+ positional = extracted.value;
137
+ continue;
138
+ }
139
+ let arg = parsed.attached;
140
+ if (arg === null || arg.length === 0) {
141
+ const next = extractQuoted(command, j);
142
+ if (next.value.length > 0 && !next.value.startsWith("-")) {
143
+ arg = next.value;
144
+ j = skipWs(command, next.next);
145
+ }
146
+ else {
147
+ arg = null;
148
+ }
149
+ }
150
+ if (uniqueNamedParam(parsed.flag, PATH_PARAM_NAMES) !== null) {
151
+ if (arg !== null && arg.length > 0 && !arg.startsWith("-"))
152
+ named = arg;
153
+ }
154
+ }
155
+ const dest = named ?? positional;
156
+ if (dest !== null && !dest.startsWith("-"))
157
+ pushUnique(out, kind, dest);
158
+ }
159
+ function pathlibWriteFollows(command, afterPathArgs) {
160
+ let i = skipWs(command, afterPathArgs);
161
+ if (command[i] === ")")
162
+ i = skipWs(command, i + 1);
163
+ while (i < command.length) {
164
+ if (command[i] === ";")
165
+ return false;
166
+ i = skipWs(command, i);
167
+ if (command[i] !== ".")
168
+ return false;
169
+ i += 1;
170
+ const start = i;
171
+ while (i < command.length) {
172
+ const c = command.charCodeAt(i);
173
+ if ((c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95) {
174
+ i += 1;
175
+ continue;
176
+ }
177
+ break;
178
+ }
179
+ const name = command.slice(start, i);
180
+ i = skipWs(command, i);
181
+ if (command[i] !== "(")
182
+ return false;
183
+ if (name === "write_text")
184
+ return true;
185
+ if (name === "write_bytes")
186
+ return true;
187
+ i += 1;
188
+ let depth = 1;
189
+ while (i < command.length && depth > 0) {
190
+ const ch = command[i];
191
+ if (ch === "(")
192
+ depth += 1;
193
+ else if (ch === ")")
194
+ depth -= 1;
195
+ i += 1;
196
+ }
197
+ }
198
+ return false;
199
+ }
200
+ function extractPythonPathlib(command, out) {
201
+ const i = skipWs(command, 0);
202
+ const head = command.slice(i, i + 10).toLowerCase();
203
+ if (!head.startsWith("python") && !head.startsWith("py ") && !head.startsWith("py.exe"))
204
+ return;
205
+ const needle = "Path(";
206
+ let from = i;
207
+ while (from < command.length) {
208
+ const at = command.indexOf(needle, from);
209
+ if (at < 0)
210
+ return;
211
+ let j = skipWs(command, at + needle.length);
212
+ const ch = command[j];
213
+ if (ch === "r" || ch === "R")
214
+ j = skipWs(command, j + 1);
215
+ const extracted = extractQuoted(command, j);
216
+ if (pathlibWriteFollows(command, extracted.next)) {
217
+ pushUnique(out, "python-pathlib", extracted.value);
218
+ }
219
+ from = at + needle.length;
220
+ }
221
+ }
222
+ function extractIoWrite(command, out) {
223
+ const i = skipWs(command, 0);
224
+ if (command[i] !== "[")
225
+ return;
226
+ const needles = ["WriteAllText(", "WriteAllBytes("];
227
+ for (const needle of needles) {
228
+ const at = command.indexOf(needle, i);
229
+ if (at < 0)
230
+ continue;
231
+ const j = skipWs(command, at + needle.length);
232
+ const extracted = extractQuoted(command, j);
233
+ pushUnique(out, "io-writealltext", extracted.value);
234
+ }
235
+ }
236
+ function classifySimpleWrite(command) {
237
+ const out = [];
238
+ extractAfterVerb(command, "Set-Content", "set-content", out);
239
+ extractAfterVerb(command, "Add-Content", "add-content", out);
240
+ extractAfterVerb(command, "Out-File", "out-file", out);
241
+ extractPythonPathlib(command, out);
242
+ extractIoWrite(command, out);
243
+ return out;
244
+ }
245
+ export function classifyShellWriteTargets(command) {
246
+ const cmd = command.trim();
247
+ if (cmd.length === 0)
248
+ return [];
249
+ const segments = splitWriteSegments(cmd);
250
+ if (segments.length === 1)
251
+ return classifySimpleWrite(segments[0] ?? cmd);
252
+ const found = [];
253
+ for (const seg of segments)
254
+ found.push(...classifySimpleWrite(seg));
255
+ if (found.length === 0)
256
+ return [];
257
+ return found.map((d) => ({ ...d, unprovable: true }));
258
+ }
259
+ export function isInRepoShellWritePath(projectRoot, dest) {
260
+ const path = dest.trim();
261
+ if (path.length === 0)
262
+ return false;
263
+ if (path.includes("*") || path.includes("?") || path.includes("$"))
264
+ return false;
265
+ const root = resolve(projectRoot);
266
+ const abs = isAbsolute(path) ? resolve(path) : resolve(root, path);
267
+ const rel = relative(root, abs);
268
+ if (rel.length === 0)
269
+ return false;
270
+ if (rel.startsWith("..") || isAbsolute(rel))
271
+ return false;
272
+ return true;
273
+ }
274
+ //# sourceMappingURL=shell-write-targets.js.map