@deftai/directive-core 0.103.0 → 0.105.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 (255) hide show
  1. package/dist/authz/classify.js +682 -35
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/gate-lists.d.ts +3 -2
  5. package/dist/check/gate-lists.js +5 -2
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.d.ts +1 -1
  9. package/dist/check/named-cause.js +33 -3
  10. package/dist/check/session-completed-ac.d.ts +39 -0
  11. package/dist/check/session-completed-ac.js +198 -0
  12. package/dist/codebase/default-extractor.d.ts +5 -0
  13. package/dist/codebase/default-extractor.js +7 -15
  14. package/dist/deposit/copy-tree.js +60 -0
  15. package/dist/deposit/python-free.js +26 -18
  16. package/dist/doctor/checks.d.ts +8 -0
  17. package/dist/doctor/checks.js +37 -25
  18. package/dist/doctor/doctor-state.d.ts +4 -0
  19. package/dist/doctor/doctor-state.js +7 -1
  20. package/dist/doctor/main.js +9 -14
  21. package/dist/doctor/types.d.ts +9 -0
  22. package/dist/fs/contained-write.d.ts +115 -1
  23. package/dist/fs/contained-write.js +242 -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 +2 -0
  31. package/dist/index.js +3 -0
  32. package/dist/init-deposit/agent-hooks.js +17 -15
  33. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  34. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  35. package/dist/init-deposit/gitignore.js +9 -0
  36. package/dist/init-deposit/hygiene.d.ts +47 -11
  37. package/dist/init-deposit/hygiene.js +172 -37
  38. package/dist/init-deposit/legacy-detect.js +6 -2
  39. package/dist/init-deposit/refresh.d.ts +21 -1
  40. package/dist/init-deposit/refresh.js +106 -9
  41. package/dist/init-deposit/scaffold.js +28 -279
  42. package/dist/init-deposit/slash-deposit.js +13 -7
  43. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  44. package/dist/init-deposit/xbrief-projections.js +24 -11
  45. package/dist/intake/clause-derivation.d.ts +74 -0
  46. package/dist/intake/clause-derivation.js +409 -0
  47. package/dist/intake/index.d.ts +1 -0
  48. package/dist/intake/index.js +1 -0
  49. package/dist/intake/issue-ingest.d.ts +3 -1
  50. package/dist/intake/issue-ingest.js +36 -43
  51. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  52. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  53. package/dist/literal-acceptance/capture.d.ts +10 -0
  54. package/dist/literal-acceptance/capture.js +224 -75
  55. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  56. package/dist/literal-acceptance/evaluate.js +70 -2
  57. package/dist/literal-acceptance/index.d.ts +3 -3
  58. package/dist/literal-acceptance/index.js +3 -3
  59. package/dist/literal-acceptance/safety.d.ts +41 -0
  60. package/dist/literal-acceptance/safety.js +126 -2
  61. package/dist/literal-acceptance/types.d.ts +8 -0
  62. package/dist/orphan-active/evaluate.d.ts +7 -1
  63. package/dist/orphan-active/evaluate.js +102 -23
  64. package/dist/platform/platform-capabilities.js +2 -3
  65. package/dist/policy/base-branch.d.ts +37 -0
  66. package/dist/policy/base-branch.js +114 -0
  67. package/dist/policy/branch-sync.d.ts +80 -0
  68. package/dist/policy/branch-sync.js +211 -0
  69. package/dist/policy/ceremony-dial-escalation.js +1 -0
  70. package/dist/policy/ceremony-dial.js +4 -14
  71. package/dist/policy/delivery-branch.d.ts +5 -3
  72. package/dist/policy/delivery-branch.js +7 -3
  73. package/dist/policy/file-size-thresholds.d.ts +24 -0
  74. package/dist/policy/file-size-thresholds.js +23 -0
  75. package/dist/policy/forge-outage-retry.d.ts +47 -0
  76. package/dist/policy/forge-outage-retry.js +157 -0
  77. package/dist/policy/index.d.ts +7 -0
  78. package/dist/policy/index.js +66 -7
  79. package/dist/policy/project-invariants.d.ts +50 -0
  80. package/dist/policy/project-invariants.js +205 -0
  81. package/dist/policy/sync-default.d.ts +129 -0
  82. package/dist/policy/sync-default.js +452 -0
  83. package/dist/policy/sync-max-files.d.ts +93 -0
  84. package/dist/policy/sync-max-files.js +192 -0
  85. package/dist/pr-monitor/absent-required.d.ts +11 -0
  86. package/dist/pr-monitor/absent-required.js +61 -0
  87. package/dist/pr-monitor/constants.d.ts +7 -0
  88. package/dist/pr-monitor/constants.js +7 -0
  89. package/dist/pr-monitor/index.d.ts +2 -1
  90. package/dist/pr-monitor/index.js +2 -1
  91. package/dist/pr-monitor/main.js +4 -0
  92. package/dist/pr-monitor/monitor.js +24 -1
  93. package/dist/pr-wait-mergeable/classify.js +6 -0
  94. package/dist/preflight/evaluate.d.ts +9 -0
  95. package/dist/preflight/evaluate.js +49 -1
  96. package/dist/preflight/index.d.ts +5 -1
  97. package/dist/preflight/index.js +2 -0
  98. package/dist/preflight/intended-placement.d.ts +62 -0
  99. package/dist/preflight/intended-placement.js +275 -0
  100. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  101. package/dist/preflight/project-invariants-gate.js +82 -0
  102. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  103. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  104. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  105. package/dist/product-first-done-gate/acceptance.js +79 -8
  106. package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
  107. package/dist/product-first-done-gate/empty-resolution.js +13 -2
  108. package/dist/product-first-done-gate/evaluate.d.ts +29 -0
  109. package/dist/product-first-done-gate/evaluate.js +307 -33
  110. package/dist/product-first-done-gate/index.d.ts +3 -1
  111. package/dist/product-first-done-gate/index.js +2 -1
  112. package/dist/release/build-dist.d.ts +101 -0
  113. package/dist/release/build-dist.js +428 -46
  114. package/dist/run-summary/emit.d.ts +50 -4
  115. package/dist/run-summary/emit.js +262 -26
  116. package/dist/run-summary/index.d.ts +3 -2
  117. package/dist/run-summary/index.js +2 -1
  118. package/dist/run-summary/session-id.d.ts +21 -0
  119. package/dist/run-summary/session-id.js +92 -0
  120. package/dist/run-summary/share.d.ts +1 -1
  121. package/dist/run-summary/share.js +2 -2
  122. package/dist/run-summary/types.d.ts +38 -6
  123. package/dist/run-summary/types.js +1 -0
  124. package/dist/scope/acceptance-activate-gate.d.ts +1 -1
  125. package/dist/scope/acceptance-activate-gate.js +4 -4
  126. package/dist/scope/acceptance-evidence.d.ts +27 -0
  127. package/dist/scope/acceptance-evidence.js +66 -0
  128. package/dist/scope/capacity-stamp.d.ts +2 -0
  129. package/dist/scope/capacity-stamp.js +4 -0
  130. package/dist/scope/delivery-evidence.d.ts +9 -3
  131. package/dist/scope/delivery-evidence.js +39 -27
  132. package/dist/scope/index.d.ts +1 -0
  133. package/dist/scope/index.js +1 -0
  134. package/dist/scope/parent-lineage.js +7 -6
  135. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  136. package/dist/scope/project-invariant-coverage.js +322 -0
  137. package/dist/scope/transition.js +66 -4
  138. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  139. package/dist/scope-provenance/compare-intent.js +233 -0
  140. package/dist/scope-provenance/digest.d.ts +18 -1
  141. package/dist/scope-provenance/digest.js +16 -11
  142. package/dist/scope-provenance/evaluate.d.ts +5 -1
  143. package/dist/scope-provenance/evaluate.js +64 -4
  144. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  145. package/dist/scope-provenance/extract-intent.js +262 -0
  146. package/dist/scope-provenance/index.d.ts +7 -1
  147. package/dist/scope-provenance/index.js +7 -1
  148. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  149. package/dist/scope-provenance/intent-digest.js +33 -0
  150. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  151. package/dist/scope-provenance/intent-evaluate.js +149 -0
  152. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  153. package/dist/scope-provenance/json-tokenizer.js +225 -0
  154. package/dist/scope-provenance/known-machine.d.ts +38 -0
  155. package/dist/scope-provenance/known-machine.js +157 -0
  156. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  157. package/dist/scope-provenance/mint-artifacts.js +425 -0
  158. package/dist/session/ac-pass-banking.d.ts +4 -36
  159. package/dist/session/ac-pass-banking.js +30 -77
  160. package/dist/session/ac-pass-reuse.d.ts +53 -0
  161. package/dist/session/ac-pass-reuse.js +95 -0
  162. package/dist/session/ceremony-dial-evidence.d.ts +42 -0
  163. package/dist/session/ceremony-dial-evidence.js +194 -0
  164. package/dist/session/git.js +19 -4
  165. package/dist/session/index.d.ts +5 -0
  166. package/dist/session/index.js +5 -0
  167. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  168. package/dist/session/occupancy-stress-worker.js +36 -0
  169. package/dist/session/occupancy.d.ts +97 -0
  170. package/dist/session/occupancy.js +422 -0
  171. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  172. package/dist/session/product-state-hash.d.ts +26 -0
  173. package/dist/session/product-state-hash.js +683 -0
  174. package/dist/session/session-ready.d.ts +2 -0
  175. package/dist/session/session-ready.js +52 -30
  176. package/dist/session/session-start.d.ts +7 -0
  177. package/dist/session/session-start.js +88 -5
  178. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  179. package/dist/session/verify-ac-session-cache.js +126 -0
  180. package/dist/session/verify-session-ritual.d.ts +5 -0
  181. package/dist/session/verify-session-ritual.js +9 -0
  182. package/dist/slash/openclaw-deposit.js +13 -9
  183. package/dist/slice/constants.d.ts +2 -2
  184. package/dist/slice/constants.js +2 -2
  185. package/dist/slice/existing.js +21 -4
  186. package/dist/slice/lock.d.ts +16 -1
  187. package/dist/slice/lock.js +137 -11
  188. package/dist/slice/record.d.ts +8 -5
  189. package/dist/slice/record.js +21 -13
  190. package/dist/story-ready/evaluate.d.ts +2 -0
  191. package/dist/story-ready/evaluate.js +13 -0
  192. package/dist/swarm/complete-cohort.d.ts +3 -0
  193. package/dist/swarm/complete-cohort.js +59 -11
  194. package/dist/swarm/finalize-cohort.js +33 -2
  195. package/dist/swarm/launch.d.ts +22 -0
  196. package/dist/swarm/launch.js +155 -0
  197. package/dist/swarm/routing-set-cli.d.ts +11 -1
  198. package/dist/swarm/routing-set-cli.js +13 -3
  199. package/dist/swarm/routing-verify.d.ts +12 -0
  200. package/dist/swarm/routing-verify.js +59 -20
  201. package/dist/swarm/routing.d.ts +14 -0
  202. package/dist/swarm/routing.js +53 -3
  203. package/dist/telemetry-coverage/evaluate.d.ts +42 -0
  204. package/dist/telemetry-coverage/evaluate.js +148 -0
  205. package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
  206. package/dist/telemetry-coverage/fake-trial.js +204 -0
  207. package/dist/telemetry-coverage/index.d.ts +5 -0
  208. package/dist/telemetry-coverage/index.js +5 -0
  209. package/dist/telemetry-coverage/kinds.d.ts +19 -0
  210. package/dist/telemetry-coverage/kinds.js +54 -0
  211. package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
  212. package/dist/telemetry-coverage/scan-callers.js +168 -0
  213. package/dist/triage/actions/index.js +4 -1
  214. package/dist/ts-check-lane/index.d.ts +3 -0
  215. package/dist/ts-check-lane/index.js +2 -0
  216. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  217. package/dist/ts-check-lane/progress-reporter.js +38 -0
  218. package/dist/ts-check-lane/progress.d.ts +32 -0
  219. package/dist/ts-check-lane/progress.js +57 -0
  220. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  221. package/dist/ts-check-lane/run-lane.js +8 -6
  222. package/dist/validate-content/validate-links.d.ts +8 -0
  223. package/dist/validate-content/validate-links.js +9 -15
  224. package/dist/value/feedback-file.js +12 -2
  225. package/dist/vbrief-build/index.d.ts +1 -0
  226. package/dist/vbrief-build/index.js +1 -0
  227. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  228. package/dist/vbrief-build/project-invariants-io.js +41 -0
  229. package/dist/vbrief-reconcile/graph.js +18 -1
  230. package/dist/vbrief-reconcile/index.d.ts +3 -1
  231. package/dist/vbrief-reconcile/index.js +2 -1
  232. package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
  233. package/dist/vbrief-reconcile/origin-freshness.js +221 -0
  234. package/dist/vbrief-reconcile/types.d.ts +13 -0
  235. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  236. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  237. package/dist/vbrief-validate/index.d.ts +1 -1
  238. package/dist/vbrief-validate/index.js +1 -1
  239. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  240. package/dist/vbrief-validate/plan-hooks.js +68 -0
  241. package/dist/verify-ac/evaluate.d.ts +7 -1
  242. package/dist/verify-ac/evaluate.js +34 -26
  243. package/dist/verify-ac/flag.d.ts +4 -0
  244. package/dist/verify-ac/flag.js +16 -0
  245. package/dist/verify-ac/index.d.ts +2 -2
  246. package/dist/verify-ac/index.js +2 -2
  247. package/dist/verify-source/contained-writes.js +2 -0
  248. package/dist/verify-source/index.d.ts +1 -0
  249. package/dist/verify-source/index.js +1 -0
  250. package/dist/verify-source/verify-stubs.d.ts +15 -1
  251. package/dist/verify-source/verify-stubs.js +20 -16
  252. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  253. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  254. package/dist/xbrief-migrate/migrate-project.js +2 -5
  255. package/package.json +3 -3
@@ -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
@@ -13,6 +13,7 @@ export * as authz from "./authz/index.js";
13
13
  export * as branch from "./branch/index.js";
14
14
  export * as cache from "./cache/index.js";
15
15
  export * as capacity from "./capacity/index.js";
16
+ export * as check from "./check/index.js";
16
17
  export * as codebase from "./codebase/index.js";
17
18
  export * as consumerCheckContract from "./consumer-check-contract/index.js";
18
19
  export * from "./coverage-hotspots/index.js";
@@ -29,6 +30,7 @@ export * as events from "./events/attribution-ledger.js";
29
30
  export * from "./forward-coverage/evaluate.js";
30
31
  export * as freshness from "./freshness/index.js";
31
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";
32
34
  export { assertDestinationNotSymlink, assertDirectoryNotSymlink, assertProjectionContained, assertWriteTargetSafe, PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, walkDirectoryRejectSymlinks, } from "./fs/projection-containment.js";
33
35
  export * as handoffEvidence from "./handoff-evidence/index.js";
34
36
  export * as intake from "./intake/index.js";
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export * as authz from "./authz/index.js";
13
13
  export * as branch from "./branch/index.js";
14
14
  export * as cache from "./cache/index.js";
15
15
  export * as capacity from "./capacity/index.js";
16
+ export * as check from "./check/index.js";
16
17
  export * as codebase from "./codebase/index.js";
17
18
  export * as consumerCheckContract from "./consumer-check-contract/index.js";
18
19
  export * from "./coverage-hotspots/index.js";
@@ -30,6 +31,8 @@ export * from "./forward-coverage/evaluate.js";
30
31
  export * as freshness from "./freshness/index.js";
31
32
  // #2951 Phase 1: contained-write API for product sinks.
32
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";
33
36
  export { assertDestinationNotSymlink, assertDirectoryNotSymlink, assertProjectionContained, assertWriteTargetSafe, PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, walkDirectoryRejectSymlinks, } from "./fs/projection-containment.js";
34
37
  export * as handoffEvidence from "./handoff-evidence/index.js";
35
38
  export * as intake from "./intake/index.js";
@@ -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";
@@ -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
@@ -29,7 +29,16 @@ export const CANONICAL_GITIGNORE_BASELINE = [
29
29
  ".deft/.cli/",
30
30
  ".deft/ritual-state.json",
31
31
  ".deft/last-session.json",
32
+ ".deft/occupancy.json",
32
33
  ".deft/routing.local.json",
34
+ // Agent-host working state (#3502). Selective, NEVER blanket `.claude/`:
35
+ // `.claude/settings.json`, `.claude/skills/` and `.claude/commands/` are
36
+ // MANAGED DEPOSITS (agent-hooks.ts / skill-discovery-hosts.ts) and must stay
37
+ // trackable -- same #1144 hybrid rule that forbids blanket `.eval/`.
38
+ // Un-ignored agent worktrees leave a permanently dirty tree, which refuses
39
+ // release Step 1 for every consumer that dispatches subagents.
40
+ ".claude/worktrees/",
41
+ ".claude/settings.local.json",
33
42
  // #3282: opt-in default run-summary JSONL at repo root (collectible; must not dirty trees).
34
43
  ".deft-run-summary.json",
35
44
  // Temporary test/local kill-switch — must stay untracked (#3039).