@deftai/directive-core 0.104.0 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/dist/authz/classify.js +1108 -67
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +34 -4
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/forward-coverage/diff-coverage.d.ts +39 -0
  17. package/dist/forward-coverage/diff-coverage.js +112 -0
  18. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  19. package/dist/forward-coverage/diff-lines.js +173 -0
  20. package/dist/forward-coverage/evaluate.d.ts +24 -9
  21. package/dist/forward-coverage/evaluate.js +125 -19
  22. package/dist/fs/contained-write.d.ts +115 -1
  23. package/dist/fs/contained-write.js +241 -5
  24. package/dist/fs/mutation-ledger.d.ts +54 -0
  25. package/dist/fs/mutation-ledger.js +133 -0
  26. package/dist/fs/non-product-dirs.d.ts +79 -0
  27. package/dist/fs/non-product-dirs.js +96 -0
  28. package/dist/hooks/dispatcher.d.ts +3 -1
  29. package/dist/hooks/dispatcher.js +14 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +2 -0
  32. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  33. package/dist/init-deposit/agent-hooks.js +18 -16
  34. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  35. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  36. package/dist/init-deposit/gitignore.js +9 -0
  37. package/dist/init-deposit/hygiene.d.ts +47 -11
  38. package/dist/init-deposit/hygiene.js +172 -37
  39. package/dist/init-deposit/legacy-detect.js +6 -2
  40. package/dist/init-deposit/refresh.d.ts +21 -1
  41. package/dist/init-deposit/refresh.js +106 -9
  42. package/dist/init-deposit/scaffold.js +28 -279
  43. package/dist/init-deposit/slash-deposit.js +13 -7
  44. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  45. package/dist/init-deposit/xbrief-projections.js +24 -11
  46. package/dist/intake/clause-derivation.d.ts +47 -1
  47. package/dist/intake/clause-derivation.js +238 -9
  48. package/dist/intake/issue-ingest.d.ts +3 -1
  49. package/dist/intake/issue-ingest.js +35 -6
  50. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  51. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  52. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  53. package/dist/lifecycle-visible/evaluate.js +710 -0
  54. package/dist/lifecycle-visible/index.d.ts +2 -0
  55. package/dist/lifecycle-visible/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +10 -0
  57. package/dist/literal-acceptance/capture.js +226 -77
  58. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  59. package/dist/literal-acceptance/evaluate.js +70 -2
  60. package/dist/literal-acceptance/index.d.ts +3 -3
  61. package/dist/literal-acceptance/index.js +3 -3
  62. package/dist/literal-acceptance/safety.d.ts +41 -0
  63. package/dist/literal-acceptance/safety.js +126 -2
  64. package/dist/literal-acceptance/types.d.ts +8 -0
  65. package/dist/orchestration/judgment-policy.d.ts +13 -0
  66. package/dist/orchestration/judgment-policy.js +15 -0
  67. package/dist/orchestration/probe-session.js +13 -8
  68. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  69. package/dist/orchestration/verify-judgment-gates.js +43 -11
  70. package/dist/orphan-active/evaluate.d.ts +7 -1
  71. package/dist/orphan-active/evaluate.js +102 -23
  72. package/dist/platform/platform-capabilities.js +2 -3
  73. package/dist/policy/base-branch.d.ts +37 -0
  74. package/dist/policy/base-branch.js +114 -0
  75. package/dist/policy/branch-sync.d.ts +80 -0
  76. package/dist/policy/branch-sync.js +211 -0
  77. package/dist/policy/ceremony-dial-escalation.js +1 -0
  78. package/dist/policy/ceremony-dial.js +11 -21
  79. package/dist/policy/delivery-branch.d.ts +5 -3
  80. package/dist/policy/delivery-branch.js +7 -3
  81. package/dist/policy/file-size-thresholds.d.ts +24 -0
  82. package/dist/policy/file-size-thresholds.js +23 -0
  83. package/dist/policy/forge-outage-retry.d.ts +47 -0
  84. package/dist/policy/forge-outage-retry.js +157 -0
  85. package/dist/policy/host-hooks.d.ts +20 -0
  86. package/dist/policy/host-hooks.js +121 -2
  87. package/dist/policy/index.d.ts +7 -0
  88. package/dist/policy/index.js +66 -7
  89. package/dist/policy/org-force-on-migration.js +2 -1
  90. package/dist/policy/plan-extensions.d.ts +1 -1
  91. package/dist/policy/plan-extensions.js +2 -0
  92. package/dist/policy/product-signal.js +6 -7
  93. package/dist/policy/project-invariants.d.ts +50 -0
  94. package/dist/policy/project-invariants.js +205 -0
  95. package/dist/policy/require-human-merge.js +8 -6
  96. package/dist/policy/resolve.d.ts +7 -2
  97. package/dist/policy/resolve.js +25 -8
  98. package/dist/policy/sync-default.d.ts +129 -0
  99. package/dist/policy/sync-default.js +452 -0
  100. package/dist/policy/sync-max-files.d.ts +93 -0
  101. package/dist/policy/sync-max-files.js +192 -0
  102. package/dist/policy/value-feedback.js +7 -8
  103. package/dist/pr-monitor/absent-required.d.ts +11 -0
  104. package/dist/pr-monitor/absent-required.js +61 -0
  105. package/dist/pr-monitor/constants.d.ts +7 -0
  106. package/dist/pr-monitor/constants.js +7 -0
  107. package/dist/pr-monitor/index.d.ts +2 -1
  108. package/dist/pr-monitor/index.js +2 -1
  109. package/dist/pr-monitor/main.js +4 -0
  110. package/dist/pr-monitor/monitor.js +24 -1
  111. package/dist/pr-wait-mergeable/classify.js +6 -0
  112. package/dist/preflight/evaluate.d.ts +4 -0
  113. package/dist/preflight/evaluate.js +35 -1
  114. package/dist/preflight/index.d.ts +4 -0
  115. package/dist/preflight/index.js +2 -0
  116. package/dist/preflight/intended-placement.d.ts +62 -0
  117. package/dist/preflight/intended-placement.js +275 -0
  118. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  119. package/dist/preflight/project-invariants-gate.js +82 -0
  120. package/dist/preflight-cache/evaluate.d.ts +12 -0
  121. package/dist/preflight-cache/evaluate.js +15 -1
  122. package/dist/preflight-cache/index.d.ts +1 -1
  123. package/dist/preflight-cache/index.js +1 -1
  124. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  125. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  126. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  127. package/dist/product-first-done-gate/acceptance.js +79 -8
  128. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  129. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  130. package/dist/product-first-done-gate/evaluate.d.ts +16 -0
  131. package/dist/product-first-done-gate/evaluate.js +298 -34
  132. package/dist/product-first-done-gate/index.d.ts +2 -0
  133. package/dist/product-first-done-gate/index.js +1 -0
  134. package/dist/release/build-dist.d.ts +101 -0
  135. package/dist/release/build-dist.js +428 -46
  136. package/dist/release/closed-verb-gate.d.ts +12 -0
  137. package/dist/release/closed-verb-gate.js +51 -0
  138. package/dist/release/index.d.ts +1 -0
  139. package/dist/release/index.js +1 -0
  140. package/dist/release/pipeline.js +7 -0
  141. package/dist/release/types.d.ts +12 -0
  142. package/dist/render/constants.d.ts +2 -1
  143. package/dist/render/constants.js +2 -1
  144. package/dist/render/framework-commands.js +4 -0
  145. package/dist/render/spec-validate.js +23 -12
  146. package/dist/run-summary/emit.d.ts +16 -10
  147. package/dist/run-summary/emit.js +26 -15
  148. package/dist/run-summary/index.d.ts +3 -2
  149. package/dist/run-summary/index.js +2 -1
  150. package/dist/run-summary/session-id.d.ts +21 -0
  151. package/dist/run-summary/session-id.js +92 -0
  152. package/dist/run-summary/types.d.ts +33 -4
  153. package/dist/run-summary/types.js +1 -0
  154. package/dist/scope/acceptance-evidence.d.ts +19 -7
  155. package/dist/scope/acceptance-evidence.js +38 -11
  156. package/dist/scope/delivery-evidence.d.ts +3 -3
  157. package/dist/scope/delivery-evidence.js +22 -27
  158. package/dist/scope/index.d.ts +1 -0
  159. package/dist/scope/index.js +1 -0
  160. package/dist/scope/main.js +15 -5
  161. package/dist/scope/parent-lineage.js +7 -6
  162. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  163. package/dist/scope/project-invariant-coverage.js +322 -0
  164. package/dist/scope/promote-from-issue.js +5 -5
  165. package/dist/scope/transition.js +1 -1
  166. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  167. package/dist/scope-provenance/compare-intent.js +233 -0
  168. package/dist/scope-provenance/digest.d.ts +18 -1
  169. package/dist/scope-provenance/digest.js +16 -11
  170. package/dist/scope-provenance/evaluate.d.ts +5 -1
  171. package/dist/scope-provenance/evaluate.js +67 -7
  172. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  173. package/dist/scope-provenance/extract-intent.js +262 -0
  174. package/dist/scope-provenance/index.d.ts +7 -1
  175. package/dist/scope-provenance/index.js +7 -1
  176. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  177. package/dist/scope-provenance/intent-digest.js +33 -0
  178. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  179. package/dist/scope-provenance/intent-evaluate.js +149 -0
  180. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  181. package/dist/scope-provenance/json-tokenizer.js +225 -0
  182. package/dist/scope-provenance/known-machine.d.ts +38 -0
  183. package/dist/scope-provenance/known-machine.js +157 -0
  184. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  185. package/dist/scope-provenance/mint-artifacts.js +425 -0
  186. package/dist/session/ac-pass-banking.d.ts +9 -36
  187. package/dist/session/ac-pass-banking.js +33 -79
  188. package/dist/session/ac-pass-reuse.d.ts +53 -0
  189. package/dist/session/ac-pass-reuse.js +95 -0
  190. package/dist/session/git.js +19 -4
  191. package/dist/session/index.d.ts +4 -0
  192. package/dist/session/index.js +4 -0
  193. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  194. package/dist/session/occupancy-stress-worker.js +36 -0
  195. package/dist/session/occupancy.d.ts +97 -0
  196. package/dist/session/occupancy.js +422 -0
  197. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  198. package/dist/session/process-cost.d.ts +32 -10
  199. package/dist/session/process-cost.js +189 -1
  200. package/dist/session/product-state-hash.d.ts +26 -0
  201. package/dist/session/product-state-hash.js +700 -0
  202. package/dist/session/ritual-entrypoint.js +16 -1
  203. package/dist/session/session-ready.d.ts +2 -0
  204. package/dist/session/session-ready.js +52 -30
  205. package/dist/session/session-start.d.ts +13 -0
  206. package/dist/session/session-start.js +103 -9
  207. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  208. package/dist/session/verify-ac-session-cache.js +126 -0
  209. package/dist/session/verify-session-ritual.d.ts +28 -0
  210. package/dist/session/verify-session-ritual.js +97 -5
  211. package/dist/slash/openclaw-deposit.js +13 -9
  212. package/dist/slice/constants.d.ts +2 -2
  213. package/dist/slice/constants.js +2 -2
  214. package/dist/slice/existing.js +9 -3
  215. package/dist/slice/lock.d.ts +16 -1
  216. package/dist/slice/lock.js +137 -11
  217. package/dist/story-ready/evaluate.d.ts +2 -0
  218. package/dist/story-ready/evaluate.js +13 -0
  219. package/dist/swarm/complete-cohort.d.ts +3 -0
  220. package/dist/swarm/complete-cohort.js +59 -11
  221. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  222. package/dist/swarm/finalize-cohort-cli.js +165 -35
  223. package/dist/swarm/finalize-cohort.d.ts +1 -0
  224. package/dist/swarm/finalize-cohort.js +47 -4
  225. package/dist/swarm/launch.d.ts +22 -0
  226. package/dist/swarm/launch.js +155 -0
  227. package/dist/swarm/routing-set-cli.d.ts +11 -1
  228. package/dist/swarm/routing-set-cli.js +15 -3
  229. package/dist/swarm/routing-verify.d.ts +13 -1
  230. package/dist/swarm/routing-verify.js +60 -21
  231. package/dist/swarm/routing.d.ts +14 -0
  232. package/dist/swarm/routing.js +53 -3
  233. package/dist/telemetry-coverage/fake-trial.js +10 -0
  234. package/dist/telemetry-coverage/kinds.js +2 -0
  235. package/dist/triage/actions/index.js +4 -1
  236. package/dist/triage/bootstrap/index.js +6 -6
  237. package/dist/triage/help/registry-data.d.ts +1 -1
  238. package/dist/triage/help/registry-data.js +1 -1
  239. package/dist/triage/welcome/writers.d.ts +1 -1
  240. package/dist/triage/welcome/writers.js +10 -5
  241. package/dist/ts-check-lane/index.d.ts +3 -0
  242. package/dist/ts-check-lane/index.js +2 -0
  243. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  244. package/dist/ts-check-lane/progress-reporter.js +38 -0
  245. package/dist/ts-check-lane/progress.d.ts +32 -0
  246. package/dist/ts-check-lane/progress.js +57 -0
  247. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  248. package/dist/ts-check-lane/run-lane.js +8 -6
  249. package/dist/validate-content/validate-links.d.ts +8 -0
  250. package/dist/validate-content/validate-links.js +9 -15
  251. package/dist/value/feedback-file.js +12 -2
  252. package/dist/value/readback.d.ts +4 -1
  253. package/dist/value/readback.js +51 -12
  254. package/dist/vbrief-build/index.d.ts +1 -0
  255. package/dist/vbrief-build/index.js +1 -0
  256. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  257. package/dist/vbrief-build/project-invariants-io.js +41 -0
  258. package/dist/vbrief-reconcile/graph.js +18 -1
  259. package/dist/vbrief-reconcile/index.d.ts +1 -1
  260. package/dist/vbrief-reconcile/index.js +1 -1
  261. package/dist/vbrief-reconcile/types.d.ts +13 -0
  262. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  263. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  264. package/dist/vbrief-validate/index.d.ts +1 -1
  265. package/dist/vbrief-validate/index.js +1 -1
  266. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  267. package/dist/vbrief-validate/plan-hooks.js +68 -0
  268. package/dist/vbrief-validate/schema.d.ts +4 -0
  269. package/dist/vbrief-validate/schema.js +1 -1
  270. package/dist/verify-ac/evaluate.d.ts +7 -1
  271. package/dist/verify-ac/evaluate.js +34 -26
  272. package/dist/verify-ac/flag.d.ts +4 -0
  273. package/dist/verify-ac/flag.js +16 -0
  274. package/dist/verify-ac/index.d.ts +2 -2
  275. package/dist/verify-ac/index.js +2 -2
  276. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  277. package/dist/verify-env/agent-hook-readiness.js +12 -8
  278. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  279. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  280. package/dist/verify-env/agent-hooks.js +3 -4
  281. package/dist/verify-source/contained-writes.js +2 -0
  282. package/dist/verify-source/verify-stubs.d.ts +15 -1
  283. package/dist/verify-source/verify-stubs.js +20 -16
  284. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  285. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  286. package/dist/xbrief-migrate/migrate-project.js +2 -5
  287. package/package.json +7 -3
@@ -0,0 +1,96 @@
1
+ /**
2
+ * non-product-dirs.ts — one source of truth for the "not product source"
3
+ * directory basenames shared by every Directive filesystem walk (#3487).
4
+ *
5
+ * Directive shipped four independently hand-maintained exclusion sets:
6
+ *
7
+ * - `EXCLUDE_DIRS` — `verify-source/verify-stubs.ts`
8
+ * - `EXCLUDE_DIRS` — `validate-content/validate-links.ts`
9
+ * - `SKIP_DIRS` — `codebase/default-extractor.ts`
10
+ * - `DEFAULT_EXCLUDES` — `release/build-dist.ts`
11
+ *
12
+ * They drifted from each other, which is exactly how #2953 added
13
+ * `.deft-scratch` / `swarm-worktrees` to some walks and missed the
14
+ * `verify-stubs` walk (#3481), and how none of them ever learned about agent
15
+ * working directories even though Directive ships first-class support for
16
+ * several agent hosts. Because `@deftai/directive-core` is published, a
17
+ * consumer cannot patch any of the four.
18
+ *
19
+ * `NON_PRODUCT_DIRS` is the common core each walk now extends. It carries only
20
+ * basenames that are never product source in any repo: VCS metadata,
21
+ * dependency trees, build output, tool caches, and agent working directories.
22
+ * Walk-specific entries (`tests`, `specs`, `.planning`, `htmlcov`, …) stay with
23
+ * the walk that needs them — this constant is a floor, not a ceiling.
24
+ *
25
+ * Adding a directory here is a one-line change all four walks pick up, and
26
+ * `non-product-dirs.test.ts` fails if any of them stops agreeing.
27
+ *
28
+ * Refs #3487, #3481, #2953, #2954, #1656.
29
+ */
30
+ /**
31
+ * Swarm / agent scratch worktree roots (#2953, #1656).
32
+ *
33
+ * `.deft-scratch` is the current layout (`.deft-scratch/worktrees/<story-id>`,
34
+ * built by `swarm/launch.ts`); `swarm-worktrees` is the pre-`.deft-scratch`
35
+ * name, kept for repos that still carry one.
36
+ */
37
+ export const AGENT_SCRATCH_DIRS = [".deft-scratch", "swarm-worktrees"];
38
+ /**
39
+ * Agent-host directories that hold host *working* state rather than product
40
+ * source (#3487).
41
+ *
42
+ * Only names with evidence of a real working directory belong here. Directive
43
+ * knows several host directories (`.claude`, `.cursor`, `.grok`, `.codex`,
44
+ * `.github/skills`, `.agents`), but most of them hold nothing except
45
+ * Directive's own committed deposits — `commands.md` § Native multi-host
46
+ * registration (LockedDecision L8) says to *commit* those, so blanket-excluding
47
+ * them would hide committed team surface for no measured gain.
48
+ *
49
+ * `.claude` is different in kind: the Claude Code host keeps live session state
50
+ * in the same directory it keeps config — `settings.local.json`, and agent
51
+ * worktrees under `.claude/worktrees/<agent-id>`, each a full nested checkout.
52
+ * Three such worktrees added ~11,000 walkable entries to this repo
53
+ * (9,128 → 20,393) that no Directive walk skipped. Its Directive-managed
54
+ * contents (`.claude/settings.json`, `.claude/commands/*.md`,
55
+ * `.claude/skills/`) are deposits generated from `content/`, which every walk
56
+ * still visits at the authoring source.
57
+ *
58
+ * Deliberately **not** listed:
59
+ * - `.openclaw` — there is no project-level `.openclaw/`; the host state dir
60
+ * is `~/.openclaw` in `$HOME` (see `doctor/openclaw-skills.ts`), and
61
+ * inventing a project one is explicitly forbidden by
62
+ * `content/docs/openclaw-agent-host.md` and asserted against in
63
+ * `slash/openclaw-adapter.test.ts`.
64
+ * - `.cursor`, `.grok`, `.codex`, `.github` — committed Directive deposits
65
+ * (`.cursor/hooks.json`, `.grok/commands/`, `.codex/prompts/`,
66
+ * `.github/skills/`) with no known working/worktree subtree. No evidence
67
+ * of a scratch convention was found for any of them.
68
+ */
69
+ export const AGENT_HOST_WORKING_DIRS = [".claude"];
70
+ /**
71
+ * Directory basenames no Directive walk should descend into.
72
+ *
73
+ * Every walk extends this set; none may shrink it. Keep entries to basenames
74
+ * that cannot be product source in any repo — a name that is merely *usually*
75
+ * uninteresting to one gate (`tests`, `scripts`, `specs`) belongs to that
76
+ * walk's own set, not here.
77
+ */
78
+ export const NON_PRODUCT_DIRS = new Set([
79
+ // Version-control metadata.
80
+ ".git",
81
+ // Dependency trees and language virtualenvs.
82
+ "node_modules",
83
+ ".venv",
84
+ // Build output and operator backups.
85
+ "dist",
86
+ "backup",
87
+ // Tool caches.
88
+ "__pycache__",
89
+ ".mypy_cache",
90
+ ".pytest_cache",
91
+ ".ruff_cache",
92
+ // Agent working directories.
93
+ ...AGENT_SCRATCH_DIRS,
94
+ ...AGENT_HOST_WORKING_DIRS,
95
+ ]);
96
+ //# sourceMappingURL=non-product-dirs.js.map
@@ -21,7 +21,9 @@ export type HookDecisionCode = "session-start" | "session-start-disabled" | "ses
21
21
  /** Enforcement skipped: root `.deft-directive-disable` test kill-switch (#3039). */
22
22
  | "directive-disabled" | "session-compact-rearm" | "session-compact-rearm-degraded" | "session-compact-noop" | "not-direct-write" | "invalid-input"
23
23
  /** Host closed stdin with zero bytes — integration failure, not a policy gate (#2864). */
24
- | "stdin-empty" | "ritual-not-ready" | "scope-not-ready" | "write-propose-ready"
24
+ | "stdin-empty" | "ritual-not-ready"
25
+ /** Product-path write while another live session occupies this worktree (#3433). */
26
+ | "occupancy-occupied" | "scope-not-ready" | "write-propose-ready"
25
27
  /** Allowlisted assist/scratch write without active xBRIEF (#1802). */
26
28
  | "write-assist-scratch-ready" | "write-ready" | "read-only-deny" | "spawn-explore-ready"
27
29
  /** Non-lifecycle assist/docs spawn allowed without active xBRIEF (#3080). */
@@ -10,6 +10,7 @@ import { classifyMcpTool, DEFAULT_RUNTIME_AUTHORITY_POLICY, evaluateRuntimeAutho
10
10
  import { loadStoryWriteFenceFromPath, resolveWriteFence } from "../policy/write-fence.js";
11
11
  import { appendSoftAgentsRebindToMessage, decisionCarriesSoftAgentsRebind, formatSoftAgentsRebindChecklist, } from "../session/compact-ritual.js";
12
12
  import { detectBranch } from "../session/git.js";
13
+ import { evaluateOccupancyWriteGate } from "../session/occupancy.js";
13
14
  import { emitSessionRitualBlockedProcessCost } from "../session/process-cost.js";
14
15
  import { markRitualStaleAfterCompact } from "../session/ritual-sentinel.js";
15
16
  import { runSessionStartHookWrite } from "../session/session-start-hook.js";
@@ -598,6 +599,19 @@ function inspectMutationGates(input, toolName, seams, options) {
598
599
  return deny(input, "ritual-not-ready", toolName, `Directive could not inspect the gated session ritual: ${String(cause)}. ` +
599
600
  formatRitualRecoveryInstruction("cold"));
600
601
  }
602
+ const occupancyGate = isSpawnTool(toolName)
603
+ ? { allow: true, message: null, occupant: null }
604
+ : evaluateOccupancyWriteGate(projectRoot, { env: environ });
605
+ if (!occupancyGate.allow && occupancyGate.message !== null) {
606
+ const ritualNote = ritual.code !== 0 ? ` Also ritual-not-ready: ${ritual.message}` : "";
607
+ emitSessionRitualBlockedProcessCost({
608
+ toolName,
609
+ code: "occupancy-occupied",
610
+ recoveryTier: ritual.recoveryTier === "rearm" ? "rearm" : "cold",
611
+ detail: occupancyGate.message,
612
+ }, { projectRoot });
613
+ return deny(input, "occupancy-occupied", toolName, `Directive denied ${toolName}: ${occupancyGate.message}${ritualNote}`);
614
+ }
601
615
  if (ritual.code !== 0) {
602
616
  // #2992: prefer re-arm recovery when age/compact stale; cold when bind invalid.
603
617
  const recoveryTier = ritual.recoveryTier === "rearm" ? "rearm" : "cold";
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ export * as events from "./events/attribution-ledger.js";
30
30
  export * from "./forward-coverage/evaluate.js";
31
31
  export * as freshness from "./freshness/index.js";
32
32
  export { ContainedWriteError, ContainedWriteErrorCode, type ContainedWriteInput, type ContainedWriteMode, type ContainedWriteResult, containedWrite, resolveContainedTarget, } from "./fs/contained-write.js";
33
+ export { AGENT_HOST_WORKING_DIRS, AGENT_SCRATCH_DIRS, NON_PRODUCT_DIRS, } from "./fs/non-product-dirs.js";
33
34
  export { assertDestinationNotSymlink, assertDirectoryNotSymlink, assertProjectionContained, assertWriteTargetSafe, PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, walkDirectoryRejectSymlinks, } from "./fs/projection-containment.js";
34
35
  export * as handoffEvidence from "./handoff-evidence/index.js";
35
36
  export * as intake from "./intake/index.js";
package/dist/index.js CHANGED
@@ -31,6 +31,8 @@ export * from "./forward-coverage/evaluate.js";
31
31
  export * as freshness from "./freshness/index.js";
32
32
  // #2951 Phase 1: contained-write API for product sinks.
33
33
  export { ContainedWriteError, ContainedWriteErrorCode, containedWrite, resolveContainedTarget, } from "./fs/contained-write.js";
34
+ // #3487: single source of truth for walk exclusions, shared by every gate.
35
+ export { AGENT_HOST_WORKING_DIRS, AGENT_SCRATCH_DIRS, NON_PRODUCT_DIRS, } from "./fs/non-product-dirs.js";
34
36
  export { assertDestinationNotSymlink, assertDirectoryNotSymlink, assertProjectionContained, assertWriteTargetSafe, PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, walkDirectoryRejectSymlinks, } from "./fs/projection-containment.js";
35
37
  export * as handoffEvidence from "./handoff-evidence/index.js";
36
38
  export * as intake from "./intake/index.js";
@@ -16,7 +16,7 @@ export declare const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
16
16
  * Mutation tool.before runs `inspectMutationGates` → gated
17
17
  * `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
18
18
  * every boundary. Live readiness alone has a multi-host fixture ceiling of
19
- * ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
19
+ * ~24s (`content/contracts/agent-hook-readiness.md`); the historical deposit
20
20
  * default of 5s was below that budget. Under Cursor `failClosed: true`, a
21
21
  * host timeout kill surfaces as opaque exit-1 with no Directive decision
22
22
  * code (#3246 / related #2864). Keep this above the readiness ceiling plus
@@ -1,7 +1,7 @@
1
- import { existsSync, readFileSync, renameSync, rmSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { assertDepositContained } from "../deposit/contain.js";
4
- import { containedWrite } from "../fs/contained-write.js";
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
7
  export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
@@ -24,7 +24,7 @@ export const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
24
24
  * Mutation tool.before runs `inspectMutationGates` → gated
25
25
  * `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
26
26
  * every boundary. Live readiness alone has a multi-host fixture ceiling of
27
- * ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
27
+ * ~24s (`content/contracts/agent-hook-readiness.md`); the historical deposit
28
28
  * default of 5s was below that budget. Under Cursor `failClosed: true`, a
29
29
  * host timeout kill surfaces as opaque exit-1 with no Directive decision
30
30
  * code (#3246 / related #2864). Keep this above the readiness ceiling plus
@@ -217,7 +217,7 @@ function mergeCursorConfig(config, path) {
217
217
  ];
218
218
  return { ...config, version: 1, hooks };
219
219
  }
220
- function writeJsonIfChanged(projectRoot, path, payload) {
220
+ function writeJsonIfChanged(projectRoot, path, payload, kind) {
221
221
  const next = `${JSON.stringify(payload, null, 2)}\n`;
222
222
  if (existsSync(path) && readFileSync(path, "utf8") === next)
223
223
  return false;
@@ -231,12 +231,18 @@ function writeJsonIfChanged(projectRoot, path, payload) {
231
231
  target: temporary,
232
232
  data: next,
233
233
  mode: "replace",
234
+ mutation: { kind, path },
235
+ });
236
+ containedRename({
237
+ root: resolve(projectRoot),
238
+ from: temporary,
239
+ to: path,
240
+ mutation: false,
234
241
  });
235
- renameSync(temporary, path);
236
242
  }
237
243
  catch (err) {
238
244
  try {
239
- rmSync(temporary, { force: true });
245
+ containedRemove({ root: resolve(projectRoot), target: temporary, mutation: false });
240
246
  }
241
247
  catch {
242
248
  /* best-effort cleanup */
@@ -298,7 +304,7 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
298
304
  for (const item of prepared) {
299
305
  if (item.mode === "skip")
300
306
  continue;
301
- if (writeJsonIfChanged(projectRoot, item.absolute, item.payload)) {
307
+ if (writeJsonIfChanged(projectRoot, item.absolute, item.payload, item.mode === "strip" ? "stripped" : "wrote")) {
302
308
  if (item.mode === "strip")
303
309
  strippedPaths.push(item.path);
304
310
  else
@@ -309,29 +315,25 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
309
315
  ".cursor/hooks/deft-cursor-hook-adapter.mjs",
310
316
  ".cursor/hooks/deft-cursor-hook-adapter.test.mjs",
311
317
  ];
312
- let removedLegacyAdapter = false;
318
+ let adaptersRemoved = 0;
313
319
  for (const relative of legacyAdapterPaths) {
314
320
  const absolute = join(projectRoot, relative);
315
321
  assertDepositContained(projectRoot, absolute);
316
- if (existsSync(absolute)) {
317
- rmSync(absolute, { force: true });
318
- removedLegacyAdapter = true;
322
+ if (containedRemove({ root: projectRoot, target: absolute }).removed) {
323
+ adaptersRemoved += 1;
319
324
  }
320
325
  }
321
- if (removedLegacyAdapter && !changedPaths.includes(AGENT_HOOK_PATHS[2])) {
322
- changedPaths.push(AGENT_HOOK_PATHS[2]);
323
- }
324
326
  if (changedPaths.length > 0) {
325
327
  io.printf(`Installed Directive agent hooks: ${changedPaths.join(", ")}\n`);
326
328
  }
327
329
  if (strippedPaths.length > 0) {
328
330
  io.printf(`Removed Directive-managed agent hooks (plan.policy.hostHooks opt-out): ${strippedPaths.join(", ")}\n`);
329
331
  }
330
- if (changedPaths.length === 0 && strippedPaths.length === 0) {
332
+ if (changedPaths.length === 0 && strippedPaths.length === 0 && adaptersRemoved === 0) {
331
333
  io.printf("Directive agent hooks already current.\n");
332
334
  }
333
335
  return {
334
- changed: changedPaths.length + strippedPaths.length > 0,
336
+ changed: changedPaths.length + strippedPaths.length + adaptersRemoved > 0,
335
337
  changedPaths: [...changedPaths, ...strippedPaths],
336
338
  };
337
339
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Embedded python3 pin/lock checker for deposited deft-core-guard (#3193 / #3427).
3
+ * Twin of cmd/deft-install/core_guard_pin_content.embed — keep byte-identical.
4
+ * Not a .py file; CodeQL default setup must not see Python here.
5
+ */
6
+ export declare const CORE_GUARD_PIN_CONTENT_PYTHON: string;
7
+ //# sourceMappingURL=core-guard-pin-content.d.ts.map
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Embedded python3 pin/lock checker for deposited deft-core-guard (#3193 / #3427).
3
+ * Twin of cmd/deft-install/core_guard_pin_content.embed — keep byte-identical.
4
+ * Not a .py file; CodeQL default setup must not see Python here.
5
+ */
6
+ export const CORE_GUARD_PIN_CONTENT_PYTHON = [
7
+ "# Content-aware pin unit for deposited deft-core-guard (#3193).",
8
+ '# Invoked as: python3 - "$BASE_SHA" "$HEAD_SHA"',
9
+ "# Mirrors packages/core/src/init-deposit/hygiene.ts isUpgradePinPathContentAllowed.",
10
+ "from __future__ import annotations",
11
+ "",
12
+ "import json",
13
+ "import re",
14
+ "import subprocess",
15
+ "import sys",
16
+ "",
17
+ "_Q = chr(39) + chr(34) # ' and \" without raw-string quote pain",
18
+ "",
19
+ "",
20
+ "def git_show(sha, path):",
21
+ " try:",
22
+ " return subprocess.check_output(",
23
+ ' ["git", "show", f"{sha}:{path}"], text=True, stderr=subprocess.DEVNULL',
24
+ " )",
25
+ " except subprocess.CalledProcessError:",
26
+ ' return ""',
27
+ "",
28
+ "",
29
+ "def changed_names(base_sha, head_sha):",
30
+ " out = subprocess.check_output(",
31
+ ' ["git", "diff", "--name-only", base_sha, head_sha], text=True',
32
+ " )",
33
+ " return [p for p in out.splitlines() if p]",
34
+ "",
35
+ "",
36
+ "def is_dir_key(n):",
37
+ ' return n.startswith("@deftai/directive")',
38
+ "",
39
+ "",
40
+ "DEP_FIELDS = (",
41
+ ' "dependencies",',
42
+ ' "devDependencies",',
43
+ ' "optionalDependencies",',
44
+ ' "peerDependencies",',
45
+ ")",
46
+ "",
47
+ "",
48
+ "def deep_eq(a, b):",
49
+ " if a is b:",
50
+ " return True",
51
+ " if type(a) is not type(b):",
52
+ " return False",
53
+ " if isinstance(a, dict):",
54
+ " if set(a) != set(b):",
55
+ " return False",
56
+ " return all(deep_eq(a[k], b[k]) for k in a)",
57
+ " if isinstance(a, list):",
58
+ " return len(a) == len(b) and all(deep_eq(x, y) for x, y in zip(a, b))",
59
+ " return a == b",
60
+ "",
61
+ "",
62
+ "def strip_pins(obj):",
63
+ " if not isinstance(obj, dict):",
64
+ " return obj",
65
+ " out = {}",
66
+ " for k, v in obj.items():",
67
+ " if k in DEP_FIELDS and isinstance(v, dict):",
68
+ " out[k] = {dk: dv for dk, dv in v.items() if not is_dir_key(dk)}",
69
+ " else:",
70
+ " out[k] = v",
71
+ " return out",
72
+ "",
73
+ "",
74
+ "def pkg_pin_only(base, head):",
75
+ " try:",
76
+ ' b, h = json.loads(base or "{}"), json.loads(head or "{}")',
77
+ " except json.JSONDecodeError:",
78
+ " return False",
79
+ " return deep_eq(strip_pins(b), strip_pins(h))",
80
+ "",
81
+ "",
82
+ "def collect_deps(pkg):",
83
+ " out = {}",
84
+ " if not isinstance(pkg, dict):",
85
+ " return out",
86
+ " for f in DEP_FIELDS:",
87
+ " block = pkg.get(f)",
88
+ " if isinstance(block, dict):",
89
+ " for k, v in block.items():",
90
+ " if isinstance(v, str):",
91
+ " out[k] = v",
92
+ ' elif isinstance(v, dict) and isinstance(v.get("version"), str):',
93
+ ' out[k] = v["version"]',
94
+ " elif v is not None:",
95
+ " out[k] = str(v)",
96
+ " return out",
97
+ "",
98
+ "",
99
+ "def only_dir_diff(bd, hd):",
100
+ " keys = set(bd) | set(hd)",
101
+ " for k in keys:",
102
+ " if is_dir_key(k):",
103
+ " continue",
104
+ " if bd.get(k) != hd.get(k):",
105
+ " return False",
106
+ " return True",
107
+ "",
108
+ "",
109
+ "def npm_root_deps(lock):",
110
+ ' pkgs = lock.get("packages") if isinstance(lock, dict) else None',
111
+ ' if isinstance(pkgs, dict) and isinstance(pkgs.get(""), dict):',
112
+ ' return collect_deps(pkgs[""])',
113
+ ' deps = lock.get("dependencies") if isinstance(lock, dict) else None',
114
+ " out = {}",
115
+ " if isinstance(deps, dict):",
116
+ " for k, v in deps.items():",
117
+ " if isinstance(v, dict):",
118
+ ' out[k] = str(v.get("version", v))',
119
+ " elif isinstance(v, str):",
120
+ " out[k] = v",
121
+ " return out",
122
+ "",
123
+ "",
124
+ "def npm_lock_ok(base, head):",
125
+ " try:",
126
+ ' b, h = json.loads(base or "{}"), json.loads(head or "{}")',
127
+ " except json.JSONDecodeError:",
128
+ " return False",
129
+ " br, hr = npm_root_deps(b), npm_root_deps(h)",
130
+ " if not only_dir_diff(br, hr):",
131
+ " return False",
132
+ ' bp, hp = b.get("packages") or {}, h.get("packages") or {}',
133
+ " if not isinstance(bp, dict):",
134
+ " bp = {}",
135
+ " if not isinstance(hp, dict):",
136
+ " hp = {}",
137
+ " all_keys = set(bp) | set(hp)",
138
+ " for key in all_keys:",
139
+ ' if key == "":',
140
+ " continue",
141
+ ' if "node_modules/@deftai/directive" in key or "/@deftai/directive/" in key:',
142
+ " continue",
143
+ " if not deep_eq(bp.get(key), hp.get(key)):",
144
+ " return False",
145
+ " # lockfileVersion 1: freeze nested dependencies tree without directive keys",
146
+ " if not bp and not hp:",
147
+ " def strip_dir(v):",
148
+ " if not isinstance(v, dict):",
149
+ " return v",
150
+ " out = {}",
151
+ " for k, val in v.items():",
152
+ " if is_dir_key(k):",
153
+ " continue",
154
+ " if isinstance(val, dict):",
155
+ " nxt = dict(val)",
156
+ ' if "dependencies" in val:',
157
+ ' nxt["dependencies"] = strip_dir(val["dependencies"])',
158
+ " out[k] = nxt",
159
+ " else:",
160
+ " out[k] = val",
161
+ " return out",
162
+ ' if not deep_eq(strip_dir(b.get("dependencies")), strip_dir(h.get("dependencies"))):',
163
+ " return False",
164
+ " return True",
165
+ "",
166
+ "",
167
+ "def unq(s):",
168
+ " s = s.strip()",
169
+ " if len(s) >= 2 and s[0] == s[-1] and s[0] in _Q:",
170
+ " return s[1:-1]",
171
+ " return s",
172
+ "",
173
+ "",
174
+ "def pnpm_root_deps(raw):",
175
+ " out = {}",
176
+ " in_imp = in_root = in_dep = False",
177
+ " cur = None",
178
+ " for line in raw.splitlines():",
179
+ ' if re.match(r"^importers:\\s*$", line):',
180
+ " in_imp, in_root, in_dep, cur = True, False, False, None",
181
+ " continue",
182
+ " if not in_imp:",
183
+ " continue",
184
+ ' if re.match(r"^[^\\s#]", line) and not line.startswith("importers"):',
185
+ " break",
186
+ ' if re.match(r"^ {2}(?:\\.|\'\\.\'|\\"\\.\\"):\\s*$", line):',
187
+ " in_root, in_dep, cur = True, False, None",
188
+ " continue",
189
+ ' if in_root and re.match(r"^ {2}\\S", line) and not re.match(r"^ {2}\\.", line):',
190
+ " in_root, in_dep, cur = False, False, None",
191
+ " continue",
192
+ " if not in_root:",
193
+ " continue",
194
+ " if re.match(",
195
+ ' r"^ {4}(?:dependencies|devDependencies|optionalDependencies|peerDependencies):\\s*$",',
196
+ " line,",
197
+ " ):",
198
+ " in_dep, cur = True, None",
199
+ " continue",
200
+ ' if in_dep and re.match(r"^ {4}\\S", line):',
201
+ " in_dep, cur = False, None",
202
+ " continue",
203
+ " if not in_dep:",
204
+ " continue",
205
+ ' m = re.match(r"^ {6}(.+?):\\s*$", line)',
206
+ " if m:",
207
+ " cur = unq(m.group(1))",
208
+ " continue",
209
+ " if cur:",
210
+ ' vm = re.match(r"^ {8}version:\\s*(.+?)\\s*$", line)',
211
+ " if vm:",
212
+ " out[cur] = unq(vm.group(1))",
213
+ " cur = None",
214
+ " return out",
215
+ "",
216
+ "",
217
+ "def pnpm_packages_by_name(raw):",
218
+ " out = {}",
219
+ " blocks = {}",
220
+ " in_pkg = False",
221
+ " cur = None",
222
+ " buf = []",
223
+ "",
224
+ " def flush():",
225
+ " nonlocal cur, buf",
226
+ " if cur is None:",
227
+ " return",
228
+ ' blocks.setdefault(cur, []).append("\\n".join(buf))',
229
+ " cur, buf = None, []",
230
+ "",
231
+ " for line in raw.splitlines():",
232
+ ' if re.match(r"^packages:\\s*$", line):',
233
+ " flush()",
234
+ " in_pkg = True",
235
+ " continue",
236
+ " if not in_pkg:",
237
+ " continue",
238
+ ' if re.match(r"^[^\\s#]", line) and not line.startswith("packages"):',
239
+ " flush()",
240
+ " break",
241
+ ' m = re.match(r"^ {2}(.+?):\\s*$", line)',
242
+ " if m:",
243
+ " flush()",
244
+ " key = unq(m.group(1))",
245
+ ' at = key.find("@", 1) if key.startswith("@") else key.find("@")',
246
+ " cur = key[:at] if at > 0 else key",
247
+ " buf = [line]",
248
+ " continue",
249
+ " if cur is not None:",
250
+ " buf.append(line)",
251
+ " flush()",
252
+ " for name, blist in blocks.items():",
253
+ ' out[name] = "\\n---\\n".join(sorted(blist))',
254
+ " return out",
255
+ "",
256
+ "",
257
+ "def pnpm_section_by_name(raw, section):",
258
+ " blocks, out = {}, {}",
259
+ " in_sec, cur, buf = False, None, []",
260
+ ' sec_re = re.compile(r"^" + re.escape(section) + r":\\s*$")',
261
+ "",
262
+ " def flush():",
263
+ " nonlocal cur, buf",
264
+ " if cur is None:",
265
+ " return",
266
+ ' blocks.setdefault(cur, []).append("\\n".join(buf))',
267
+ " cur, buf = None, []",
268
+ "",
269
+ " for line in raw.splitlines():",
270
+ " if sec_re.match(line):",
271
+ " flush()",
272
+ " in_sec = True",
273
+ " continue",
274
+ " if not in_sec:",
275
+ " continue",
276
+ ' if re.match(r"^[^\\s#]", line) and not line.startswith(section):',
277
+ " flush()",
278
+ " break",
279
+ ' m = re.match(r"^ {2}(.+?):\\s*$", line)',
280
+ " if m:",
281
+ " flush()",
282
+ " key = unq(m.group(1))",
283
+ ' at = key.find("@", 1) if key.startswith("@") else key.find("@")',
284
+ " cur = key[:at] if at > 0 else key",
285
+ " buf = [line]",
286
+ " continue",
287
+ " if cur is not None:",
288
+ " buf.append(line)",
289
+ " flush()",
290
+ " for name, blist in blocks.items():",
291
+ ' out[name] = "\\n---\\n".join(sorted(blist))',
292
+ " return out",
293
+ "",
294
+ "",
295
+ "def pnpm_ok(base, head):",
296
+ " br, hr = pnpm_root_deps(base), pnpm_root_deps(head)",
297
+ " if not only_dir_diff(br, hr):",
298
+ " return False",
299
+ ' for section in ("packages", "snapshots"):',
300
+ " bp, hp = pnpm_section_by_name(base, section), pnpm_section_by_name(head, section)",
301
+ " for name in set(bp) | set(hp):",
302
+ " if is_dir_key(name):",
303
+ " continue",
304
+ " if bp.get(name) != hp.get(name):",
305
+ " return False",
306
+ " return True",
307
+ "",
308
+ "",
309
+ "def yarn_blocks(raw):",
310
+ " blocks = {}",
311
+ " names, buf = [], []",
312
+ "",
313
+ " def flush():",
314
+ " nonlocal names, buf",
315
+ " if not names:",
316
+ " return",
317
+ ' body = "\\n".join(buf)',
318
+ " for n in names:",
319
+ " blocks[n] = body",
320
+ " names, buf = [], []",
321
+ "",
322
+ " for line in raw.splitlines():",
323
+ ' if line == "" or line.startswith("#"):',
324
+ ' if names and line == "":',
325
+ " flush()",
326
+ " continue",
327
+ ' if not re.match(r"^\\s", line) and line.endswith(":"):',
328
+ " flush()",
329
+ " header = line[:-1]",
330
+ " names = []",
331
+ ' for part in header.split(","):',
332
+ " p = part.strip()",
333
+ " if len(p) >= 2 and p[0] == p[-1] and p[0] in _Q:",
334
+ " p = p[1:-1]",
335
+ ' at = p.find("@", 1) if p.startswith("@") else p.find("@")',
336
+ " names.append(p[:at] if at > 0 else p)",
337
+ " buf = [line]",
338
+ " continue",
339
+ " if names:",
340
+ " buf.append(line)",
341
+ " flush()",
342
+ " return blocks",
343
+ "",
344
+ "",
345
+ "def yarn_ok(base, head):",
346
+ " bb, hb = yarn_blocks(base), yarn_blocks(head)",
347
+ " for name in set(bb) | set(hb):",
348
+ " if is_dir_key(name):",
349
+ " continue",
350
+ " if bb.get(name) != hb.get(name):",
351
+ " return False",
352
+ " return True",
353
+ "",
354
+ "",
355
+ "def main(argv: list[str] | None = None) -> int:",
356
+ " args = list(sys.argv[1:] if argv is None else argv)",
357
+ " if len(args) < 2:",
358
+ ' print("usage: core_guard_pin_content <base_sha> <head_sha>", file=sys.stderr)',
359
+ " return 2",
360
+ " base_sha, head_sha = args[0], args[1]",
361
+ " paths = changed_names(base_sha, head_sha)",
362
+ ' core = [p for p in paths if p == ".deft/core" or p.startswith(".deft/core/")]',
363
+ " if not core:",
364
+ " return 0",
365
+ " checks = {",
366
+ ' "package.json": pkg_pin_only,',
367
+ ' "package-lock.json": npm_lock_ok,',
368
+ ' "pnpm-lock.yaml": pnpm_ok,',
369
+ ' "yarn.lock": yarn_ok,',
370
+ " }",
371
+ " bad = []",
372
+ " for path, fn in checks.items():",
373
+ " if path not in paths:",
374
+ " continue",
375
+ " if not fn(git_show(base_sha, path), git_show(head_sha, path)):",
376
+ " bad.append(path)",
377
+ " if bad:",
378
+ " print(",
379
+ ' "::error title=deft-core guard (#3193)::package/lock co-travel with "',
380
+ ' ".deft/core/** must be @deftai/directive* pin-only + lock follow-through. "',
381
+ ' "Rejected: " + ", ".join(bad)',
382
+ " )",
383
+ ' print("--- content-rejected pin/lock paths ---")',
384
+ ' print("\\n".join(bad))',
385
+ " return 1",
386
+ ' print("OK: package/lock content is Directive pin unit (#3193).")',
387
+ " return 0",
388
+ "",
389
+ "",
390
+ 'if __name__ == "__main__":',
391
+ " sys.exit(main())",
392
+ ].join("\n") + "\n";
393
+ //# sourceMappingURL=core-guard-pin-content.js.map