@amsterdamdatalabs/adl-agent-control-darwin-arm64 0.1.4

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 (741) hide show
  1. package/bin/adl +4 -0
  2. package/bin/cursor-settings +14 -0
  3. package/bin/default-agent-file +14 -0
  4. package/bin/git +670 -0
  5. package/bin/git-ado +425 -0
  6. package/bin/global-agents +14 -0
  7. package/bin/global-surface-sweep +14 -0
  8. package/bin/health-check +38 -0
  9. package/bin/kimi-hooks +14 -0
  10. package/bin/lean-ctx-observe-strip +361 -0
  11. package/bin/lib/bun-compile-build.sh +34 -0
  12. package/bin/lib/lean-ctx-observe-hooks.sh +634 -0
  13. package/bin/managed-settings +14 -0
  14. package/bin/opencode-hooks +14 -0
  15. package/bin/reconcile +2648 -0
  16. package/bin/scratch-reap +14 -0
  17. package/bin/shell-path-policy +64 -0
  18. package/bin/surface-policy +14 -0
  19. package/bin/sync-and-reconcile +215 -0
  20. package/bin/update-azure-agent-pool +29 -0
  21. package/bin/validate-manifest +171 -0
  22. package/bin/vscode-settings +14 -0
  23. package/package.json +44 -0
  24. package/packages/agent-hooks/README.md +411 -0
  25. package/packages/agent-hooks/assets/hooks.json +97 -0
  26. package/packages/agent-hooks/assets/opencode-plugin.template.ts +224 -0
  27. package/packages/agent-hooks/bunfig.toml +27 -0
  28. package/packages/agent-hooks/deploy-profile.json +53 -0
  29. package/packages/agent-hooks/deploy-profile.schema.json +120 -0
  30. package/packages/agent-hooks/package.json +23 -0
  31. package/packages/agent-hooks/src/cli/args.ts +99 -0
  32. package/packages/agent-hooks/src/cli/doctor.ts +241 -0
  33. package/packages/agent-hooks/src/cli/rules.ts +133 -0
  34. package/packages/agent-hooks/src/deploy/deploy.ts +181 -0
  35. package/packages/agent-hooks/src/deploy/doctor.ts +158 -0
  36. package/packages/agent-hooks/src/deploy/index.ts +17 -0
  37. package/packages/agent-hooks/src/deploy/json-merge.ts +115 -0
  38. package/packages/agent-hooks/src/deploy/legacy-lock.ts +213 -0
  39. package/packages/agent-hooks/src/deploy/list.ts +117 -0
  40. package/packages/agent-hooks/src/deploy/lock.ts +65 -0
  41. package/packages/agent-hooks/src/deploy/opencode-template.ts +74 -0
  42. package/packages/agent-hooks/src/deploy/profile.ts +117 -0
  43. package/packages/agent-hooks/src/deploy/render.ts +162 -0
  44. package/packages/agent-hooks/src/deploy/source.ts +84 -0
  45. package/packages/agent-hooks/src/gating/config.ts +514 -0
  46. package/packages/agent-hooks/src/gating/root.ts +117 -0
  47. package/packages/agent-hooks/src/hooks/briefing/handler.ts +22 -0
  48. package/packages/agent-hooks/src/hooks/briefing/index.ts +320 -0
  49. package/packages/agent-hooks/src/hooks/contract.ts +112 -0
  50. package/packages/agent-hooks/src/hooks/crg/handler.ts +22 -0
  51. package/packages/agent-hooks/src/hooks/crg/index.ts +209 -0
  52. package/packages/agent-hooks/src/hooks/lean-ctx/handler.ts +22 -0
  53. package/packages/agent-hooks/src/hooks/lean-ctx/index.ts +173 -0
  54. package/packages/agent-hooks/src/hooks/lean-ctx/mapping.ts +121 -0
  55. package/packages/agent-hooks/src/hooks/lean-ctx/scope-rewrite.ts +207 -0
  56. package/packages/agent-hooks/src/hooks/log.ts +233 -0
  57. package/packages/agent-hooks/src/hooks/user-prompt-submit/config.ts +58 -0
  58. package/packages/agent-hooks/src/hooks/user-prompt-submit/constants.ts +149 -0
  59. package/packages/agent-hooks/src/hooks/user-prompt-submit/handler.ts +72 -0
  60. package/packages/agent-hooks/src/hooks/user-prompt-submit/index.ts +203 -0
  61. package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +212 -0
  62. package/packages/agent-hooks/src/hooks/user-prompt-submit/skill-roots.ts +73 -0
  63. package/packages/agent-hooks/src/hooks/user-prompt-submit/skills.ts +98 -0
  64. package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +115 -0
  65. package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +174 -0
  66. package/packages/agent-hooks/src/hosts/impl/claude.ts +47 -0
  67. package/packages/agent-hooks/src/hosts/impl/codex.ts +47 -0
  68. package/packages/agent-hooks/src/hosts/impl/cursor.ts +40 -0
  69. package/packages/agent-hooks/src/hosts/impl/kimi.ts +47 -0
  70. package/packages/agent-hooks/src/hosts/impl/opencode.ts +54 -0
  71. package/packages/agent-hooks/src/hosts/registry.ts +29 -0
  72. package/packages/agent-hooks/src/hosts/spec.ts +113 -0
  73. package/packages/agent-hooks/src/lean-ctx/env.ts +106 -0
  74. package/packages/agent-hooks/src/lean-ctx/mcp.ts +140 -0
  75. package/packages/agent-hooks/src/lib/spawn-with-timeout.ts +120 -0
  76. package/packages/agent-hooks/src/log/writer.ts +230 -0
  77. package/packages/agent-hooks/src/main.ts +269 -0
  78. package/packages/agent-hooks/src/pre-tool-use/handler.ts +549 -0
  79. package/packages/agent-hooks/src/rules/engine.ts +293 -0
  80. package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +51 -0
  81. package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +320 -0
  82. package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +249 -0
  83. package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +258 -0
  84. package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +731 -0
  85. package/packages/agent-hooks/src/rules/impl/adl-identity.ts +128 -0
  86. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +179 -0
  87. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +65 -0
  88. package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +156 -0
  89. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +121 -0
  90. package/packages/agent-hooks/src/rules/impl/adl-session-required.ts +403 -0
  91. package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +92 -0
  92. package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +74 -0
  93. package/packages/agent-hooks/src/rules/impl/advisory-coverage.ts +43 -0
  94. package/packages/agent-hooks/src/rules/impl/advisory-finder.ts +62 -0
  95. package/packages/agent-hooks/src/rules/impl/agent-secrets-stay-unread.ts +173 -0
  96. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +508 -0
  97. package/packages/agent-hooks/src/rules/impl/fs-canonical-checkout.ts +86 -0
  98. package/packages/agent-hooks/src/rules/impl/fs-git-internals.ts +75 -0
  99. package/packages/agent-hooks/src/rules/impl/fs-mv.ts +66 -0
  100. package/packages/agent-hooks/src/rules/impl/fs-rm.ts +83 -0
  101. package/packages/agent-hooks/src/rules/impl/fs-trash.ts +140 -0
  102. package/packages/agent-hooks/src/rules/impl/git-core.ts +101 -0
  103. package/packages/agent-hooks/src/rules/impl/git-helpers.ts +408 -0
  104. package/packages/agent-hooks/src/rules/impl/git-history.ts +77 -0
  105. package/packages/agent-hooks/src/rules/impl/git-paths.ts +139 -0
  106. package/packages/agent-hooks/src/rules/impl/git-plumbing.ts +167 -0
  107. package/packages/agent-hooks/src/rules/impl/git-protected-tree.ts +132 -0
  108. package/packages/agent-hooks/src/rules/impl/git-transitions.ts +292 -0
  109. package/packages/agent-hooks/src/rules/impl/git-verify.ts +95 -0
  110. package/packages/agent-hooks/src/rules/impl/git-worktree.ts +265 -0
  111. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +76 -0
  112. package/packages/agent-hooks/src/rules/impl/messages.ts +128 -0
  113. package/packages/agent-hooks/src/rules/impl/parse-uncertain.ts +143 -0
  114. package/packages/agent-hooks/src/rules/impl/pr-completion.ts +35 -0
  115. package/packages/agent-hooks/src/rules/impl/shell-remote-script-exec.ts +63 -0
  116. package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +283 -0
  117. package/packages/agent-hooks/src/rules/impl/tool-bun-test.ts +81 -0
  118. package/packages/agent-hooks/src/rules/impl/tool-cargo.ts +66 -0
  119. package/packages/agent-hooks/src/rules/impl/tool-chmod-hook.ts +42 -0
  120. package/packages/agent-hooks/src/rules/impl/tool-find.ts +82 -0
  121. package/packages/agent-hooks/src/rules/impl/tool-frontend.ts +83 -0
  122. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +93 -0
  123. package/packages/agent-hooks/src/rules/impl/tool-lean-ctx-global.ts +594 -0
  124. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +77 -0
  125. package/packages/agent-hooks/src/rules/ports.ts +178 -0
  126. package/packages/agent-hooks/src/rules/registry.ts +233 -0
  127. package/packages/agent-hooks/src/rules/shell/git-identity.ts +1415 -0
  128. package/packages/agent-hooks/src/rules/shell/git-internals.ts +186 -0
  129. package/packages/agent-hooks/src/rules/shell/invocation.ts +1083 -0
  130. package/packages/agent-hooks/src/rules/shell/piped-script.ts +201 -0
  131. package/packages/agent-hooks/src/rules/shell/prefixes.ts +214 -0
  132. package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +108 -0
  133. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +149 -0
  134. package/packages/agent-hooks/src/rules/shell/split.ts +179 -0
  135. package/packages/agent-hooks/src/rules/shell/substitution.ts +394 -0
  136. package/packages/agent-hooks/src/rules/shell/token.ts +180 -0
  137. package/packages/agent-hooks/src/rules/shell/xargs.ts +81 -0
  138. package/packages/agent-hooks/src/rules/spec.ts +152 -0
  139. package/packages/agent-hooks/src/surface-policy.ts +37 -0
  140. package/packages/agent-hooks/tsconfig.json +23 -0
  141. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +107 -0
  142. package/packages/agent-settings/assets/agents/architect.toml +30 -0
  143. package/packages/agent-settings/assets/agents/code-reviewer.toml +27 -0
  144. package/packages/agent-settings/assets/agents/critic.toml +35 -0
  145. package/packages/agent-settings/assets/agents/executor.toml +28 -0
  146. package/packages/agent-settings/assets/agents/explore.toml +25 -0
  147. package/packages/agent-settings/assets/agents/planner.toml +29 -0
  148. package/packages/agent-settings/assets/lean-ctx/config.toml +39 -0
  149. package/packages/agent-settings/assets/lean-ctx/gitignore +6 -0
  150. package/packages/agent-settings/assets/lean-ctx/layout.toml +5 -0
  151. package/packages/agent-settings/assets/mcp.json +20 -0
  152. package/packages/agent-settings/managed/README.md +331 -0
  153. package/packages/agent-settings/managed/claude-managed-settings.json +150 -0
  154. package/packages/agent-settings/managed/codex-requirements.toml +105 -0
  155. package/packages/agent-settings/protected.json +470 -0
  156. package/packages/agent-settings/src/deploy/agent-render.ts +94 -0
  157. package/packages/agent-settings/src/deploy/agent-toml.ts +76 -0
  158. package/packages/agent-settings/src/deploy/agents-deploy.ts +129 -0
  159. package/packages/agent-settings/src/deploy/doctor.ts +201 -0
  160. package/packages/agent-settings/src/deploy/index.ts +33 -0
  161. package/packages/agent-settings/src/deploy/json-pretty.ts +66 -0
  162. package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +138 -0
  163. package/packages/agent-settings/src/deploy/legacy-lock.ts +286 -0
  164. package/packages/agent-settings/src/deploy/list.ts +135 -0
  165. package/packages/agent-settings/src/deploy/lock.ts +112 -0
  166. package/packages/agent-settings/src/deploy/mcp-deploy.ts +175 -0
  167. package/packages/agent-settings/src/deploy/mcp-inspect.ts +56 -0
  168. package/packages/agent-settings/src/deploy/mcp-merge.ts +164 -0
  169. package/packages/agent-settings/src/deploy/mcp-render.ts +80 -0
  170. package/packages/agent-settings/src/deploy/mcp-source.ts +43 -0
  171. package/packages/agent-settings/src/deploy/profile.ts +296 -0
  172. package/packages/agent-settings/src/deploy/settings.ts +65 -0
  173. package/packages/agent-settings/src/global-agents.ts +230 -0
  174. package/packages/agent-settings/src/global-surface-sweep.ts +2179 -0
  175. package/packages/agent-settings/src/kimi-hooks.ts +455 -0
  176. package/packages/agent-settings/src/opencode-hooks.ts +76 -0
  177. package/packages/agent-settings/src/surface-policy.ts +441 -0
  178. package/packages/agent-settings/surface-policy.json +20 -0
  179. package/packages/agent-skills/assets/code-review/SKILL.md +100 -0
  180. package/packages/agent-skills/assets/codebase-design/SKILL.md +98 -0
  181. package/packages/agent-skills/assets/codebase-design/references/DEEPENING.md +37 -0
  182. package/packages/agent-skills/assets/codebase-design/references/DESIGN-IT-TWICE.md +44 -0
  183. package/packages/agent-skills/assets/codebase-design/references/TESTABILITY.md +29 -0
  184. package/packages/agent-skills/assets/debug-issue/SKILL.md +40 -0
  185. package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +100 -0
  186. package/packages/agent-skills/assets/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +34 -0
  187. package/packages/agent-skills/assets/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  188. package/packages/agent-skills/assets/domain-modeling/SKILL.md +86 -0
  189. package/packages/agent-skills/assets/domain-modeling/references/ADR-FORMAT.md +47 -0
  190. package/packages/agent-skills/assets/domain-modeling/references/CONTEXT-FORMAT.md +60 -0
  191. package/packages/agent-skills/assets/explore-codebase/SKILL.md +41 -0
  192. package/packages/agent-skills/assets/handoff/SKILL.md +28 -0
  193. package/packages/agent-skills/assets/implement/SKILL.md +28 -0
  194. package/packages/agent-skills/assets/refactor-safely/SKILL.md +44 -0
  195. package/packages/agent-skills/assets/review-changes/SKILL.md +45 -0
  196. package/packages/agent-skills/assets/tdd/SKILL.md +49 -0
  197. package/packages/agent-skills/assets/tdd/references/mocking.md +59 -0
  198. package/packages/agent-skills/assets/tdd/references/tests.md +77 -0
  199. package/packages/agent-skills/assets/testing/SKILL.md +54 -0
  200. package/packages/agent-skills/assets/to-feature/SKILL.md +91 -0
  201. package/packages/agent-skills/assets/to-feature/references/FEATURE-SPEC-TEMPLATE.md +88 -0
  202. package/packages/agent-skills/assets/to-workitems/SKILL.md +90 -0
  203. package/packages/agent-skills/assets/to-workitems/references/AZURE-DEVOPS-HIERARCHY.md +118 -0
  204. package/packages/agent-skills/assets/to-workitems/references/TICKET-TEMPLATES.md +70 -0
  205. package/packages/agent-skills/assets/wayfinder/SKILL.md +100 -0
  206. package/packages/agent-skills/assets/wayfinder/references/TEMPLATES.md +35 -0
  207. package/packages/agent-skills/assets/wayfinder/references/TICKET-TYPES.md +8 -0
  208. package/packages/agent-skills/deploy-profile.json +9 -0
  209. package/packages/agent-skills/deploy-profile.schema.json +45 -0
  210. package/packages/agent-skills/dist/deploy.d.ts +27 -0
  211. package/packages/agent-skills/dist/deploy.d.ts.map +1 -0
  212. package/packages/agent-skills/dist/deploy.js +186 -0
  213. package/packages/agent-skills/dist/deploy.js.map +1 -0
  214. package/packages/agent-skills/dist/doctor.d.ts +24 -0
  215. package/packages/agent-skills/dist/doctor.d.ts.map +1 -0
  216. package/packages/agent-skills/dist/doctor.js +141 -0
  217. package/packages/agent-skills/dist/doctor.js.map +1 -0
  218. package/packages/agent-skills/dist/fs-utils.d.ts +8 -0
  219. package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -0
  220. package/packages/agent-skills/dist/fs-utils.js +40 -0
  221. package/packages/agent-skills/dist/fs-utils.js.map +1 -0
  222. package/packages/agent-skills/dist/index.d.ts +8 -0
  223. package/packages/agent-skills/dist/index.d.ts.map +1 -0
  224. package/packages/agent-skills/dist/index.js +8 -0
  225. package/packages/agent-skills/dist/index.js.map +1 -0
  226. package/packages/agent-skills/dist/legacy-lock.d.ts +44 -0
  227. package/packages/agent-skills/dist/legacy-lock.d.ts.map +1 -0
  228. package/packages/agent-skills/dist/legacy-lock.js +121 -0
  229. package/packages/agent-skills/dist/legacy-lock.js.map +1 -0
  230. package/packages/agent-skills/dist/list.d.ts +23 -0
  231. package/packages/agent-skills/dist/list.d.ts.map +1 -0
  232. package/packages/agent-skills/dist/list.js +75 -0
  233. package/packages/agent-skills/dist/list.js.map +1 -0
  234. package/packages/agent-skills/dist/manifest.d.ts +15 -0
  235. package/packages/agent-skills/dist/manifest.d.ts.map +1 -0
  236. package/packages/agent-skills/dist/manifest.js +57 -0
  237. package/packages/agent-skills/dist/manifest.js.map +1 -0
  238. package/packages/agent-skills/dist/profile.d.ts +41 -0
  239. package/packages/agent-skills/dist/profile.d.ts.map +1 -0
  240. package/packages/agent-skills/dist/profile.js +86 -0
  241. package/packages/agent-skills/dist/profile.js.map +1 -0
  242. package/packages/agent-skills/dist/skill-config.d.ts +24 -0
  243. package/packages/agent-skills/dist/skill-config.d.ts.map +1 -0
  244. package/packages/agent-skills/dist/skill-config.js +68 -0
  245. package/packages/agent-skills/dist/skill-config.js.map +1 -0
  246. package/packages/agent-skills/dist/skill-meta.d.ts +17 -0
  247. package/packages/agent-skills/dist/skill-meta.d.ts.map +1 -0
  248. package/packages/agent-skills/dist/skill-meta.js +67 -0
  249. package/packages/agent-skills/dist/skill-meta.js.map +1 -0
  250. package/packages/agent-skills/package.json +31 -0
  251. package/packages/agent-skills/src/deploy.ts +222 -0
  252. package/packages/agent-skills/src/doctor.ts +178 -0
  253. package/packages/agent-skills/src/fs-utils.ts +40 -0
  254. package/packages/agent-skills/src/index.ts +29 -0
  255. package/packages/agent-skills/src/legacy-lock.ts +152 -0
  256. package/packages/agent-skills/src/list.ts +94 -0
  257. package/packages/agent-skills/src/manifest.ts +66 -0
  258. package/packages/agent-skills/src/profile.ts +128 -0
  259. package/packages/agent-skills/src/skill-config.ts +75 -0
  260. package/packages/agent-skills/src/skill-meta.ts +78 -0
  261. package/packages/agent-skills/tsconfig.build.json +13 -0
  262. package/packages/agent-skills/tsconfig.json +23 -0
  263. package/packages/azdo/README.md +111 -0
  264. package/packages/azdo/package.json +21 -0
  265. package/packages/azdo/src/azdo-pl.ts +275 -0
  266. package/packages/azdo/src/azdo-pr-review.ts +160 -0
  267. package/packages/azdo/src/azdo-pr.ts +289 -0
  268. package/packages/azdo/src/azdo-repo-doctor.ts +839 -0
  269. package/packages/azdo/src/azdo-wit.ts +145 -0
  270. package/packages/azdo/src/cli.ts +251 -0
  271. package/packages/azdo/src/lib/agent-identity.ts +198 -0
  272. package/packages/azdo/src/lib/agent-token.ts +167 -0
  273. package/packages/azdo/src/lib/client.ts +259 -0
  274. package/packages/azdo/src/lib/graph-identity.ts +153 -0
  275. package/packages/azdo/src/lib/validate-pr-metadata.ts +57 -0
  276. package/packages/azdo/src/mcp-server.ts +209 -0
  277. package/packages/azdo/src/tool-dispatch.ts +204 -0
  278. package/packages/azdo/tsconfig.json +13 -0
  279. package/packages/fleet/jobs/git-maintain/src/main.ts +588 -0
  280. package/packages/fleet/manifest.json +207 -0
  281. package/packages/fleet/manifest.schema.json +283 -0
  282. package/packages/fleet/src/default-agent-file.ts +144 -0
  283. package/packages/fleet/src/install-adl-package.ts +178 -0
  284. package/packages/fleet/src/install-embedding-model.ts +412 -0
  285. package/packages/fleet/src/launch-agents.ts +649 -0
  286. package/packages/fleet/src/managed-settings.ts +278 -0
  287. package/packages/fleet/src/scratch-reap.ts +601 -0
  288. package/packages/fleet/src/vscode-settings.ts +705 -0
  289. package/packages/fleet/systemd/adl-azure-agent-update.service +13 -0
  290. package/packages/fleet/systemd/adl-azure-agent-update.timer +10 -0
  291. package/packages/repo-controls/AGENTS.md +96 -0
  292. package/packages/repo-controls/CLAUDE.md +1 -0
  293. package/packages/repo-controls/LICENSE +20 -0
  294. package/packages/repo-controls/README.md +493 -0
  295. package/packages/repo-controls/assets/enable-integration-auto-complete.mjs +79 -0
  296. package/packages/repo-controls/assets/promote.ts +371 -0
  297. package/packages/repo-controls/assets/version-bump.sh +189 -0
  298. package/packages/repo-controls/azure-pipelines.yml +274 -0
  299. package/packages/repo-controls/bin/adl-repo-controls.ts +10 -0
  300. package/packages/repo-controls/bunfig.toml +58 -0
  301. package/packages/repo-controls/package/adl-compliance/AGENTS.md +163 -0
  302. package/packages/repo-controls/package/adl-compliance/CLAUDE.md +1 -0
  303. package/packages/repo-controls/package/adl-compliance/conftest.py +78 -0
  304. package/packages/repo-controls/package/adl-compliance/pyproject.toml +31 -0
  305. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/__init__.py +50 -0
  306. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +98 -0
  307. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/__init__.py +1 -0
  308. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/__init__.py +12 -0
  309. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/check.py +210 -0
  310. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-bad-status/docs/architecture/overview.md +8 -0
  311. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-bad-status/docs/decisions/adr-901-proposed.md +10 -0
  312. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-broken-backlink/docs/decisions/adr-902-dangling.md +11 -0
  313. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-clean/docs/architecture/overview.md +8 -0
  314. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-clean/docs/decisions/adr-900-example.md +10 -0
  315. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-frontmatter-mismatch/.plans/b_committed/26-08-08-lifecycle-fixture.md +3 -0
  316. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-frontmatter-mismatch/.specs/demo/lifecycle.md +7 -0
  317. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-frontmatter-mismatch/docs/decisions/adr-903-frontmatter-mismatch.md +11 -0
  318. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-inactive-plan/.plans/c_done/26-08-08-completed-fixture.md +3 -0
  319. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-inactive-plan/.specs/demo/lifecycle.md +7 -0
  320. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-inactive-plan/docs/decisions/adr-904-completed-plan.md +11 -0
  321. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +127 -0
  322. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/__init__.py +7 -0
  323. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/check.py +91 -0
  324. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/AGENTS.md +1 -0
  325. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/CLAUDE.md +1 -0
  326. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/architecture/declared.txt +1 -0
  327. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/compliance/declared.txt +1 -0
  328. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/decisions/declared.txt +1 -0
  329. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/index.md +1 -0
  330. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/operations/declared.txt +1 -0
  331. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/research/declared.txt +1 -0
  332. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/stray-root-file.md +1 -0
  333. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/totally-made-up/nonsense.md +1 -0
  334. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +106 -0
  335. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/__init__.py +7 -0
  336. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +291 -0
  337. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/clean/backend/clean.rs +1 -0
  338. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/excluded/frontend/mobile/src/i18n/locales/zh-CN/strings.ts +1 -0
  339. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/violation/backend/escaped.rs +2 -0
  340. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/violation/backend/raw.rs +1 -0
  341. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/violation/backend//346/226/207/344/273/266.rs +1 -0
  342. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +193 -0
  343. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/__init__.py +12 -0
  344. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/check.py +140 -0
  345. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-clean/docs/architecture/foo.md +7 -0
  346. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-clean/docs/architecture/index.md +10 -0
  347. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-violation/docs/architecture/foo.md +7 -0
  348. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-violation/docs/architecture/index.md +9 -0
  349. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/test_check.py +98 -0
  350. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/__init__.py +12 -0
  351. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +378 -0
  352. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +192 -0
  353. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs/index.md +14 -0
  354. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs/one.md +7 -0
  355. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs/two.md +7 -0
  356. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs-knowledge-indexes.yaml +20 -0
  357. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs/index.md +15 -0
  358. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs/one.md +7 -0
  359. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs/two.md +7 -0
  360. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs-knowledge-indexes.yaml +16 -0
  361. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs/index.md +13 -0
  362. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs/one.md +7 -0
  363. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs/two.md +7 -0
  364. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs-knowledge-indexes.yaml +16 -0
  365. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +102 -0
  366. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/__init__.py +12 -0
  367. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/check.py +387 -0
  368. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-bad-date/plans/LEDGER.md +5 -0
  369. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-clean/plans/LEDGER.md +6 -0
  370. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-empty-table/plans/LEDGER.md +4 -0
  371. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-long-event/plans/LEDGER.md +5 -0
  372. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-violation/plans/LEDGER.md +5 -0
  373. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +427 -0
  374. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/__init__.py +11 -0
  375. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/check.py +84 -0
  376. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-fence/docs/bad-fence.md +2 -0
  377. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-fence/docs/valid.md +5 -0
  378. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-frontmatter/docs/bad-front-matter.md +2 -0
  379. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-frontmatter/docs/valid.md +5 -0
  380. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-clean/docs/valid.md +7 -0
  381. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/test_check.py +126 -0
  382. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/__init__.py +12 -0
  383. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +210 -0
  384. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +119 -0
  385. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +17 -0
  386. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +8 -0
  387. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs/architecture/bad-status.md +17 -0
  388. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +8 -0
  389. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +49 -0
  390. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +159 -0
  391. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/__init__.py +12 -0
  392. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/check.py +578 -0
  393. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/architecture/backend/crate-overview.md +8 -0
  394. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/architecture/team/team-overview.md +8 -0
  395. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/compliance/controls/control-matrix.md +12 -0
  396. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/compliance/controls/control-matrix.yaml +10 -0
  397. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/research/benchmarks/note.md +24 -0
  398. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/backend/crate-overview.md +8 -0
  399. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-delivery.md +10 -0
  400. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-recommendation.md +12 -0
  401. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/target-narration.md +10 -0
  402. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/team/team-overview.md +8 -0
  403. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/compliance/controls/control-matrix.md +8 -0
  404. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/compliance/controls/control-matrix.yaml +17 -0
  405. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/research/benchmarks/bad-research.md +8 -0
  406. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +461 -0
  407. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/__init__.py +1 -0
  408. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/__init__.py +12 -0
  409. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/check.py +124 -0
  410. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/.workflows/AGENTS.md +1 -0
  411. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/.workflows/CLAUDE.md +1 -0
  412. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/AGENTS.md +3 -0
  413. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/CLAUDE.md +2 -0
  414. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/INVARIANTS.md +3 -0
  415. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/bad-pointer/AGENTS.md +3 -0
  416. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/bad-pointer/CLAUDE.md +1 -0
  417. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/docs/AGENTS.md +1 -0
  418. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/docs/CLAUDE.md +1 -0
  419. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/missing-pair/AGENTS.md +3 -0
  420. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/owned/AGENTS.md +1 -0
  421. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/owned/CLAUDE.md +2 -0
  422. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/test_check.py +116 -0
  423. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/__init__.py +13 -0
  424. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/check.py +380 -0
  425. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/fixtures/redeclared-workflows-tree/automation/pipelines/pr-validation.yaml +1 -0
  426. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/test_check.py +274 -0
  427. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +12 -0
  428. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +227 -0
  429. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  430. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +6 -0
  431. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/fresh/fresh.ts +1 -0
  432. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub1/only.ts +1 -0
  433. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub10/only.ts +1 -0
  434. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub11/only.ts +1 -0
  435. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub2/only.ts +1 -0
  436. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub3/only.ts +1 -0
  437. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub4/only.ts +1 -0
  438. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub5/only.ts +1 -0
  439. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub6/only.ts +1 -0
  440. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub7/only.ts +1 -0
  441. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub8/only.ts +1 -0
  442. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub9/only.ts +1 -0
  443. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/services/i18n/locales/de/common.json +1 -0
  444. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  445. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/vendored-crate/Cargo.toml +2 -0
  446. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +6 -0
  447. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/fresh/fresh.ts +1 -0
  448. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub1/only.ts +1 -0
  449. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub10/only.ts +1 -0
  450. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub11/only.ts +1 -0
  451. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub2/only.ts +1 -0
  452. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub3/only.ts +1 -0
  453. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub4/only.ts +1 -0
  454. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub5/only.ts +1 -0
  455. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub6/only.ts +1 -0
  456. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub7/only.ts +1 -0
  457. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub8/only.ts +1 -0
  458. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub9/only.ts +1 -0
  459. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  460. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +6 -0
  461. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +138 -0
  462. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/__init__.py +12 -0
  463. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +240 -0
  464. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-clean/.plans/a_new/plan.md +7 -0
  465. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-clean/backend/AGENTS.md +4 -0
  466. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-clean/docs/page.md +7 -0
  467. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-stray/backend/notes.md +4 -0
  468. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-stray/docs/page.md +8 -0
  469. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +220 -0
  470. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/__init__.py +5 -0
  471. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +225 -0
  472. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/GAPS-AND-FINDINGS.md +5 -0
  473. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/spec.md +10 -0
  474. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +330 -0
  475. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/__init__.py +12 -0
  476. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +240 -0
  477. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/agent/crates/enact-providers/src/snapshots/README.md +1 -0
  478. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-app/assets/builtin-assistants/avatars/assistant.jpg +1 -0
  479. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-assets/assets/logos/tools/coding/tool.png +1 -0
  480. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-session/src/backend/codex_init_prompt.md +1 -0
  481. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/mobile/assets/images/icon.png +1 -0
  482. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/logos/brand/app.png +1 -0
  483. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/themes/default-theme.png +1 -0
  484. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/public/pwa/icon-192.png +1 -0
  485. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/resources/product.png +1 -0
  486. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/apps/legacy/file.ts +1 -0
  487. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/backend/crates/enact-session/src/backend/README.md +1 -0
  488. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/frontend/unapproved/screenshot.png +1 -0
  489. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +230 -0
  490. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +53 -0
  491. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/__init__.py +7 -0
  492. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +438 -0
  493. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/AGENTS.md +1 -0
  494. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +9 -0
  495. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/spec.md +12 -0
  496. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/templates/feature.md +1 -0
  497. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +9 -0
  498. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/spec.md +12 -0
  499. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +461 -0
  500. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/__init__.py +11 -0
  501. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/check.py +62 -0
  502. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/NotAMigration.sql +1 -0
  503. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/0001_long_prefix.sql +1 -0
  504. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/001_init.sql +1 -0
  505. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/002_add_index.sql +1 -0
  506. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/003-dashed-slug.sql +1 -0
  507. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/004_MixedCase.sql +1 -0
  508. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/005_trailing space.sql +1 -0
  509. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/01_short_prefix.sql +1 -0
  510. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/not-a-migration.sql +1 -0
  511. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/test_check.py +116 -0
  512. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/__init__.py +11 -0
  513. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +188 -0
  514. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/.plans/c_done/26-07-27-history.md +3 -0
  515. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/README.md +8 -0
  516. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/links.md +20 -0
  517. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/route-target.md +8 -0
  518. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/.plans/c_done/26-07-27-history.md +3 -0
  519. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/README.md +8 -0
  520. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/links.md +20 -0
  521. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/route-target.md +8 -0
  522. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/test_check.py +166 -0
  523. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/__init__.py +11 -0
  524. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +135 -0
  525. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.agents/declared.txt +1 -0
  526. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.changeset/declared.txt +1 -0
  527. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.claude/declared.txt +1 -0
  528. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.code-review-graph/declared.txt +1 -0
  529. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.code-review-graph_old/declared.txt +1 -0
  530. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.codex/declared.txt +1 -0
  531. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.cursor/declared.txt +1 -0
  532. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.cursorrules +1 -0
  533. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.dockerignore +1 -0
  534. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.docs/declared.txt +1 -0
  535. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.donors/declared.txt +1 -0
  536. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.gitattributes +1 -0
  537. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.kimi-code/declared.txt +1 -0
  538. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.lfsconfig +1 -0
  539. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.mcp.json +1 -0
  540. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.opencode/declared.txt +1 -0
  541. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.workflows/declared.txt +1 -0
  542. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/AGENTS.md +1 -0
  543. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/CHANGELOG.md +1 -0
  544. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/CLAUDE-INVARIANTS.md +1 -0
  545. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/CLAUDE.md +1 -0
  546. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/INDEPENDENT-VERIFICATION.md +1 -0
  547. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/INVARIANTS.md +1 -0
  548. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/LICENSE +1 -0
  549. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/NOTICE +1 -0
  550. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/README.md +1 -0
  551. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/RELEASE.md +1 -0
  552. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/VERSION +1 -0
  553. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/backend/declared.txt +1 -0
  554. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/compliance/declared.txt +1 -0
  555. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/dev/declared.txt +1 -0
  556. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/docker/declared.txt +1 -0
  557. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/docs/declared.txt +1 -0
  558. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/frontend/declared.txt +1 -0
  559. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/infra/declared.txt +1 -0
  560. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/justfile +1 -0
  561. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/opencode.jsonc +1 -0
  562. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/rule-tests/undeclared.txt +1 -0
  563. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/rules/undeclared.txt +1 -0
  564. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/unapproved-root/undeclared.txt +1 -0
  565. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/undeclared-root-file.txt +1 -0
  566. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +122 -0
  567. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/__init__.py +12 -0
  568. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +290 -0
  569. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.changeset/some-change.md +3 -0
  570. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.plans/AGENTS.md +3 -0
  571. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.specs/AGENTS.md +3 -0
  572. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.workflows/AGENTS.md +3 -0
  573. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/AGENTS.md +3 -0
  574. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/CHANGELOG.md +3 -0
  575. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/CLAUDE.md +3 -0
  576. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/INDEPENDENT-VERIFICATION.md +3 -0
  577. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/INVARIANTS.md +3 -0
  578. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/README.md +3 -0
  579. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/RELEASE.md +3 -0
  580. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/STRAY.md +3 -0
  581. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/docs/AGENTS.md +3 -0
  582. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/docs/architecture/note.md +3 -0
  583. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/frontend/README.md +3 -0
  584. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/.workflows/AGENTS.md +1 -0
  585. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/AGENTS.md +1 -0
  586. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/README.md +1 -0
  587. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/docs/overview.md +1 -0
  588. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/plans/AGENTS.md +1 -0
  589. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +329 -0
  590. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/__init__.py +12 -0
  591. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +127 -0
  592. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/a/b/c/d/at-limit.sh +3 -0
  593. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/a/b/c/d/e/too-deep.sh +3 -0
  594. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-01.sh +3 -0
  595. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-02.sh +3 -0
  596. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-03.sh +3 -0
  597. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-04.sh +3 -0
  598. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-05.sh +3 -0
  599. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-06.sh +3 -0
  600. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-07.sh +3 -0
  601. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-08.sh +3 -0
  602. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-09.sh +3 -0
  603. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-10.sh +3 -0
  604. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-11.sh +3 -0
  605. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-12.sh +3 -0
  606. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-13.sh +3 -0
  607. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-14.sh +3 -0
  608. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-15.sh +3 -0
  609. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-16.sh +3 -0
  610. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +163 -0
  611. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/__init__.py +1 -0
  612. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/__init__.py +11 -0
  613. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +169 -0
  614. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/.gitattributes +1 -0
  615. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/large file.bin +3 -0
  616. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/.gitattributes +1 -0
  617. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/large file.bin +3 -0
  618. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/small.txt +1 -0
  619. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/missing-filter/large file.bin +0 -0
  620. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/.gitattributes +1 -0
  621. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/payload.dat +0 -0
  622. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +133 -0
  623. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/__init__.py +1 -0
  624. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/__init__.py +12 -0
  625. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/check.py +129 -0
  626. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/disjoint/gate-registry.yaml +13 -0
  627. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/empty/gate-registry.yaml +7 -0
  628. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/overlapping/gate-registry.yaml +13 -0
  629. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/test_check.py +109 -0
  630. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/__init__.py +1 -0
  631. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/__init__.py +11 -0
  632. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/check.py +149 -0
  633. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/clean/docs/readme.md +4 -0
  634. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/relevant/VERSION +1 -0
  635. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/test_check.py +151 -0
  636. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/__init__.py +11 -0
  637. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/check.py +202 -0
  638. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/changesets/package.json +5 -0
  639. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/package.json +5 -0
  640. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/CHANGELOG.md +5 -0
  641. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/VERSION +1 -0
  642. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/changesets/package.json +5 -0
  643. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/package.json +5 -0
  644. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/CHANGELOG.md +5 -0
  645. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/VERSION +1 -0
  646. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/test_check.py +162 -0
  647. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/__init__.py +1 -0
  648. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/__init__.py +12 -0
  649. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/check.py +103 -0
  650. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/git-bad.sh +2 -0
  651. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/git-note.sh +3 -0
  652. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/good.sh +2 -0
  653. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/grep-bad.sh +2 -0
  654. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/grep-note.sh +3 -0
  655. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/grep-trailing.sh +2 -0
  656. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/notes.md +1 -0
  657. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/elsewhere/loose-git.sh +6 -0
  658. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/elsewhere/loose-grep.sh +4 -0
  659. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_check.py +117 -0
  660. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/__init__.py +13 -0
  661. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +265 -0
  662. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/fixtures/ignored-paths/keep.log +1 -0
  663. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/fixtures/ignored-paths/src/main.rs +1 -0
  664. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +180 -0
  665. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/__init__.py +12 -0
  666. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/check.py +84 -0
  667. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/fixtures/license-declared-clean/LICENSE +10 -0
  668. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/fixtures/license-declared-mismatch/LICENSE +3 -0
  669. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/fixtures/license-declared-missing/README.md +1 -0
  670. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/test_check.py +66 -0
  671. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/__init__.py +11 -0
  672. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +167 -0
  673. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.lock +7 -0
  674. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.toml +4 -0
  675. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/rust-toolchain.toml +3 -0
  676. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/src/main.rs +1 -0
  677. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/frontend/bun.lock +1 -0
  678. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.lock +7 -0
  679. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.toml +4 -0
  680. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/rust-toolchain.toml +3 -0
  681. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/src/main.rs +1 -0
  682. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/frontend/bun.lock +0 -0
  683. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +109 -0
  684. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +483 -0
  685. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/__init__.py +1 -0
  686. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/ast_grep.py +199 -0
  687. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/batch.py +52 -0
  688. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +303 -0
  689. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +106 -0
  690. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +99 -0
  691. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/errors.py +32 -0
  692. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/finding.py +65 -0
  693. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/models.py +40 -0
  694. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/receipt.py +63 -0
  695. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/scope.py +82 -0
  696. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/verdict.py +161 -0
  697. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/__init__.py +1 -0
  698. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +131 -0
  699. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.toml +30 -0
  700. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +65 -0
  701. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +791 -0
  702. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.schema.yaml +176 -0
  703. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +791 -0
  704. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/grants-registry.yaml +112 -0
  705. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/grants.py +129 -0
  706. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +281 -0
  707. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +250 -0
  708. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/severity-policy.yaml +114 -0
  709. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/providers/__init__.py +11 -0
  710. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +371 -0
  711. package/packages/repo-controls/package/adl-compliance/uv.lock +199 -0
  712. package/packages/repo-controls/package.json +33 -0
  713. package/packages/repo-controls/scripts/hooks/commit-msg +63 -0
  714. package/packages/repo-controls/scripts/hooks/lib/hook-log.sh +62 -0
  715. package/packages/repo-controls/scripts/hooks/post-checkout +15 -0
  716. package/packages/repo-controls/scripts/hooks/post-commit +18 -0
  717. package/packages/repo-controls/scripts/hooks/pre-commit +82 -0
  718. package/packages/repo-controls/scripts/hooks/pre-push +144 -0
  719. package/packages/repo-controls/src/azdo-push.ts +463 -0
  720. package/packages/repo-controls/src/azure-devops-governance.ts +248 -0
  721. package/packages/repo-controls/src/azure-devops-remote.ts +61 -0
  722. package/packages/repo-controls/src/cli.ts +188 -0
  723. package/packages/repo-controls/src/compliance-config.ts +200 -0
  724. package/packages/repo-controls/src/config.ts +690 -0
  725. package/packages/repo-controls/src/constants.ts +97 -0
  726. package/packages/repo-controls/src/coverage-ratchet.ts +153 -0
  727. package/packages/repo-controls/src/doc-freshness.ts +172 -0
  728. package/packages/repo-controls/src/doctor.ts +304 -0
  729. package/packages/repo-controls/src/findings-ratchet.ts +163 -0
  730. package/packages/repo-controls/src/gitignore-block.ts +253 -0
  731. package/packages/repo-controls/src/hook-log.ts +72 -0
  732. package/packages/repo-controls/src/hook.ts +694 -0
  733. package/packages/repo-controls/src/install.ts +506 -0
  734. package/packages/repo-controls/src/integrity.ts +104 -0
  735. package/packages/repo-controls/src/promote.ts +25 -0
  736. package/packages/repo-controls/src/runtime.ts +110 -0
  737. package/packages/repo-controls/src/workflows-drift.ts +106 -0
  738. package/packages/repo-controls/src/worktree-guard.ts +272 -0
  739. package/packages/repo-controls/src/worktree-limit.ts +11 -0
  740. package/packages/repo-controls/tsconfig.json +14 -0
  741. package/packages/repo-controls/workflows/scripts/release/verify-npm-artifact.ts +119 -0
@@ -0,0 +1,2179 @@
1
+ /**
2
+ * Safely inventory and remove machine-global agent host wiring (Claude, Codex,
3
+ * Cursor, OpenCode, Kimi, shell rc blocks, LaunchAgents)
4
+ * that enact-vps does not ship and that should have been repository-local.
5
+ *
6
+ * Discovery is broad. Authorization is narrow: apply mode accepts only a
7
+ * reviewed plan emitted by this module, matches every operation against the
8
+ * built-in surface allowlist, and verifies the target's SHA-256 fingerprint
9
+ * immediately before changing it. Discovery alone never authorizes deletion.
10
+ */
11
+
12
+ import { spawnSync } from 'node:child_process'
13
+ import { createHash } from 'node:crypto'
14
+ import {
15
+ chmodSync,
16
+ chownSync,
17
+ cpSync,
18
+ copyFileSync,
19
+ lstatSync,
20
+ mkdirSync,
21
+ mkdtempSync,
22
+ readFileSync,
23
+ readdirSync,
24
+ readlinkSync,
25
+ renameSync,
26
+ rmSync,
27
+ writeFileSync,
28
+ } from 'node:fs'
29
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
30
+ import { fileURLToPath } from 'node:url'
31
+ import { BEGIN as KIMI_BEGIN, END as KIMI_END } from './kimi-hooks.ts'
32
+ import { declaredLaunchAgentPlists } from '../../../packages/fleet/src/launch-agents.ts'
33
+
34
+ const LAUNCH_AGENTS_DIR = 'Library/LaunchAgents'
35
+
36
+ /**
37
+ * LaunchAgent scope is agent tooling only. A `~/Library/LaunchAgents` entry is
38
+ * a sweep candidate when its file name or its raw plist bytes (Label,
39
+ * Program/ProgramArguments, environment -- XML and binary plists both store
40
+ * these strings verbatim) contain, case-insensitively, one of these agent
41
+ * tool tokens. Everything else (GoogleUpdater, Keystone, other application
42
+ * updaters) is not an agent surface and is neither a candidate nor reported.
43
+ * LaunchAgents enact-vps declares in fleet/manifest.json are always in
44
+ * scope and protected by identity (see loadProtectionList).
45
+ */
46
+ export const AGENT_TOOL_TOKENS = [
47
+ 'lean-ctx',
48
+ 'leanctx',
49
+ 'adl',
50
+ 'jcode',
51
+ 'claude',
52
+ 'codex',
53
+ 'cursor',
54
+ 'kimi',
55
+ 'opencode',
56
+ 'copilot',
57
+ 'orca',
58
+ ] as const
59
+
60
+ export function isAgentToolingLaunchAgent(name: string, content: string): boolean {
61
+ const haystack = `${name}\n${content}`.toLowerCase()
62
+ return AGENT_TOOL_TOKENS.some((token) => haystack.includes(token))
63
+ }
64
+
65
+ export interface PreservedRegion {
66
+ readonly begin: string
67
+ readonly end: string
68
+ }
69
+
70
+ export type SweepTarget =
71
+ /** Entries of a JSON object/array. `key` may be a `/`-separated nested path, e.g. `permissions/allow`. */
72
+ | { readonly kind: 'json-map'; readonly file: string; readonly key: string }
73
+ /**
74
+ * A hooks-shaped map: `document[key]` is an object keyed by event name,
75
+ * each value an array of hook groups (Claude/Codex: `{matcher?, hooks:
76
+ * [...]}`; Cursor: the group IS the leaf hook, `{command, timeout}`).
77
+ * Unlike `json-map` (which treats a whole per-event array as one
78
+ * candidate), this enumerates one candidate per GROUP — selector
79
+ * `/key/eventName/index` — so a single event can mix a still-sweepable
80
+ * entry with an entry that must never be a candidate (Orca's own
81
+ * user-level hook, 2026-09-27 decision: "Orca is allowed for adl", the
82
+ * managed layer calls it, keep the lock on) without the two fighting for
83
+ * the same removal unit. An event whose every group is removed loses the
84
+ * key entirely, matching `json-map`'s own convention.
85
+ */
86
+ | { readonly kind: 'json-hook-map'; readonly file: string; readonly key: string }
87
+ | { readonly kind: 'json-root'; readonly file: string }
88
+ | {
89
+ readonly kind: 'toml-tables'
90
+ readonly file: string
91
+ readonly prefixes: readonly string[]
92
+ /** A delimited region owned by another enact-vps deployer; never inspected or changed. */
93
+ readonly preserve?: PreservedRegion
94
+ }
95
+ /** Tool-injected marker-delimited regions (`# >>> x >>>`, `# x — begin`, `<!-- x -->`). */
96
+ | { readonly kind: 'text-blocks'; readonly file: string }
97
+ | {
98
+ readonly kind: 'dir-entries'
99
+ readonly dir: string
100
+ readonly ignore?: readonly string[]
101
+ readonly descend?: readonly string[]
102
+ /** Scheduled enforcement never removes these; they are reported as pending explicit approval. */
103
+ readonly requiresApproval?: boolean
104
+ }
105
+ | { readonly kind: 'path'; readonly path: string }
106
+ /**
107
+ * Directly-contained regular files of `dir` whose content contains `marker`
108
+ * as an exact full line within the first 5 lines. Unlike `dir-entries`, this
109
+ * is content-addressed, not membership-addressed: `dir` may hold many
110
+ * unrelated files (and subdirectories, symlinks) that must never be
111
+ * candidates just for living there.
112
+ */
113
+ | { readonly kind: 'marker-files'; readonly dir: string; readonly marker: string }
114
+
115
+ const SHELL_RC_FILES = ['.zshenv', '.zprofile', '.zshrc', '.zlogin', '.bashenv', '.bashrc', '.bash_profile', '.profile']
116
+
117
+ export const SWEEP_TARGETS: readonly SweepTarget[] = [
118
+ { kind: 'json-hook-map', file: '.claude/settings.json', key: 'hooks' },
119
+ { kind: 'json-map', file: '.claude/settings.json', key: 'mcpServers' },
120
+ { kind: 'json-map', file: '.claude/settings.json', key: 'enabledPlugins' },
121
+ { kind: 'json-map', file: '.claude/settings.json', key: 'extraKnownMarketplaces' },
122
+ { kind: 'json-map', file: '.claude/settings.json', key: 'permissions/allow' },
123
+ { kind: 'json-map', file: '.claude/settings.json', key: 'permissions/deny' },
124
+ { kind: 'json-map', file: '.claude/settings.json', key: 'permissions/ask' },
125
+ { kind: 'text-blocks', file: '.claude/CLAUDE.md' },
126
+ { kind: 'json-map', file: '.claude.json', key: 'mcpServers' },
127
+ { kind: 'dir-entries', dir: '.claude/plugins/cache' },
128
+ { kind: 'dir-entries', dir: '.claude/skills' },
129
+ { kind: 'dir-entries', dir: '.claude/commands' },
130
+ { kind: 'dir-entries', dir: '.claude/agents' },
131
+ { kind: 'dir-entries', dir: '.claude/plugins/repos' },
132
+ { kind: 'dir-entries', dir: '.claude/plugins/marketplaces' },
133
+ { kind: 'json-map', file: '.claude/plugins/installed_plugins.json', key: 'plugins' },
134
+ { kind: 'json-root', file: '.claude/plugins/known_marketplaces.json' },
135
+ { kind: 'json-hook-map', file: '.codex/hooks.json', key: 'hooks' },
136
+ { kind: 'toml-tables', file: '.codex/config.toml', prefixes: ['mcp_servers', 'hooks'] },
137
+ { kind: 'text-blocks', file: '.codex/AGENTS.md' },
138
+ { kind: 'dir-entries', dir: '.codex/rules' },
139
+ { kind: 'dir-entries', dir: '.codex/skills' },
140
+ { kind: 'dir-entries', dir: '.codex/plugins', descend: ['cache'] },
141
+ { kind: 'dir-entries', dir: '.codex/plugins/cache' },
142
+ { kind: 'dir-entries', dir: '.codex/marketplaces' },
143
+ {
144
+ kind: 'dir-entries',
145
+ dir: '.codex/cache',
146
+ descend: [
147
+ 'bundled_plugin_exclusions',
148
+ 'codex_app_directory',
149
+ 'codex_apps_server_info',
150
+ 'codex_apps_tools',
151
+ 'remote_plugin_catalog',
152
+ ],
153
+ },
154
+ { kind: 'dir-entries', dir: '.codex/cache/bundled_plugin_exclusions' },
155
+ { kind: 'dir-entries', dir: '.codex/cache/codex_app_directory' },
156
+ { kind: 'dir-entries', dir: '.codex/cache/codex_apps_server_info' },
157
+ { kind: 'dir-entries', dir: '.codex/cache/codex_apps_tools' },
158
+ { kind: 'dir-entries', dir: '.codex/cache/remote_plugin_catalog' },
159
+ { kind: 'dir-entries', dir: '.codex/vendor_imports' },
160
+ { kind: 'json-hook-map', file: '.cursor/hooks.json', key: 'hooks' },
161
+ { kind: 'json-map', file: '.cursor/mcp.json', key: 'mcpServers' },
162
+ { kind: 'dir-entries', dir: '.cursor/skills' },
163
+ { kind: 'dir-entries', dir: '.cursor/skills-cursor' },
164
+ { kind: 'dir-entries', dir: '.cursor/commands' },
165
+ { kind: 'dir-entries', dir: '.cursor/agents' },
166
+ { kind: 'dir-entries', dir: '.cursor/plugins' },
167
+ { kind: 'dir-entries', dir: '.cursor/hooks' },
168
+ { kind: 'dir-entries', dir: '.cursor/rules' },
169
+ { kind: 'text-blocks', file: '.cursor/AGENTS.md' },
170
+ { kind: 'dir-entries', dir: '.agents', ignore: ['GLOBAL-AGENTS.md'], descend: ['skills'] },
171
+ { kind: 'dir-entries', dir: '.agents/skills' },
172
+ ...['.config/opencode/opencode.json', '.config/opencode/opencode.jsonc'].flatMap((file): SweepTarget[] => [
173
+ { kind: 'json-map', file, key: 'mcp' },
174
+ { kind: 'json-map', file, key: 'permission' },
175
+ { kind: 'json-map', file, key: 'plugin' },
176
+ ]),
177
+ { kind: 'dir-entries', dir: '.config/opencode/plugins' },
178
+ { kind: 'dir-entries', dir: '.config/opencode/skills' },
179
+ { kind: 'text-blocks', file: '.config/opencode/AGENTS.md' },
180
+ {
181
+ kind: 'toml-tables',
182
+ file: '.kimi-code/config.toml',
183
+ prefixes: ['hooks'],
184
+ preserve: { begin: KIMI_BEGIN, end: KIMI_END },
185
+ },
186
+ { kind: 'json-map', file: '.kimi-code/mcp.json', key: 'mcpServers' },
187
+ { kind: 'dir-entries', dir: '.kimi-code/skills' },
188
+ { kind: 'dir-entries', dir: '.kimi-code/plugins' },
189
+ { kind: 'text-blocks', file: '.kimi-code/AGENTS.md' },
190
+ ...SHELL_RC_FILES.map((file): SweepTarget => ({ kind: 'text-blocks', file })),
191
+ { kind: 'dir-entries', dir: 'Library/LaunchAgents', requiresApproval: true },
192
+ {
193
+ kind: 'marker-files',
194
+ dir: '.local/bin',
195
+ marker: '# Generated by .agents/setup/setup.sh -- do not edit.',
196
+ },
197
+ ]
198
+
199
+ function managedFileTarget(file: string): boolean {
200
+ return SWEEP_TARGETS.some(
201
+ (target) =>
202
+ (target.kind === 'json-map' ||
203
+ target.kind === 'json-hook-map' ||
204
+ target.kind === 'json-root' ||
205
+ target.kind === 'toml-tables' ||
206
+ target.kind === 'text-blocks') &&
207
+ target.file === file,
208
+ )
209
+ }
210
+
211
+ function preservedRegionFor(file: string): PreservedRegion | undefined {
212
+ for (const target of SWEEP_TARGETS) {
213
+ if (target.kind === 'toml-tables' && target.file === file && target.preserve !== undefined) return target.preserve
214
+ }
215
+ return undefined
216
+ }
217
+
218
+ export function operationRequiresApproval(path: string): boolean {
219
+ return SWEEP_TARGETS.some(
220
+ (target) => target.kind === 'dir-entries' && target.requiresApproval === true && dirname(path) === target.dir,
221
+ )
222
+ }
223
+
224
+ export type RemovalItem =
225
+ | {
226
+ readonly kind: 'json-entry'
227
+ readonly target: string
228
+ readonly selector: string
229
+ readonly beforeSha256: string
230
+ }
231
+ | {
232
+ readonly kind: 'toml-table'
233
+ readonly target: string
234
+ readonly header: string
235
+ readonly beforeSha256: string
236
+ }
237
+ | {
238
+ readonly kind: 'toml-key'
239
+ readonly target: string
240
+ readonly key: string
241
+ readonly beforeSha256: string
242
+ }
243
+ | {
244
+ readonly kind: 'text-block'
245
+ readonly target: string
246
+ readonly name: string
247
+ readonly beforeSha256: string
248
+ }
249
+ | { readonly kind: 'path'; readonly target: string; readonly beforeSha256: string }
250
+
251
+ export interface ProtectionList {
252
+ readonly schema: 1
253
+ readonly paths: readonly { readonly path: string; readonly reason: string }[]
254
+ readonly jsonEntries: readonly {
255
+ readonly target: string
256
+ readonly selector: string
257
+ readonly reason: string
258
+ }[]
259
+ readonly tomlTables: readonly {
260
+ readonly target: string
261
+ readonly header: string
262
+ readonly reason: string
263
+ }[]
264
+ /** Exact marker-block identities (file + block name) that are shipped/owned and must be retained. */
265
+ readonly textBlocks: readonly {
266
+ readonly target: string
267
+ readonly name: string
268
+ readonly reason: string
269
+ }[]
270
+ /**
271
+ * Declared shipped baseline of top-level keys for a settings file. When a
272
+ * baseline exists for a file, every other top-level JSON key (or TOML root
273
+ * key / table whose first segment is undeclared) is foreign and swept.
274
+ * Files without a baseline never lose root keys.
275
+ */
276
+ readonly rootKeys: readonly {
277
+ readonly target: string
278
+ readonly keys: readonly string[]
279
+ readonly reason: string
280
+ }[]
281
+ }
282
+
283
+ export interface ProtectedItem {
284
+ readonly removal: RemovalItem
285
+ readonly reason: string
286
+ }
287
+
288
+ export const EMPTY_PROTECTION: ProtectionList = {
289
+ schema: 1,
290
+ paths: [],
291
+ jsonEntries: [],
292
+ tomlTables: [],
293
+ textBlocks: [],
294
+ rootKeys: [],
295
+ }
296
+
297
+ export const DEFAULT_PROTECTION_PATH = fileURLToPath(new URL('../protected.json', import.meta.url))
298
+ export const DEFAULT_MANIFEST_PATH = fileURLToPath(new URL('../../fleet/manifest.json', import.meta.url))
299
+
300
+ export interface SweepNote {
301
+ readonly level: 'warn' | 'ok'
302
+ readonly text: string
303
+ }
304
+
305
+ export type SweepOperation =
306
+ | {
307
+ readonly id: string
308
+ readonly kind: 'write-file'
309
+ readonly path: string
310
+ readonly absolutePath: string
311
+ readonly beforeSha256: string
312
+ readonly removals: readonly RemovalItem[]
313
+ readonly content: string
314
+ readonly mode: number
315
+ readonly uid: number
316
+ readonly gid: number
317
+ readonly requiresApproval: false
318
+ }
319
+ | {
320
+ readonly id: string
321
+ readonly kind: 'remove-path'
322
+ readonly path: string
323
+ readonly absolutePath: string
324
+ readonly beforeSha256: string
325
+ readonly removals: readonly RemovalItem[]
326
+ readonly requiresApproval: boolean
327
+ }
328
+
329
+ export interface ApprovedOperation {
330
+ readonly id: string
331
+ readonly kind: SweepOperation['kind']
332
+ readonly path: string
333
+ readonly beforeSha256: string
334
+ readonly removals: readonly RemovalItem[]
335
+ }
336
+
337
+ export interface ApprovalPlan {
338
+ readonly schema: 1
339
+ readonly home: string
340
+ readonly generatedAt: string
341
+ readonly operations: readonly ApprovedOperation[]
342
+ }
343
+
344
+ export interface SweepPlan {
345
+ readonly operations: readonly SweepOperation[]
346
+ readonly protected: readonly ProtectedItem[]
347
+ readonly notes: readonly SweepNote[]
348
+ }
349
+
350
+ export interface ApplyResult {
351
+ readonly applied: readonly ApprovedOperation[]
352
+ readonly alreadyClean: readonly string[]
353
+ readonly unapproved: readonly ApprovedOperation[]
354
+ readonly backupRoot: string | null
355
+ }
356
+
357
+ export interface EnforceResult extends ApplyResult {
358
+ readonly protectedItems: number
359
+ /** Candidates scheduled enforcement must not remove; they need a reviewed plan + apply. */
360
+ readonly pendingApproval: readonly ApprovedOperation[]
361
+ }
362
+
363
+ export interface ReviewCopyResult {
364
+ readonly destination: string
365
+ readonly copiedPaths: number
366
+ readonly copiedConfigFiles: number
367
+ readonly candidateItems: number
368
+ readonly protectedItems: number
369
+ }
370
+
371
+ interface DraftFile {
372
+ readonly path: string
373
+ readonly relativePath: string
374
+ readonly original: string
375
+ content: string
376
+ readonly mode: number
377
+ readonly uid: number
378
+ readonly gid: number
379
+ readonly removals: RemovalItem[]
380
+ }
381
+
382
+ export function clearJsonMap(
383
+ document: unknown,
384
+ key: string,
385
+ ): { readonly document: unknown; readonly cleared: readonly string[] } | null {
386
+ if (document === null || typeof document !== 'object' || Array.isArray(document)) return null
387
+ const root = document as Record<string, unknown>
388
+ const value = root[key]
389
+ if (value === null || typeof value !== 'object') return null
390
+
391
+ const cleared = Array.isArray(value) ? value.map((_, index) => String(index)) : Object.keys(value)
392
+ if (cleared.length === 0) return null
393
+
394
+ return { document: { ...root, [key]: Array.isArray(value) ? [] : {} }, cleared }
395
+ }
396
+
397
+ export function dropTomlTables(
398
+ text: string,
399
+ prefixes: readonly string[],
400
+ ): { readonly text: string; readonly dropped: readonly string[] } {
401
+ const kept: string[] = []
402
+ const dropped: string[] = []
403
+ let dropping = false
404
+
405
+ for (const line of text.split('\n')) {
406
+ const trimmed = line.trim()
407
+ if (trimmed.startsWith('[')) {
408
+ const name = trimmed.slice(trimmed.startsWith('[[') ? 2 : 1).replace(/\]+$/, '')
409
+ dropping = prefixes.some((prefix) => name === prefix || name.startsWith(`${prefix}.`))
410
+ if (dropping) dropped.push(trimmed)
411
+ }
412
+ if (!dropping) kept.push(line)
413
+ }
414
+
415
+ if (dropped.length === 0) return { text, dropped }
416
+ return { text: kept.join('\n').replace(/\n{3,}/g, '\n\n'), dropped }
417
+ }
418
+
419
+ function messageOf(cause: unknown): string {
420
+ return cause instanceof Error ? cause.message : String(cause)
421
+ }
422
+
423
+ function pathExists(path: string): boolean {
424
+ try {
425
+ lstatSync(path)
426
+ return true
427
+ } catch (cause) {
428
+ const code =
429
+ cause !== null && typeof cause === 'object' && 'code' in cause
430
+ ? (cause as { readonly code?: unknown }).code
431
+ : undefined
432
+ if (code === 'ENOENT' || code === 'ENOTDIR') return false
433
+ throw cause
434
+ }
435
+ }
436
+
437
+ function relativeInside(root: string, path: string): string {
438
+ const value = relative(root, path)
439
+ if (value.length === 0 || value === '..' || value.startsWith(`..${sep}`) || isAbsolute(value)) {
440
+ throw new Error(`path escapes its allowed root: ${path}`)
441
+ }
442
+ return value.split(sep).join('/')
443
+ }
444
+
445
+ function insideHome(home: string, relativePath: string): string {
446
+ if (relativePath.length === 0 || isAbsolute(relativePath)) {
447
+ throw new Error(`expected a non-empty path relative to home: ${relativePath}`)
448
+ }
449
+ const path = resolve(home, relativePath)
450
+ relativeInside(home, path)
451
+ return path
452
+ }
453
+
454
+ function fingerprintPath(path: string): string {
455
+ const hash = createHash('sha256')
456
+
457
+ const visit = (current: string, label: string): void => {
458
+ const stat = lstatSync(current)
459
+ if (stat.isSymbolicLink()) {
460
+ hash.update(`link\0${label}\0${readlinkSync(current)}\0`)
461
+ return
462
+ }
463
+ if (stat.isDirectory()) {
464
+ hash.update(`dir\0${label}\0`)
465
+ for (const name of readdirSync(current).sort()) visit(join(current, name), `${label}/${name}`)
466
+ return
467
+ }
468
+ if (stat.isFile()) {
469
+ hash.update(`file\0${label}\0${stat.mode & 0o777}\0`)
470
+ hash.update(readFileSync(current))
471
+ hash.update('\0')
472
+ return
473
+ }
474
+ hash.update(`other\0${label}\0${stat.mode}\0`)
475
+ }
476
+
477
+ visit(path, '.')
478
+ return `sha256:${hash.digest('hex')}`
479
+ }
480
+
481
+ function jsonPointerPart(value: string): string {
482
+ return value.replaceAll('~', '~0').replaceAll('/', '~1')
483
+ }
484
+
485
+ function assertRelativeProtectionPath(value: string, label: string): void {
486
+ if (
487
+ value.length === 0 ||
488
+ isAbsolute(value) ||
489
+ value === '..' ||
490
+ value.startsWith('../') ||
491
+ value.includes('/../') ||
492
+ value.endsWith('/..')
493
+ ) {
494
+ throw new Error(`${label} must be a path relative to home: ${value}`)
495
+ }
496
+ }
497
+
498
+ export function decodeProtectionList(value: unknown): ProtectionList {
499
+ if (
500
+ !isRecord(value) ||
501
+ value.schema !== 1 ||
502
+ !Array.isArray(value.paths) ||
503
+ !Array.isArray(value.json_entries) ||
504
+ !Array.isArray(value.toml_tables)
505
+ ) {
506
+ throw new Error('protection list must have schema 1 and paths, json_entries, and toml_tables arrays')
507
+ }
508
+
509
+ const paths = value.paths.map((raw) => {
510
+ if (
511
+ !isRecord(raw) ||
512
+ typeof raw.path !== 'string' ||
513
+ typeof raw.reason !== 'string' ||
514
+ raw.reason.length === 0
515
+ ) {
516
+ throw new Error('protection list contains an invalid path entry')
517
+ }
518
+ assertRelativeProtectionPath(raw.path, 'protected path')
519
+ if (raw.sha256 !== undefined || raw.mutable_file !== undefined) {
520
+ throw new Error('protected paths are identity-only and must not contain fingerprints')
521
+ }
522
+ return { path: raw.path, reason: raw.reason }
523
+ })
524
+ const jsonEntries = value.json_entries.map((raw) => {
525
+ if (
526
+ !isRecord(raw) ||
527
+ typeof raw.target !== 'string' ||
528
+ typeof raw.selector !== 'string' ||
529
+ !raw.selector.startsWith('/') ||
530
+ typeof raw.reason !== 'string' ||
531
+ raw.reason.length === 0
532
+ ) {
533
+ throw new Error('protection list contains an invalid JSON entry')
534
+ }
535
+ assertRelativeProtectionPath(raw.target, 'protected JSON target')
536
+ if (raw.sha256 !== undefined) {
537
+ throw new Error('protected JSON entries are identity-only and must not contain fingerprints')
538
+ }
539
+ return { target: raw.target, selector: raw.selector, reason: raw.reason }
540
+ })
541
+ const tomlTables = value.toml_tables.map((raw) => {
542
+ if (
543
+ !isRecord(raw) ||
544
+ typeof raw.target !== 'string' ||
545
+ typeof raw.header !== 'string' ||
546
+ !raw.header.startsWith('[') ||
547
+ typeof raw.reason !== 'string' ||
548
+ raw.reason.length === 0
549
+ ) {
550
+ throw new Error('protection list contains an invalid TOML table entry')
551
+ }
552
+ assertRelativeProtectionPath(raw.target, 'protected TOML target')
553
+ if (raw.sha256 !== undefined) {
554
+ throw new Error('protected TOML tables are identity-only and must not contain fingerprints')
555
+ }
556
+ return { target: raw.target, header: raw.header, reason: raw.reason }
557
+ })
558
+ if (value.text_blocks !== undefined && !Array.isArray(value.text_blocks)) {
559
+ throw new Error('protection list text_blocks must be an array')
560
+ }
561
+ const textBlocks = ((value.text_blocks ?? []) as unknown[]).map((raw) => {
562
+ if (
563
+ !isRecord(raw) ||
564
+ typeof raw.target !== 'string' ||
565
+ typeof raw.name !== 'string' ||
566
+ raw.name.length === 0 ||
567
+ typeof raw.reason !== 'string' ||
568
+ raw.reason.length === 0
569
+ ) {
570
+ throw new Error('protection list contains an invalid text block entry')
571
+ }
572
+ assertRelativeProtectionPath(raw.target, 'protected text block target')
573
+ return { target: raw.target, name: raw.name, reason: raw.reason }
574
+ })
575
+ if (value.root_keys !== undefined && !Array.isArray(value.root_keys)) {
576
+ throw new Error('protection list root_keys must be an array')
577
+ }
578
+ const rootKeys = ((value.root_keys ?? []) as unknown[]).map((raw) => {
579
+ if (
580
+ !isRecord(raw) ||
581
+ typeof raw.target !== 'string' ||
582
+ !Array.isArray(raw.keys) ||
583
+ raw.keys.length === 0 ||
584
+ !raw.keys.every((key) => typeof key === 'string' && key.length > 0) ||
585
+ new Set(raw.keys).size !== raw.keys.length ||
586
+ typeof raw.reason !== 'string' ||
587
+ raw.reason.length === 0
588
+ ) {
589
+ throw new Error('protection list contains an invalid root key baseline')
590
+ }
591
+ assertRelativeProtectionPath(raw.target, 'root key baseline target')
592
+ return { target: raw.target, keys: raw.keys as string[], reason: raw.reason }
593
+ })
594
+
595
+ const identities = [
596
+ ...paths.map((entry) => `path:${entry.path}`),
597
+ ...jsonEntries.map((entry) => `json:${entry.target}#${entry.selector}`),
598
+ ...tomlTables.map((entry) => `toml:${entry.target}#${entry.header}`),
599
+ ...textBlocks.map((entry) => `block:${entry.target}#${entry.name}`),
600
+ ...rootKeys.map((entry) => `root-keys:${entry.target}`),
601
+ ]
602
+ if (new Set(identities).size !== identities.length) throw new Error('protection list contains duplicate entries')
603
+
604
+ return { schema: 1, paths, jsonEntries, tomlTables, textBlocks, rootKeys }
605
+ }
606
+
607
+ /**
608
+ * The checked-in protection list plus every LaunchAgent plist enact-vps
609
+ * itself ships (declared under `launch_agents` in fleet/manifest.json).
610
+ * Shipped-job protection is derived from the manifest, never hand-listed.
611
+ */
612
+ export function loadProtectionList(path = DEFAULT_PROTECTION_PATH, manifestPath = DEFAULT_MANIFEST_PATH): ProtectionList {
613
+ const list = decodeProtectionList(JSON.parse(readFileSync(path, 'utf8')))
614
+ const declared = declaredLaunchAgentPlists(JSON.parse(readFileSync(manifestPath, 'utf8')))
615
+ const listed = new Set(list.paths.map((entry) => entry.path))
616
+ const derived = declared
617
+ .filter((entry) => !listed.has(entry.path))
618
+ .map((entry) => ({ path: entry.path, reason: `LaunchAgent shipped by enact-vps (fleet/manifest.json ${entry.declaredAt})` }))
619
+ return { ...list, paths: [...list.paths, ...derived] }
620
+ }
621
+
622
+ function tomlHeaderName(header: string): string {
623
+ const trimmed = header.trim()
624
+ return trimmed.slice(trimmed.startsWith('[[') ? 2 : 1).replace(/\]+$/, '')
625
+ }
626
+
627
+ function fingerprintBytes(value: string): string {
628
+ return `sha256:${createHash('sha256').update(value).digest('hex')}`
629
+ }
630
+
631
+ function fingerprintJsonValue(value: unknown): string {
632
+ return fingerprintBytes(JSON.stringify(value))
633
+ }
634
+
635
+ /**
636
+ * Orca (2026-09-27 decision: "keep the orca hooks, let orca manage it, Orca
637
+ * is allowed for adl"; the MANAGED layer calls Orca, the managed-hooks-only
638
+ * lock stays on) installs its OWN user-level hooks on every hook-capable
639
+ * surface: Claude `~/.claude/settings.json` (`hooks` + `statusLine`), Codex
640
+ * `~/.codex/hooks.json`, Kimi `~/.kimi-code/config.toml` (`[[hooks]]`), and
641
+ * Cursor `~/.cursor/hooks.json`. Those entries must never be swept as
642
+ * "third-party wiring that should have been repo-local" the way a genuine
643
+ * foreign tool's hook is.
644
+ *
645
+ * Identity is by EXACT reference to the real install path, ground-truthed
646
+ * against this Mac's own live files (`~/.claude/settings.json`,
647
+ * `~/.codex/hooks.json`, `~/.cursor/hooks.json`, `~/.kimi-code/config.toml`,
648
+ * fetched 2026-09-27): Orca renders the path as `${HOME-}/.orca/agent-hooks/`
649
+ * (Claude), or resolves it to an absolute `<home>/.orca/agent-hooks/` at
650
+ * install time (Codex, Cursor, Kimi) — never a fixed literal across
651
+ * machines. The one substring every real form shares is the literal path
652
+ * segment `.orca/agent-hooks/`, so that is the marker, not a loose token
653
+ * match on "orca" alone: `/opt/orca/kimi-hook.sh` (a look-alike that merely
654
+ * mentions "orca" — see `third-party-wiring.test.ts`'s `KIMI_ORCA_HOOK`
655
+ * fixture) does NOT contain this segment and stays a genuine sweep
656
+ * candidate.
657
+ */
658
+ const ORCA_HOOK_REFERENCE_MARKER = '.orca/agent-hooks/'
659
+
660
+ const ORCA_HOOK_PROTECTION_REASON =
661
+ "Orca's own user-level agent hook (exact reference to .orca/agent-hooks/); Orca is allowed for adl (Tarun, 2026-09-27) — the managed layer calls it, this user-level copy is not swept"
662
+
663
+ /** Recursively true iff any string reachable from `value` contains the exact Orca hook path segment. */
664
+ function containsOrcaHookReference(value: unknown): boolean {
665
+ if (typeof value === 'string') return value.includes(ORCA_HOOK_REFERENCE_MARKER)
666
+ if (Array.isArray(value)) return value.some(containsOrcaHookReference)
667
+ if (isRecord(value)) return Object.values(value).some(containsOrcaHookReference)
668
+ return false
669
+ }
670
+
671
+ /** Same as `protectionFor`, but checks the live Orca-content identity first, ahead of the static, selector-based `protection` list. */
672
+ function protectionForValue(removal: RemovalItem, value: unknown, protection: ProtectionList): string | null {
673
+ if (containsOrcaHookReference(value)) return ORCA_HOOK_PROTECTION_REASON
674
+ return protectionFor(removal, protection)
675
+ }
676
+
677
+ function protectionFor(
678
+ removal: RemovalItem,
679
+ protection: ProtectionList,
680
+ ): string | null {
681
+ if (removal.kind === 'path') {
682
+ const match = protection.paths.find((entry) => removal.target === entry.path)
683
+ return match?.reason ?? null
684
+ }
685
+ if (removal.kind === 'json-entry') {
686
+ const match = protection.jsonEntries.find(
687
+ (entry) => entry.target === removal.target && entry.selector === removal.selector,
688
+ )
689
+ return match?.reason ?? null
690
+ }
691
+ if (removal.kind === 'text-block') {
692
+ const match = protection.textBlocks.find((entry) => entry.target === removal.target && entry.name === removal.name)
693
+ return match?.reason ?? null
694
+ }
695
+ if (removal.kind === 'toml-key') return null
696
+ const match = protection.tomlTables.find(
697
+ (entry) => entry.target === removal.target && entry.header === removal.header,
698
+ )
699
+ return match?.reason ?? null
700
+ }
701
+
702
+ function valueAtPath(document: Record<string, unknown>, path: readonly string[]): unknown {
703
+ let current: unknown = document
704
+ for (const part of path) {
705
+ if (!isRecord(current) || !Object.hasOwn(current, part)) return undefined
706
+ current = current[part]
707
+ }
708
+ return current
709
+ }
710
+
711
+ function withValueAtPath(
712
+ document: Record<string, unknown>,
713
+ path: readonly string[],
714
+ value: unknown,
715
+ ): Record<string, unknown> {
716
+ const [head, ...rest] = path
717
+ if (head === undefined) throw new Error('empty JSON key path')
718
+ if (rest.length === 0) return { ...document, [head]: value }
719
+ const child = document[head]
720
+ if (!isRecord(child)) throw new Error(`JSON key path is not an object: ${path.join('/')}`)
721
+ return { ...document, [head]: withValueAtPath(child, rest, value) }
722
+ }
723
+
724
+ function filterJsonMap(
725
+ document: unknown,
726
+ key: string,
727
+ target: string,
728
+ protection: ProtectionList,
729
+ ): {
730
+ readonly document: unknown
731
+ readonly removals: readonly RemovalItem[]
732
+ readonly protected: readonly ProtectedItem[]
733
+ } | null {
734
+ if (!isRecord(document)) return null
735
+ const path = key.split('/')
736
+ const value = valueAtPath(document, path)
737
+ if (value === null || typeof value !== 'object') return null
738
+ const pointer = path.map(jsonPointerPart).join('/')
739
+
740
+ const removals: RemovalItem[] = []
741
+ const protectedItems: ProtectedItem[] = []
742
+ if (Array.isArray(value)) {
743
+ const kept: unknown[] = []
744
+ for (const [index, entry] of value.entries()) {
745
+ const removal: RemovalItem = {
746
+ kind: 'json-entry',
747
+ target,
748
+ selector: `/${pointer}/${index}`,
749
+ beforeSha256: fingerprintJsonValue(entry),
750
+ }
751
+ const reason = protectionForValue(removal, entry, protection)
752
+ if (reason === null) removals.push(removal)
753
+ else {
754
+ kept.push(entry)
755
+ protectedItems.push({ removal, reason })
756
+ }
757
+ }
758
+ if (removals.length === 0 && protectedItems.length === 0) return null
759
+ return { document: removals.length === 0 ? document : withValueAtPath(document, path, kept), removals, protected: protectedItems }
760
+ }
761
+
762
+ const kept: Record<string, unknown> = {}
763
+ for (const [name, entry] of Object.entries(value as Record<string, unknown>)) {
764
+ const removal: RemovalItem = {
765
+ kind: 'json-entry',
766
+ target,
767
+ selector: `/${pointer}/${jsonPointerPart(name)}`,
768
+ beforeSha256: fingerprintJsonValue(entry),
769
+ }
770
+ const reason = protectionForValue(removal, entry, protection)
771
+ if (reason === null) removals.push(removal)
772
+ else {
773
+ kept[name] = entry
774
+ protectedItems.push({ removal, reason })
775
+ }
776
+ }
777
+ if (removals.length === 0 && protectedItems.length === 0) return null
778
+ return { document: removals.length === 0 ? document : withValueAtPath(document, path, kept), removals, protected: protectedItems }
779
+ }
780
+
781
+ /**
782
+ * `json-hook-map` filter: `document[key]` is an object keyed by event name,
783
+ * each value an array of hook groups. One candidate per GROUP (selector
784
+ * `/key/eventName/index`), not per event — see the `SweepTarget` union
785
+ * member's own doc comment for why. An event that loses every group loses
786
+ * the key entirely (same convention as `filterJsonMap`'s object branch); a
787
+ * malformed event whose value is not an array falls back to whole-value
788
+ * removal so a hand-edited file never throws instead of sweeping.
789
+ */
790
+ interface FilteredEntry {
791
+ readonly kept: unknown
792
+ readonly removals: readonly RemovalItem[]
793
+ readonly protected: readonly ProtectedItem[]
794
+ }
795
+
796
+ /** One event's array of hook groups: one candidate per group (selector `.../<index>`). Extracted from `filterJsonHookMap` to keep both functions under the cognitive-complexity limit. */
797
+ function filterHookGroups(groups: readonly unknown[], selectorPrefix: string, target: string, protection: ProtectionList): FilteredEntry {
798
+ const removals: RemovalItem[] = []
799
+ const protectedItems: ProtectedItem[] = []
800
+ const kept: unknown[] = []
801
+ for (const [index, group] of groups.entries()) {
802
+ const removal: RemovalItem = {
803
+ kind: 'json-entry',
804
+ target,
805
+ selector: `${selectorPrefix}/${index}`,
806
+ beforeSha256: fingerprintJsonValue(group),
807
+ }
808
+ const reason = protectionForValue(removal, group, protection)
809
+ if (reason === null) removals.push(removal)
810
+ else {
811
+ kept.push(group)
812
+ protectedItems.push({ removal, reason })
813
+ }
814
+ }
815
+ return { kept, removals, protected: protectedItems }
816
+ }
817
+
818
+ function filterJsonHookMap(
819
+ document: unknown,
820
+ key: string,
821
+ target: string,
822
+ protection: ProtectionList,
823
+ ): {
824
+ readonly document: unknown
825
+ readonly removals: readonly RemovalItem[]
826
+ readonly protected: readonly ProtectedItem[]
827
+ } | null {
828
+ if (!isRecord(document)) return null
829
+ const path = key.split('/')
830
+ const value = valueAtPath(document, path)
831
+ if (!isRecord(value)) return null
832
+ const pointer = path.map(jsonPointerPart).join('/')
833
+
834
+ const removals: RemovalItem[] = []
835
+ const protectedItems: ProtectedItem[] = []
836
+ const kept: Record<string, unknown> = {}
837
+ for (const [eventName, groups] of Object.entries(value)) {
838
+ const selectorPrefix = `/${pointer}/${jsonPointerPart(eventName)}`
839
+ if (!Array.isArray(groups)) {
840
+ const removal: RemovalItem = { kind: 'json-entry', target, selector: selectorPrefix, beforeSha256: fingerprintJsonValue(groups) }
841
+ const reason = protectionForValue(removal, groups, protection)
842
+ if (reason === null) removals.push(removal)
843
+ else {
844
+ kept[eventName] = groups
845
+ protectedItems.push({ removal, reason })
846
+ }
847
+ continue
848
+ }
849
+ const filtered = filterHookGroups(groups, selectorPrefix, target, protection)
850
+ removals.push(...filtered.removals)
851
+ protectedItems.push(...filtered.protected)
852
+ if ((filtered.kept as unknown[]).length > 0) kept[eventName] = filtered.kept
853
+ }
854
+ if (removals.length === 0 && protectedItems.length === 0) return null
855
+ return { document: removals.length === 0 ? document : withValueAtPath(document, path, kept), removals, protected: protectedItems }
856
+ }
857
+
858
+ function filterJsonRoot(
859
+ document: unknown,
860
+ target: string,
861
+ protection: ProtectionList,
862
+ ): {
863
+ readonly document: unknown
864
+ readonly removals: readonly RemovalItem[]
865
+ readonly protected: readonly ProtectedItem[]
866
+ } | null {
867
+ if (!isRecord(document)) return null
868
+ const kept: Record<string, unknown> = {}
869
+ const removals: RemovalItem[] = []
870
+ const protectedItems: ProtectedItem[] = []
871
+ for (const [name, entry] of Object.entries(document)) {
872
+ const removal: RemovalItem = {
873
+ kind: 'json-entry',
874
+ target,
875
+ selector: `/${jsonPointerPart(name)}`,
876
+ beforeSha256: fingerprintJsonValue(entry),
877
+ }
878
+ const reason = protectionForValue(removal, entry, protection)
879
+ if (reason === null) removals.push(removal)
880
+ else {
881
+ kept[name] = entry
882
+ protectedItems.push({ removal, reason })
883
+ }
884
+ }
885
+ if (removals.length === 0 && protectedItems.length === 0) return null
886
+ return { document: removals.length === 0 ? document : kept, removals, protected: protectedItems }
887
+ }
888
+
889
+ interface TomlBlock {
890
+ readonly header: string | null
891
+ readonly lines: readonly string[]
892
+ /** True for a region owned by another deployer (kept verbatim, never inspected). */
893
+ readonly preserved: boolean
894
+ }
895
+
896
+ function splitTomlBlocks(text: string, preserve?: PreservedRegion): readonly TomlBlock[] {
897
+ const blocks: { header: string | null; lines: string[]; preserved: boolean }[] = []
898
+ let current: { header: string | null; lines: string[]; preserved: boolean } = {
899
+ header: null,
900
+ lines: [],
901
+ preserved: false,
902
+ }
903
+ let inRegion = false
904
+ for (const line of text.split('\n')) {
905
+ const trimmed = line.trim()
906
+ if (inRegion) {
907
+ current.lines.push(line)
908
+ if (trimmed === preserve?.end) {
909
+ inRegion = false
910
+ blocks.push(current)
911
+ current = { header: null, lines: [], preserved: false }
912
+ }
913
+ continue
914
+ }
915
+ if (preserve !== undefined && trimmed === preserve.begin) {
916
+ if (current.lines.length > 0 || current.header !== null) blocks.push(current)
917
+ current = { header: null, lines: [line], preserved: true }
918
+ inRegion = true
919
+ continue
920
+ }
921
+ if (trimmed.startsWith('[')) {
922
+ if (current.lines.length > 0) blocks.push(current)
923
+ current = { header: trimmed, lines: [line], preserved: false }
924
+ } else current.lines.push(line)
925
+ }
926
+ if (inRegion) throw new Error(`unterminated preserved region: ${preserve?.begin}`)
927
+ blocks.push(current)
928
+ return blocks
929
+ }
930
+
931
+ function tomlFirstSegment(header: string): string {
932
+ const name = tomlHeaderName(header).trim()
933
+ const quote = name[0]
934
+ if (quote === '"' || quote === "'") {
935
+ const close = name.indexOf(quote, 1)
936
+ return close === -1 ? name : name.slice(1, close)
937
+ }
938
+ return name.split('.')[0]!.trim()
939
+ }
940
+
941
+ interface TomlRootKey {
942
+ readonly key: string
943
+ /** Line indexes inside the root block, inclusive. */
944
+ readonly start: number
945
+ readonly end: number
946
+ }
947
+
948
+ /** Top-level `key = value` statements before the first table, including multi-line arrays/strings. */
949
+ function tomlRootKeys(lines: readonly string[]): readonly TomlRootKey[] {
950
+ const keys: TomlRootKey[] = []
951
+ let index = 0
952
+ while (index < lines.length) {
953
+ const match = /^\s*("[^"]*"|'[^']*'|[A-Za-z0-9_-]+)\s*(?:\.[^=]*)?=(.*)$/.exec(lines[index]!)
954
+ if (match === null) {
955
+ index += 1
956
+ continue
957
+ }
958
+ const rawKey = match[1]!
959
+ const key = rawKey.startsWith('"') || rawKey.startsWith("'") ? rawKey.slice(1, -1) : rawKey
960
+ const start = index
961
+ let depth = 0
962
+ let multiline: '"""' | "'''" | null = null
963
+ let text = match[2]!
964
+ for (;;) {
965
+ let position = 0
966
+ while (position < text.length) {
967
+ if (multiline !== null) {
968
+ const close = text.indexOf(multiline, position)
969
+ if (close === -1) {
970
+ position = text.length
971
+ break
972
+ }
973
+ position = close + 3
974
+ multiline = null
975
+ continue
976
+ }
977
+ const character = text[position]!
978
+ if (text.startsWith('"""', position) || text.startsWith("'''", position)) {
979
+ multiline = text.slice(position, position + 3) as '"""' | "'''"
980
+ position += 3
981
+ } else if (character === '"' || character === "'") {
982
+ let close = position + 1
983
+ while (close < text.length && text[close] !== character) close += character === '"' && text[close] === '\\' ? 2 : 1
984
+ position = close + 1
985
+ } else if (character === '#') {
986
+ break
987
+ } else {
988
+ if (character === '[' || character === '{') depth += 1
989
+ if (character === ']' || character === '}') depth -= 1
990
+ position += 1
991
+ }
992
+ }
993
+ if ((depth <= 0 && multiline === null) || index + 1 >= lines.length) break
994
+ index += 1
995
+ text = lines[index]!
996
+ }
997
+ keys.push({ key, start, end: index })
998
+ index += 1
999
+ }
1000
+ return keys
1001
+ }
1002
+
1003
+ export interface TextBlock {
1004
+ readonly name: string
1005
+ /** Line indexes, inclusive. */
1006
+ readonly start: number
1007
+ readonly end: number
1008
+ readonly text: string
1009
+ }
1010
+
1011
+ const TEXT_BLOCK_SYNTAXES: readonly { readonly open: RegExp; readonly close: (name: string) => string }[] = [
1012
+ { open: /^# >>> (.+?) >>>$/, close: (name) => `# <<< ${name} <<<` },
1013
+ { open: /^# (.+?) — begin$/, close: (name) => `# ${name} — end` },
1014
+ { open: /^<!-- (?!\/)(.+?) -->$/, close: (name) => `<!-- /${name} -->` },
1015
+ ]
1016
+
1017
+ /** Outermost tool-injected marker-delimited regions. Unterminated openers are ordinary lines. */
1018
+ export function findTextBlocks(text: string): readonly TextBlock[] {
1019
+ const lines = text.split('\n')
1020
+ const blocks: TextBlock[] = []
1021
+ let index = 0
1022
+ while (index < lines.length) {
1023
+ const trimmed = lines[index]!.trim()
1024
+ let found: TextBlock | null = null
1025
+ for (const syntax of TEXT_BLOCK_SYNTAXES) {
1026
+ const match = syntax.open.exec(trimmed)
1027
+ if (match === null) continue
1028
+ const name = match[1]!
1029
+ const closer = syntax.close(name)
1030
+ const end = lines.findIndex((line, candidate) => candidate > index && line.trim() === closer)
1031
+ if (end === -1) continue
1032
+ found = { name, start: index, end, text: lines.slice(index, end + 1).join('\n') }
1033
+ break
1034
+ }
1035
+ if (found === null) {
1036
+ index += 1
1037
+ continue
1038
+ }
1039
+ blocks.push(found)
1040
+ index = found.end + 1
1041
+ }
1042
+ return blocks
1043
+ }
1044
+
1045
+ function filterTextBlocks(
1046
+ text: string,
1047
+ target: string,
1048
+ protection: ProtectionList,
1049
+ ): { readonly text: string; readonly removals: readonly RemovalItem[]; readonly protected: readonly ProtectedItem[] } {
1050
+ const lines = text.split('\n')
1051
+ const drop = new Set<number>()
1052
+ const removals: RemovalItem[] = []
1053
+ const protectedItems: ProtectedItem[] = []
1054
+ for (const block of findTextBlocks(text)) {
1055
+ const removal: RemovalItem = { kind: 'text-block', target, name: block.name, beforeSha256: fingerprintBytes(block.text) }
1056
+ const reason = protectionFor(removal, protection)
1057
+ if (reason !== null) {
1058
+ protectedItems.push({ removal, reason })
1059
+ continue
1060
+ }
1061
+ removals.push(removal)
1062
+ for (let line = block.start; line <= block.end; line += 1) drop.add(line)
1063
+ let previous = block.start - 1
1064
+ while (previous >= 0 && drop.has(previous)) previous -= 1
1065
+ const next = block.end + 1
1066
+ if (next < lines.length && lines[next]!.trim() === '' && (previous < 0 || lines[previous]!.trim() === '')) {
1067
+ drop.add(next)
1068
+ }
1069
+ }
1070
+ if (removals.length === 0) return { text, removals, protected: protectedItems }
1071
+ return { text: lines.filter((_, index) => !drop.has(index)).join('\n'), removals, protected: protectedItems }
1072
+ }
1073
+
1074
+ function filterJsonRootKeys(
1075
+ document: unknown,
1076
+ target: string,
1077
+ allowed: readonly string[],
1078
+ protection: ProtectionList,
1079
+ ): { readonly document: unknown; readonly removals: readonly RemovalItem[] } | null {
1080
+ if (!isRecord(document)) return null
1081
+ const kept: Record<string, unknown> = {}
1082
+ const removals: RemovalItem[] = []
1083
+ for (const [name, entry] of Object.entries(document)) {
1084
+ const removal: RemovalItem = {
1085
+ kind: 'json-entry',
1086
+ target,
1087
+ selector: `/${jsonPointerPart(name)}`,
1088
+ beforeSha256: fingerprintJsonValue(entry),
1089
+ }
1090
+ if (allowed.includes(name) || protectionForValue(removal, entry, protection) !== null) kept[name] = entry
1091
+ else removals.push(removal)
1092
+ }
1093
+ if (removals.length === 0) return null
1094
+ return { document: kept, removals }
1095
+ }
1096
+
1097
+ function filterTomlRootKeys(
1098
+ text: string,
1099
+ target: string,
1100
+ allowed: readonly string[],
1101
+ protection: ProtectionList,
1102
+ ): { readonly text: string; readonly removals: readonly RemovalItem[] } {
1103
+ const blocks = splitTomlBlocks(text, preservedRegionFor(target))
1104
+ const kept: string[][] = []
1105
+ const removals: RemovalItem[] = []
1106
+ for (const [position, block] of blocks.entries()) {
1107
+ if (block.preserved) {
1108
+ kept.push([...block.lines])
1109
+ continue
1110
+ }
1111
+ if (block.header === null) {
1112
+ if (position !== 0) {
1113
+ kept.push([...block.lines])
1114
+ continue
1115
+ }
1116
+ const drop = new Set<number>()
1117
+ for (const rootKey of tomlRootKeys(block.lines)) {
1118
+ if (allowed.includes(rootKey.key)) continue
1119
+ const removed = block.lines.slice(rootKey.start, rootKey.end + 1)
1120
+ removals.push({ kind: 'toml-key', target, key: rootKey.key, beforeSha256: fingerprintBytes(removed.join('\n')) })
1121
+ for (let line = rootKey.start; line <= rootKey.end; line += 1) drop.add(line)
1122
+ }
1123
+ kept.push(block.lines.filter((_, index) => !drop.has(index)))
1124
+ continue
1125
+ }
1126
+ const removal: RemovalItem = {
1127
+ kind: 'toml-table',
1128
+ target,
1129
+ header: block.header,
1130
+ beforeSha256: fingerprintBytes(block.lines.join('\n')),
1131
+ }
1132
+ if (
1133
+ allowed.includes(tomlFirstSegment(block.header)) ||
1134
+ protectionForValue(removal, block.lines.join('\n'), protection) !== null
1135
+ ) {
1136
+ kept.push([...block.lines])
1137
+ } else removals.push(removal)
1138
+ }
1139
+ return {
1140
+ text: removals.length === 0 ? text : kept.flat().join('\n').replace(/\n{3,}/g, '\n\n'),
1141
+ removals,
1142
+ }
1143
+ }
1144
+
1145
+ function filterTomlTables(
1146
+ text: string,
1147
+ prefixes: readonly string[],
1148
+ target: string,
1149
+ protection: ProtectionList,
1150
+ ): { readonly text: string; readonly removals: readonly RemovalItem[]; readonly protected: readonly ProtectedItem[] } {
1151
+ const blocks = splitTomlBlocks(text, preservedRegionFor(target))
1152
+ const kept: string[][] = []
1153
+ const removals: RemovalItem[] = []
1154
+ const protectedItems: ProtectedItem[] = []
1155
+ for (const block of blocks) {
1156
+ if (block.header === null || block.preserved) {
1157
+ kept.push([...block.lines])
1158
+ continue
1159
+ }
1160
+ const name = tomlHeaderName(block.header)
1161
+ const selected = prefixes.some((prefix) => name === prefix || name.startsWith(`${prefix}.`))
1162
+ if (!selected) {
1163
+ kept.push([...block.lines])
1164
+ continue
1165
+ }
1166
+ const removal: RemovalItem = {
1167
+ kind: 'toml-table',
1168
+ target,
1169
+ header: block.header,
1170
+ beforeSha256: fingerprintBytes(block.lines.join('\n')),
1171
+ }
1172
+ const reason = protectionForValue(removal, block.lines.join('\n'), protection)
1173
+ if (reason === null) removals.push(removal)
1174
+ else {
1175
+ kept.push([...block.lines])
1176
+ protectedItems.push({ removal, reason })
1177
+ }
1178
+ }
1179
+
1180
+ return {
1181
+ text: removals.length === 0 ? text : kept.flat().join('\n').replace(/\n{3,}/g, '\n\n'),
1182
+ removals,
1183
+ protected: protectedItems,
1184
+ }
1185
+ }
1186
+
1187
+ function entriesOf(dir: string, notes: SweepNote[]): readonly string[] {
1188
+ if (!pathExists(dir)) return []
1189
+ try {
1190
+ if (!lstatSync(dir).isDirectory()) {
1191
+ notes.push({ level: 'warn', text: `${dir} is not a directory; left untouched` })
1192
+ return []
1193
+ }
1194
+ return readdirSync(dir).filter((name) => name !== '.DS_Store')
1195
+ } catch (cause) {
1196
+ notes.push({ level: 'warn', text: `${dir} could not be read; left untouched (${messageOf(cause)})` })
1197
+ return []
1198
+ }
1199
+ }
1200
+
1201
+ /** True iff `marker` appears as an exact, standalone line within the first 5 lines of `content`. */
1202
+ function hasMarkerLine(content: string, marker: string): boolean {
1203
+ return content.split('\n').slice(0, 5).includes(marker)
1204
+ }
1205
+
1206
+ function launchAgentInScope(path: string, name: string): boolean {
1207
+ const stat = lstatSync(path)
1208
+ return isAgentToolingLaunchAgent(name, stat.isFile() ? readFileSync(path).toString('latin1') : '')
1209
+ }
1210
+
1211
+ function editableFile(
1212
+ home: string,
1213
+ relativePath: string,
1214
+ drafts: Map<string, DraftFile>,
1215
+ notes: SweepNote[],
1216
+ ): DraftFile | null {
1217
+ const path = insideHome(home, relativePath)
1218
+ const existing = drafts.get(path)
1219
+ if (existing !== undefined) return existing
1220
+ if (!pathExists(path)) return null
1221
+
1222
+ const stat = lstatSync(path)
1223
+ if (!stat.isFile()) {
1224
+ notes.push({ level: 'warn', text: `${path} is not a regular file; left untouched` })
1225
+ return null
1226
+ }
1227
+ const content = readFileSync(path, 'utf8')
1228
+ const draft: DraftFile = {
1229
+ path,
1230
+ relativePath,
1231
+ original: content,
1232
+ content,
1233
+ mode: stat.mode & 0o777,
1234
+ uid: stat.uid,
1235
+ gid: stat.gid,
1236
+ removals: [],
1237
+ }
1238
+ drafts.set(path, draft)
1239
+ return draft
1240
+ }
1241
+
1242
+ export function planGlobalSweep(
1243
+ homeInput: string,
1244
+ protection: ProtectionList = loadProtectionList(),
1245
+ ): SweepPlan {
1246
+ if (!isAbsolute(homeInput)) throw new Error(`--home must be absolute: ${homeInput}`)
1247
+ const home = resolve(homeInput)
1248
+ const drafts = new Map<string, DraftFile>()
1249
+ const removals = new Map<string, SweepOperation>()
1250
+ const protectedItems: ProtectedItem[] = []
1251
+ const notes: SweepNote[] = []
1252
+
1253
+ // Declared shipped baselines run first, so a wholly foreign container key is
1254
+ // reported once as a root removal instead of entry-by-entry.
1255
+ for (const baseline of protection.rootKeys) {
1256
+ if (!managedFileTarget(baseline.target)) {
1257
+ throw new Error(`root key baseline targets an unmanaged file: ${baseline.target}`)
1258
+ }
1259
+ const draft = editableFile(home, baseline.target, drafts, notes)
1260
+ if (draft === null) continue
1261
+ if (baseline.target.endsWith('.toml')) {
1262
+ const result = filterTomlRootKeys(draft.content, baseline.target, baseline.keys, protection)
1263
+ if (result.removals.length === 0) continue
1264
+ draft.content = result.text
1265
+ draft.removals.push(...result.removals)
1266
+ continue
1267
+ }
1268
+ let parsed: unknown
1269
+ try {
1270
+ parsed = JSON.parse(draft.content)
1271
+ } catch {
1272
+ notes.push({ level: 'warn', text: `${draft.path} is not valid JSON; left untouched` })
1273
+ continue
1274
+ }
1275
+ const result = filterJsonRootKeys(parsed, baseline.target, baseline.keys, protection)
1276
+ if (result === null) continue
1277
+ draft.content = `${JSON.stringify(result.document, null, 2)}\n`
1278
+ draft.removals.push(...result.removals)
1279
+ }
1280
+
1281
+ for (const target of SWEEP_TARGETS) {
1282
+ if (target.kind === 'text-blocks') {
1283
+ const draft = editableFile(home, target.file, drafts, notes)
1284
+ if (draft === null) continue
1285
+ const result = filterTextBlocks(draft.content, target.file, protection)
1286
+ protectedItems.push(...result.protected)
1287
+ if (result.removals.length === 0) continue
1288
+ draft.content = result.text
1289
+ draft.removals.push(...result.removals)
1290
+ continue
1291
+ }
1292
+
1293
+ if (target.kind === 'json-map' || target.kind === 'json-hook-map' || target.kind === 'json-root') {
1294
+ const draft = editableFile(home, target.file, drafts, notes)
1295
+ if (draft === null) continue
1296
+ let parsed: unknown
1297
+ try {
1298
+ parsed = JSON.parse(draft.content)
1299
+ } catch {
1300
+ notes.push({ level: 'warn', text: `${draft.path} is not valid JSON; left untouched` })
1301
+ continue
1302
+ }
1303
+
1304
+ if (target.kind === 'json-map') {
1305
+ const result = filterJsonMap(parsed, target.key, target.file, protection)
1306
+ if (result === null) continue
1307
+ protectedItems.push(...result.protected)
1308
+ if (result.removals.length === 0) continue
1309
+ draft.content = `${JSON.stringify(result.document, null, 2)}\n`
1310
+ draft.removals.push(...result.removals)
1311
+ } else if (target.kind === 'json-hook-map') {
1312
+ const result = filterJsonHookMap(parsed, target.key, target.file, protection)
1313
+ if (result === null) continue
1314
+ protectedItems.push(...result.protected)
1315
+ if (result.removals.length === 0) continue
1316
+ draft.content = `${JSON.stringify(result.document, null, 2)}\n`
1317
+ draft.removals.push(...result.removals)
1318
+ } else {
1319
+ const result = filterJsonRoot(parsed, target.file, protection)
1320
+ if (result === null) continue
1321
+ protectedItems.push(...result.protected)
1322
+ if (result.removals.length === 0) continue
1323
+ draft.content = `${JSON.stringify(result.document, null, 2)}\n`
1324
+ draft.removals.push(...result.removals)
1325
+ }
1326
+ continue
1327
+ }
1328
+
1329
+ if (target.kind === 'toml-tables') {
1330
+ const draft = editableFile(home, target.file, drafts, notes)
1331
+ if (draft === null) continue
1332
+ const result = filterTomlTables(draft.content, target.prefixes, target.file, protection)
1333
+ protectedItems.push(...result.protected)
1334
+ if (result.removals.length === 0) continue
1335
+ draft.content = result.text
1336
+ draft.removals.push(...result.removals)
1337
+ continue
1338
+ }
1339
+
1340
+ if (target.kind === 'dir-entries') {
1341
+ const dir = insideHome(home, target.dir)
1342
+ for (const name of entriesOf(dir, notes)) {
1343
+ const path = join(dir, name)
1344
+ if (target.ignore?.includes(name) === true) continue
1345
+ if (target.descend?.includes(name) === true && lstatSync(path).isDirectory()) continue
1346
+ const relativePath = relativeInside(home, path)
1347
+ const beforeSha256 = fingerprintPath(path)
1348
+ const removal: RemovalItem = { kind: 'path', target: relativePath, beforeSha256 }
1349
+ const reason = protectionFor(removal, protection)
1350
+ if (reason !== null) {
1351
+ protectedItems.push({ removal, reason })
1352
+ continue
1353
+ }
1354
+ if (target.dir === LAUNCH_AGENTS_DIR && !launchAgentInScope(path, name)) continue
1355
+ removals.set(relativePath, {
1356
+ id: `remove-path:${relativePath}`,
1357
+ kind: 'remove-path',
1358
+ path: relativePath,
1359
+ absolutePath: path,
1360
+ beforeSha256,
1361
+ removals: [removal],
1362
+ requiresApproval: target.requiresApproval === true,
1363
+ })
1364
+ }
1365
+ continue
1366
+ }
1367
+
1368
+ if (target.kind === 'marker-files') {
1369
+ const dir = insideHome(home, target.dir)
1370
+ for (const name of entriesOf(dir, notes)) {
1371
+ const path = join(dir, name)
1372
+ if (!lstatSync(path).isFile()) continue
1373
+ if (!hasMarkerLine(readFileSync(path, 'utf8'), target.marker)) continue
1374
+ const relativePath = relativeInside(home, path)
1375
+ const beforeSha256 = fingerprintPath(path)
1376
+ const removal: RemovalItem = { kind: 'path', target: relativePath, beforeSha256 }
1377
+ const reason = protectionFor(removal, protection)
1378
+ if (reason !== null) {
1379
+ protectedItems.push({ removal, reason })
1380
+ continue
1381
+ }
1382
+ removals.set(relativePath, {
1383
+ id: `remove-path:${relativePath}`,
1384
+ kind: 'remove-path',
1385
+ path: relativePath,
1386
+ absolutePath: path,
1387
+ beforeSha256,
1388
+ removals: [removal],
1389
+ requiresApproval: false,
1390
+ })
1391
+ }
1392
+ continue
1393
+ }
1394
+
1395
+ const path = insideHome(home, target.path)
1396
+ if (!pathExists(path)) continue
1397
+ const relativePath = relativeInside(home, path)
1398
+ const beforeSha256 = fingerprintPath(path)
1399
+ const removal: RemovalItem = { kind: 'path', target: relativePath, beforeSha256 }
1400
+ const reason = protectionFor(removal, protection)
1401
+ if (reason !== null) {
1402
+ protectedItems.push({ removal, reason })
1403
+ continue
1404
+ }
1405
+ removals.set(relativePath, {
1406
+ id: `remove-path:${relativePath}`,
1407
+ kind: 'remove-path',
1408
+ path: relativePath,
1409
+ absolutePath: path,
1410
+ beforeSha256,
1411
+ removals: [removal],
1412
+ requiresApproval: false,
1413
+ })
1414
+ }
1415
+
1416
+ const writes: SweepOperation[] = []
1417
+ for (const draft of drafts.values()) {
1418
+ if (draft.content === draft.original) continue
1419
+ writes.push({
1420
+ id: `write-file:${draft.relativePath}`,
1421
+ kind: 'write-file',
1422
+ path: draft.relativePath,
1423
+ absolutePath: draft.path,
1424
+ beforeSha256: fingerprintPath(draft.path),
1425
+ removals: [...draft.removals].sort((a, b) => describeRemoval(a).localeCompare(describeRemoval(b))),
1426
+ content: draft.content,
1427
+ mode: draft.mode,
1428
+ uid: draft.uid,
1429
+ gid: draft.gid,
1430
+ requiresApproval: false,
1431
+ })
1432
+ }
1433
+
1434
+ const operations = [...writes, ...removals.values()].sort((a, b) => a.id.localeCompare(b.id))
1435
+ protectedItems.sort((a, b) => describeRemoval(a.removal).localeCompare(describeRemoval(b.removal)))
1436
+ if (operations.length === 0 && notes.length === 0) {
1437
+ notes.push({
1438
+ level: 'ok',
1439
+ text:
1440
+ protectedItems.length === 0
1441
+ ? 'global agent surfaces are already clean'
1442
+ : 'no unprotected global agent surface candidates found',
1443
+ })
1444
+ }
1445
+ const uniqueNotes = notes.filter(
1446
+ (note, index) => notes.findIndex((other) => other.level === note.level && other.text === note.text) === index,
1447
+ )
1448
+ return { operations, protected: protectedItems, notes: uniqueNotes }
1449
+ }
1450
+
1451
+ function publicOperation(operation: SweepOperation): ApprovedOperation {
1452
+ return {
1453
+ id: operation.id,
1454
+ kind: operation.kind,
1455
+ path: operation.path,
1456
+ beforeSha256: operation.beforeSha256,
1457
+ removals: operation.removals,
1458
+ }
1459
+ }
1460
+
1461
+ export function createApprovalPlan(
1462
+ home: string,
1463
+ generatedAt = new Date().toISOString(),
1464
+ protection: ProtectionList = loadProtectionList(),
1465
+ ): ApprovalPlan {
1466
+ const resolvedHome = resolve(home)
1467
+ return {
1468
+ schema: 1,
1469
+ home: resolvedHome,
1470
+ generatedAt,
1471
+ operations: planGlobalSweep(resolvedHome, protection).operations.map(publicOperation),
1472
+ }
1473
+ }
1474
+
1475
+ function isRecord(value: unknown): value is Record<string, unknown> {
1476
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
1477
+ }
1478
+
1479
+ function decodeRemovalItem(value: unknown): RemovalItem {
1480
+ if (
1481
+ !isRecord(value) ||
1482
+ typeof value.target !== 'string' ||
1483
+ typeof value.beforeSha256 !== 'string' ||
1484
+ !/^sha256:[0-9a-f]{64}$/.test(value.beforeSha256)
1485
+ ) {
1486
+ throw new Error('invalid removal item')
1487
+ }
1488
+ if (value.kind === 'path') {
1489
+ return { kind: 'path', target: value.target, beforeSha256: value.beforeSha256 }
1490
+ }
1491
+ if (value.kind === 'json-entry' && typeof value.selector === 'string') {
1492
+ return {
1493
+ kind: 'json-entry',
1494
+ target: value.target,
1495
+ selector: value.selector,
1496
+ beforeSha256: value.beforeSha256,
1497
+ }
1498
+ }
1499
+ if (value.kind === 'toml-key' && typeof value.key === 'string') {
1500
+ return { kind: 'toml-key', target: value.target, key: value.key, beforeSha256: value.beforeSha256 }
1501
+ }
1502
+ if (value.kind === 'text-block' && typeof value.name === 'string') {
1503
+ return { kind: 'text-block', target: value.target, name: value.name, beforeSha256: value.beforeSha256 }
1504
+ }
1505
+ if (value.kind === 'toml-table' && typeof value.header === 'string') {
1506
+ return {
1507
+ kind: 'toml-table',
1508
+ target: value.target,
1509
+ header: value.header,
1510
+ beforeSha256: value.beforeSha256,
1511
+ }
1512
+ }
1513
+ throw new Error('invalid removal item')
1514
+ }
1515
+
1516
+ export function decodeApprovalPlan(value: unknown): ApprovalPlan {
1517
+ if (!isRecord(value) || value.schema !== 1 || typeof value.home !== 'string' || !isAbsolute(value.home)) {
1518
+ throw new Error('approval plan must have schema 1 and an absolute home')
1519
+ }
1520
+ if (typeof value.generatedAt !== 'string' || !Array.isArray(value.operations)) {
1521
+ throw new Error('approval plan is missing generatedAt or operations')
1522
+ }
1523
+
1524
+ const operations: ApprovedOperation[] = []
1525
+ const ids = new Set<string>()
1526
+ for (const raw of value.operations) {
1527
+ if (
1528
+ !isRecord(raw) ||
1529
+ (raw.kind !== 'write-file' && raw.kind !== 'remove-path') ||
1530
+ typeof raw.path !== 'string' ||
1531
+ typeof raw.id !== 'string' ||
1532
+ raw.id !== `${raw.kind}:${raw.path}` ||
1533
+ typeof raw.beforeSha256 !== 'string' ||
1534
+ !/^sha256:[0-9a-f]{64}$/.test(raw.beforeSha256) ||
1535
+ !Array.isArray(raw.removals)
1536
+ ) {
1537
+ throw new Error('approval plan contains an invalid operation')
1538
+ }
1539
+ if (ids.has(raw.id)) throw new Error(`approval plan contains duplicate operation: ${raw.id}`)
1540
+ ids.add(raw.id)
1541
+ operations.push({
1542
+ id: raw.id,
1543
+ kind: raw.kind,
1544
+ path: raw.path,
1545
+ beforeSha256: raw.beforeSha256,
1546
+ removals: raw.removals.map(decodeRemovalItem),
1547
+ })
1548
+ }
1549
+
1550
+ return { schema: 1, home: resolve(value.home), generatedAt: value.generatedAt, operations }
1551
+ }
1552
+
1553
+ function knownOperation(operation: ApprovedOperation): boolean {
1554
+ if (operation.kind === 'write-file') return managedFileTarget(operation.path)
1555
+ return SWEEP_TARGETS.some(
1556
+ (target) =>
1557
+ (target.kind === 'path' && target.path === operation.path) ||
1558
+ (target.kind === 'dir-entries' && dirname(operation.path) === target.dir) ||
1559
+ (target.kind === 'marker-files' && dirname(operation.path) === target.dir),
1560
+ )
1561
+ }
1562
+
1563
+ function sameRemovals(left: readonly RemovalItem[], right: readonly RemovalItem[]): boolean {
1564
+ return JSON.stringify(left) === JSON.stringify(right)
1565
+ }
1566
+
1567
+ function atomicWrite(path: string, content: string, mode: number, uid?: number, gid?: number): void {
1568
+ const temporary = join(dirname(path), `.global-surface-sweep.${process.pid}.${Date.now()}.tmp`)
1569
+ try {
1570
+ writeFileSync(temporary, content, { mode })
1571
+ chmodSync(temporary, mode)
1572
+ if (uid !== undefined && gid !== undefined) {
1573
+ const temporaryStat = lstatSync(temporary)
1574
+ if (temporaryStat.uid !== uid || temporaryStat.gid !== gid) chownSync(temporary, uid, gid)
1575
+ }
1576
+ renameSync(temporary, path)
1577
+ } catch (cause) {
1578
+ rmSync(temporary, { force: true })
1579
+ throw cause
1580
+ }
1581
+ }
1582
+
1583
+ /**
1584
+ * Best-effort unload of a LaunchAgent immediately before its plist is removed
1585
+ * (moved to backup), so an approved removal never leaves an orphaned running
1586
+ * job. `launchctl bootout` on a job that is not currently loaded is expected
1587
+ * and harmless (the overwhelmingly common case for a stale/foreign plist); we
1588
+ * only fail loud when the job is verifiably still loaded afterward, since
1589
+ * deleting the plist out from under a still-running job is the unsafe outcome
1590
+ * this exists to prevent. macOS-only: this directory only exists on Darwin.
1591
+ */
1592
+ function bootOutLaunchAgentIfPresent(relativePath: string): void {
1593
+ if (process.platform !== 'darwin') return
1594
+ if (dirname(relativePath) !== LAUNCH_AGENTS_DIR) return
1595
+ const base = relativePath.slice(relativePath.lastIndexOf('/') + 1)
1596
+ const label = base.endsWith('.plist') ? base.slice(0, -'.plist'.length) : base
1597
+ const uid = typeof process.getuid === 'function' ? process.getuid() : 0
1598
+ spawnSync('launchctl', ['bootout', `gui/${uid}/${label}`], { encoding: 'utf8' })
1599
+ const stillLoaded = spawnSync('launchctl', ['list', label], { encoding: 'utf8' })
1600
+ if (stillLoaded.status === 0) {
1601
+ throw new Error(`LaunchAgent ${label} is still loaded after bootout; refusing to remove ${relativePath}`)
1602
+ }
1603
+ }
1604
+
1605
+ function backupPath(backupRoot: string, relativePath: string): string {
1606
+ const path = resolve(backupRoot, relativePath)
1607
+ relativeInside(backupRoot, path)
1608
+ return path
1609
+ }
1610
+
1611
+ /**
1612
+ * CLI-only reporting (Tarun, 2026-09-28): print the exact original -> backup
1613
+ * mapping for every file this run copied (write-file) or moved (remove-path)
1614
+ * into the backup root `applyApprovedSweep` already creates before touching
1615
+ * anything -- see the `selected` loop there, which backs up every operation
1616
+ * it applies and fails loudly (throws, and rolls back what it already
1617
+ * applied) if a backup step itself fails. When the run applied nothing
1618
+ * (report-only audit, or an already-clean apply/enforce), there is no
1619
+ * backup root at all, so this prints the explicit "no backups" line instead
1620
+ * of an empty block.
1621
+ */
1622
+ function printBackupsThisRun(home: string, applied: readonly ApprovedOperation[], backupRoot: string | null): void {
1623
+ if (backupRoot === null || applied.length === 0) {
1624
+ console.log('global-surface-sweep: no files changed; no backups')
1625
+ return
1626
+ }
1627
+ console.log('global-surface-sweep: backups this run:')
1628
+ for (const operation of applied) {
1629
+ console.log(` ${resolve(home, operation.path)} -> ${backupPath(backupRoot, operation.path)}`)
1630
+ }
1631
+ }
1632
+
1633
+ function reviewSurface(relativePath: string): { readonly surface: string; readonly path: string } {
1634
+ for (const [prefix, surface] of [
1635
+ ['.agents/', 'agents'],
1636
+ ['.claude/', 'claude'],
1637
+ ['.codex/', 'codex'],
1638
+ ['.cursor/', 'cursor'],
1639
+ ['.config/opencode/', 'opencode'],
1640
+ ['.kimi-code/', 'kimi'],
1641
+ ['Library/LaunchAgents/', 'launchagents'],
1642
+ ['.local/bin/', 'localbin'],
1643
+ ] as const) {
1644
+ if (relativePath.startsWith(prefix)) return { surface, path: relativePath.slice(prefix.length) }
1645
+ }
1646
+ if (relativePath === '.claude.json') return { surface: 'claude', path: 'home-.claude.json' }
1647
+ if (SHELL_RC_FILES.includes(relativePath)) return { surface: 'shell', path: relativePath }
1648
+ throw new Error(`cannot classify review-copy surface: ${relativePath}`)
1649
+ }
1650
+
1651
+ function reviewPath(destination: string, relativePath: string, category: string, suffix = ''): string {
1652
+ const classified = reviewSurface(relativePath)
1653
+ const path = resolve(destination, classified.surface, category, `${classified.path}${suffix}`)
1654
+ relativeInside(destination, path)
1655
+ return path
1656
+ }
1657
+
1658
+ function writeReviewFile(path: string, content: string, mode = 0o600): void {
1659
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
1660
+ writeFileSync(path, content, { mode })
1661
+ chmodSync(path, mode)
1662
+ }
1663
+
1664
+ function jsonPointerValue(document: unknown, selector: string): unknown {
1665
+ let current = document
1666
+ for (const encoded of selector.split('/').slice(1)) {
1667
+ const key = encoded.replaceAll('~1', '/').replaceAll('~0', '~')
1668
+ if (current === null || typeof current !== 'object' || !(key in current)) {
1669
+ throw new Error(`JSON selector no longer exists: ${selector}`)
1670
+ }
1671
+ current = (current as Record<string, unknown>)[key]
1672
+ }
1673
+ return current
1674
+ }
1675
+
1676
+ export function copyReviewBundle(
1677
+ homeInput: string,
1678
+ destinationInput: string,
1679
+ protection: ProtectionList = loadProtectionList(),
1680
+ generatedAt = new Date().toISOString(),
1681
+ ): ReviewCopyResult {
1682
+ if (!isAbsolute(homeInput) || !isAbsolute(destinationInput)) {
1683
+ throw new Error('--home and --destination must be absolute')
1684
+ }
1685
+ const home = resolve(homeInput)
1686
+ const destination = resolve(destinationInput)
1687
+ if (destination === home) throw new Error('review-copy destination cannot be the home directory')
1688
+ if (pathExists(destination)) {
1689
+ if (!lstatSync(destination).isDirectory()) throw new Error('review-copy destination is not a directory')
1690
+ if (readdirSync(destination).length > 0) throw new Error('review-copy destination must be empty')
1691
+ } else mkdirSync(destination, { recursive: true, mode: 0o700 })
1692
+ chmodSync(destination, 0o700)
1693
+
1694
+ const plan = planGlobalSweep(home, protection)
1695
+ for (const operation of plan.operations) {
1696
+ if (destination === operation.absolutePath || destination.startsWith(`${operation.absolutePath}${sep}`)) {
1697
+ throw new Error(`review-copy destination is inside candidate: ${operation.path}`)
1698
+ }
1699
+ if (!pathExists(operation.absolutePath) || fingerprintPath(operation.absolutePath) !== operation.beforeSha256) {
1700
+ throw new Error(`candidate drifted before review copy: ${operation.path}`)
1701
+ }
1702
+ }
1703
+
1704
+ const copies: {
1705
+ readonly source: string
1706
+ readonly destination: string
1707
+ readonly kind: 'path' | 'config-original' | 'proposed-after' | 'removed-fragment'
1708
+ }[] = []
1709
+ let copiedPaths = 0
1710
+ let copiedConfigFiles = 0
1711
+
1712
+ for (const operation of plan.operations) {
1713
+ if (operation.kind === 'remove-path') {
1714
+ const copy = reviewPath(destination, operation.path, 'paths')
1715
+ mkdirSync(dirname(copy), { recursive: true, mode: 0o700 })
1716
+ cpSync(operation.absolutePath, copy, {
1717
+ recursive: true,
1718
+ dereference: false,
1719
+ errorOnExist: true,
1720
+ preserveTimestamps: true,
1721
+ verbatimSymlinks: true,
1722
+ })
1723
+ if (fingerprintPath(copy) !== operation.beforeSha256) {
1724
+ throw new Error(`review copy fingerprint mismatch: ${operation.path}`)
1725
+ }
1726
+ copies.push({ source: operation.path, destination: relativeInside(destination, copy), kind: 'path' })
1727
+ copiedPaths += 1
1728
+ continue
1729
+ }
1730
+
1731
+ const original = reviewPath(destination, operation.path, 'config-originals')
1732
+ mkdirSync(dirname(original), { recursive: true, mode: 0o700 })
1733
+ copyFileSync(operation.absolutePath, original)
1734
+ chmodSync(original, operation.mode)
1735
+ if (fingerprintPath(original) !== operation.beforeSha256) {
1736
+ throw new Error(`review config copy fingerprint mismatch: ${operation.path}`)
1737
+ }
1738
+ copies.push({
1739
+ source: operation.path,
1740
+ destination: relativeInside(destination, original),
1741
+ kind: 'config-original',
1742
+ })
1743
+
1744
+ const proposed = reviewPath(destination, operation.path, 'proposed-after')
1745
+ writeReviewFile(proposed, operation.content)
1746
+ copies.push({
1747
+ source: operation.path,
1748
+ destination: relativeInside(destination, proposed),
1749
+ kind: 'proposed-after',
1750
+ })
1751
+
1752
+ const jsonRemovals = operation.removals.filter(
1753
+ (removal): removal is Extract<RemovalItem, { readonly kind: 'json-entry' }> =>
1754
+ removal.kind === 'json-entry',
1755
+ )
1756
+ if (jsonRemovals.length > 0) {
1757
+ const document = JSON.parse(readFileSync(operation.absolutePath, 'utf8'))
1758
+ const entries = jsonRemovals.map((removal) => {
1759
+ const value = jsonPointerValue(document, removal.selector)
1760
+ if (fingerprintJsonValue(value) !== removal.beforeSha256) {
1761
+ throw new Error(`JSON removal drifted during review copy: ${operation.path}#${removal.selector}`)
1762
+ }
1763
+ return { selector: removal.selector, beforeSha256: removal.beforeSha256, value }
1764
+ })
1765
+ const removed = reviewPath(destination, operation.path, 'removed-fragments', '.removed.json')
1766
+ writeReviewFile(removed, `${JSON.stringify({ source: operation.path, entries }, null, 2)}\n`)
1767
+ copies.push({
1768
+ source: operation.path,
1769
+ destination: relativeInside(destination, removed),
1770
+ kind: 'removed-fragment',
1771
+ })
1772
+ }
1773
+
1774
+ const tomlRemovals = operation.removals.filter(
1775
+ (removal): removal is Extract<RemovalItem, { readonly kind: 'toml-table' | 'toml-key' }> =>
1776
+ removal.kind === 'toml-table' || removal.kind === 'toml-key',
1777
+ )
1778
+ if (tomlRemovals.length > 0) {
1779
+ const blocks = splitTomlBlocks(readFileSync(operation.absolutePath, 'utf8'), preservedRegionFor(operation.path))
1780
+ const rootBlock = blocks[0]?.header === null && blocks[0]?.preserved === false ? blocks[0] : null
1781
+ const rootKeyTexts = rootBlock === null
1782
+ ? []
1783
+ : tomlRootKeys(rootBlock.lines).map((rootKey) => ({
1784
+ key: rootKey.key,
1785
+ text: rootBlock.lines.slice(rootKey.start, rootKey.end + 1).join('\n'),
1786
+ }))
1787
+ const removedBlocks = tomlRemovals.map((removal) => {
1788
+ if (removal.kind === 'toml-key') {
1789
+ const match = rootKeyTexts.find(
1790
+ (candidate) => candidate.key === removal.key && fingerprintBytes(candidate.text) === removal.beforeSha256,
1791
+ )
1792
+ if (match === undefined) {
1793
+ throw new Error(`TOML key removal drifted during review copy: ${operation.path} ${removal.key}`)
1794
+ }
1795
+ return match.text
1796
+ }
1797
+ const block = blocks.find(
1798
+ (candidate) =>
1799
+ candidate.header === removal.header &&
1800
+ fingerprintBytes(candidate.lines.join('\n')) === removal.beforeSha256,
1801
+ )
1802
+ if (block === undefined) {
1803
+ throw new Error(`TOML removal drifted during review copy: ${operation.path} ${removal.header}`)
1804
+ }
1805
+ return block.lines.join('\n')
1806
+ })
1807
+ const removed = reviewPath(destination, operation.path, 'removed-fragments', '.removed.toml')
1808
+ writeReviewFile(removed, removedBlocks.join('\n'))
1809
+ copies.push({
1810
+ source: operation.path,
1811
+ destination: relativeInside(destination, removed),
1812
+ kind: 'removed-fragment',
1813
+ })
1814
+ }
1815
+ const blockRemovals = operation.removals.filter(
1816
+ (removal): removal is Extract<RemovalItem, { readonly kind: 'text-block' }> => removal.kind === 'text-block',
1817
+ )
1818
+ if (blockRemovals.length > 0) {
1819
+ const blocks = findTextBlocks(readFileSync(operation.absolutePath, 'utf8'))
1820
+ const removedTexts = blockRemovals.map((removal) => {
1821
+ const block = blocks.find(
1822
+ (candidate) => candidate.name === removal.name && fingerprintBytes(candidate.text) === removal.beforeSha256,
1823
+ )
1824
+ if (block === undefined) {
1825
+ throw new Error(`text block removal drifted during review copy: ${operation.path} ${removal.name}`)
1826
+ }
1827
+ return block.text
1828
+ })
1829
+ const removed = reviewPath(destination, operation.path, 'removed-fragments', '.removed.txt')
1830
+ writeReviewFile(removed, `${removedTexts.join('\n\n')}\n`)
1831
+ copies.push({
1832
+ source: operation.path,
1833
+ destination: relativeInside(destination, removed),
1834
+ kind: 'removed-fragment',
1835
+ })
1836
+ }
1837
+ copiedConfigFiles += 1
1838
+ }
1839
+
1840
+ for (const operation of plan.operations) {
1841
+ if (!pathExists(operation.absolutePath) || fingerprintPath(operation.absolutePath) !== operation.beforeSha256) {
1842
+ throw new Error(`candidate drifted during review copy: ${operation.path}`)
1843
+ }
1844
+ }
1845
+
1846
+ const candidateItems = plan.operations.reduce((count, operation) => count + operation.removals.length, 0)
1847
+ writeReviewFile(
1848
+ join(destination, 'manifest.json'),
1849
+ `${JSON.stringify(
1850
+ {
1851
+ schema: 1,
1852
+ generatedAt,
1853
+ sourceHome: home,
1854
+ notice: 'Review copy only. No live agent surface was changed or deleted.',
1855
+ candidateOperations: plan.operations.length,
1856
+ candidateItems,
1857
+ protectedItems: plan.protected.length,
1858
+ copies,
1859
+ operations: plan.operations.map(publicOperation),
1860
+ protected: plan.protected,
1861
+ },
1862
+ null,
1863
+ 2,
1864
+ )}\n`,
1865
+ )
1866
+ writeReviewFile(
1867
+ join(destination, 'README.md'),
1868
+ [
1869
+ '# Global surface sweep review copy',
1870
+ '',
1871
+ 'This directory is a non-destructive snapshot. No live agent surface was changed or deleted.',
1872
+ '',
1873
+ `- Candidate operations: ${plan.operations.length}`,
1874
+ `- Candidate items: ${candidateItems}`,
1875
+ `- Protected items not copied as candidates: ${plan.protected.length}`,
1876
+ '',
1877
+ 'Each surface contains `paths/` for exact filesystem candidates, `config-originals/` for the',
1878
+ 'full source configuration, `proposed-after/` for the planned result, and `removed-fragments/`',
1879
+ 'for the exact JSON values or TOML blocks selected for removal. `manifest.json` records hashes',
1880
+ 'and source-to-copy mappings. Some removed fragments may contain credentials; keep this directory private.',
1881
+ '',
1882
+ ].join('\n'),
1883
+ )
1884
+
1885
+ return {
1886
+ destination,
1887
+ copiedPaths,
1888
+ copiedConfigFiles,
1889
+ candidateItems,
1890
+ protectedItems: plan.protected.length,
1891
+ }
1892
+ }
1893
+
1894
+ export function applyApprovedSweep(
1895
+ homeInput: string,
1896
+ approvalInput: ApprovalPlan,
1897
+ stateDirInput: string,
1898
+ now = new Date().toISOString(),
1899
+ protection: ProtectionList = loadProtectionList(),
1900
+ ): ApplyResult {
1901
+ if (!isAbsolute(homeInput) || !isAbsolute(stateDirInput)) {
1902
+ throw new Error('--home and --state-dir must be absolute')
1903
+ }
1904
+ const home = resolve(homeInput)
1905
+ const stateDir = resolve(stateDirInput)
1906
+ const approval = decodeApprovalPlan(approvalInput)
1907
+ if (approval.home !== home) throw new Error(`approval plan is for ${approval.home}, not ${home}`)
1908
+
1909
+ const currentPlan = planGlobalSweep(home, protection)
1910
+ const currentById = new Map(currentPlan.operations.map((operation) => [operation.id, operation]))
1911
+ const approvedIds = new Set(approval.operations.map((operation) => operation.id))
1912
+ const selected: SweepOperation[] = []
1913
+ const alreadyClean: string[] = []
1914
+
1915
+ for (const approved of approval.operations) {
1916
+ if (!knownOperation(approved)) throw new Error(`approval operation is outside managed surfaces: ${approved.id}`)
1917
+ const current = currentById.get(approved.id)
1918
+ if (current === undefined) {
1919
+ const path = insideHome(home, approved.path)
1920
+ if (approved.kind === 'remove-path' && pathExists(path)) {
1921
+ throw new Error(`approved removal is no longer a direct managed entry: ${approved.path}`)
1922
+ }
1923
+ const unreadable = currentPlan.notes.some(
1924
+ (note) => note.level === 'warn' && note.text.startsWith(`${path} `) && note.text.includes('left untouched'),
1925
+ )
1926
+ if (unreadable) throw new Error(`approved target can no longer be read safely: ${approved.path}`)
1927
+ alreadyClean.push(approved.id)
1928
+ continue
1929
+ }
1930
+ if (
1931
+ current.kind !== approved.kind ||
1932
+ current.path !== approved.path ||
1933
+ current.beforeSha256 !== approved.beforeSha256 ||
1934
+ !sameRemovals(current.removals, approved.removals)
1935
+ ) {
1936
+ throw new Error(`approved target drifted after review: ${approved.path}`)
1937
+ }
1938
+ selected.push(current)
1939
+ }
1940
+
1941
+ const unapproved = currentPlan.operations
1942
+ .filter((operation) => !approvedIds.has(operation.id))
1943
+ .map(publicOperation)
1944
+ if (selected.length === 0) return { applied: [], alreadyClean, unapproved, backupRoot: null }
1945
+
1946
+ const stamp = now.replaceAll(/[^0-9A-Za-z_-]/g, '-')
1947
+ const backupParent = join(stateDir, 'global-surface-sweep', 'backups')
1948
+ for (const operation of selected) {
1949
+ const target = resolve(operation.absolutePath)
1950
+ if (stateDir === target || stateDir.startsWith(`${target}${sep}`)) {
1951
+ throw new Error(`state directory cannot be inside a removal target: ${operation.path}`)
1952
+ }
1953
+ }
1954
+ mkdirSync(backupParent, { recursive: true, mode: 0o700 })
1955
+ const backupRoot = mkdtempSync(join(backupParent, `${stamp}-`))
1956
+ chmodSync(backupRoot, 0o700)
1957
+
1958
+ const applied: { readonly operation: SweepOperation; readonly backup: string }[] = []
1959
+ try {
1960
+ for (const operation of selected) {
1961
+ if (!pathExists(operation.absolutePath) || fingerprintPath(operation.absolutePath) !== operation.beforeSha256) {
1962
+ throw new Error(`approved target drifted immediately before apply: ${operation.path}`)
1963
+ }
1964
+ const backup = backupPath(backupRoot, operation.path)
1965
+ mkdirSync(dirname(backup), { recursive: true, mode: 0o700 })
1966
+ if (operation.kind === 'write-file') {
1967
+ copyFileSync(operation.absolutePath, backup)
1968
+ chmodSync(backup, operation.mode)
1969
+ const backupStat = lstatSync(backup)
1970
+ if (backupStat.uid !== operation.uid || backupStat.gid !== operation.gid) {
1971
+ chownSync(backup, operation.uid, operation.gid)
1972
+ }
1973
+ atomicWrite(operation.absolutePath, operation.content, operation.mode, operation.uid, operation.gid)
1974
+ } else {
1975
+ bootOutLaunchAgentIfPresent(operation.path)
1976
+ renameSync(operation.absolutePath, backup)
1977
+ }
1978
+ applied.push({ operation, backup })
1979
+ }
1980
+
1981
+ atomicWrite(
1982
+ join(backupRoot, 'applied-plan.json'),
1983
+ `${JSON.stringify(
1984
+ {
1985
+ schema: 1,
1986
+ appliedAt: now,
1987
+ home,
1988
+ operations: applied.map(({ operation }) => publicOperation(operation)),
1989
+ },
1990
+ null,
1991
+ 2,
1992
+ )}\n`,
1993
+ 0o600,
1994
+ )
1995
+ } catch (cause) {
1996
+ for (const { operation, backup } of [...applied].reverse()) {
1997
+ if (operation.kind === 'write-file') {
1998
+ atomicWrite(
1999
+ operation.absolutePath,
2000
+ readFileSync(backup, 'utf8'),
2001
+ operation.mode,
2002
+ operation.uid,
2003
+ operation.gid,
2004
+ )
2005
+ } else {
2006
+ mkdirSync(dirname(operation.absolutePath), { recursive: true })
2007
+ renameSync(backup, operation.absolutePath)
2008
+ }
2009
+ }
2010
+ throw cause
2011
+ }
2012
+
2013
+ return {
2014
+ applied: applied.map(({ operation }) => publicOperation(operation)),
2015
+ alreadyClean,
2016
+ unapproved,
2017
+ backupRoot,
2018
+ }
2019
+ }
2020
+
2021
+ export function enforceGlobalSweep(
2022
+ homeInput: string,
2023
+ stateDirInput: string,
2024
+ now = new Date().toISOString(),
2025
+ protection: ProtectionList = loadProtectionList(),
2026
+ ): EnforceResult {
2027
+ if (!isAbsolute(homeInput) || !isAbsolute(stateDirInput)) {
2028
+ throw new Error('--home and --state-dir must be absolute')
2029
+ }
2030
+ const home = resolve(homeInput)
2031
+ const plan = planGlobalSweep(home, protection)
2032
+ const result = applyApprovedSweep(
2033
+ home,
2034
+ {
2035
+ schema: 1,
2036
+ home,
2037
+ generatedAt: now,
2038
+ operations: plan.operations.filter((operation) => !operation.requiresApproval).map(publicOperation),
2039
+ },
2040
+ resolve(stateDirInput),
2041
+ now,
2042
+ protection,
2043
+ )
2044
+ const pendingApproval = result.unapproved.filter((operation) => operationRequiresApproval(operation.path))
2045
+ return { ...result, protectedItems: plan.protected.length, pendingApproval }
2046
+ }
2047
+
2048
+ export function describeRemoval(removal: RemovalItem): string {
2049
+ if (removal.kind === 'json-entry') {
2050
+ return `JSON ${removal.target}#${removal.selector} (${removal.beforeSha256})`
2051
+ }
2052
+ if (removal.kind === 'toml-table') {
2053
+ return `TOML ${removal.target} ${removal.header} (${removal.beforeSha256})`
2054
+ }
2055
+ if (removal.kind === 'toml-key') {
2056
+ return `TOML-KEY ${removal.target} ${removal.key} (${removal.beforeSha256})`
2057
+ }
2058
+ if (removal.kind === 'text-block') {
2059
+ return `BLOCK ${removal.target} «${removal.name}» (${removal.beforeSha256})`
2060
+ }
2061
+ return `PATH ${removal.target} (${removal.beforeSha256})`
2062
+ }
2063
+
2064
+ function usage(): never {
2065
+ throw new Error(
2066
+ 'usage: global-surface-sweep --home ABSOLUTE_PATH --mode audit|plan|copy-review|apply|enforce ' +
2067
+ '[--protection FILE --destination ABSOLUTE_PATH --approval PLAN.json --state-dir ABSOLUTE_PATH]',
2068
+ )
2069
+ }
2070
+
2071
+ export function main(argv: readonly string[]): void {
2072
+ let home = ''
2073
+ let mode = 'audit'
2074
+ let approvalPath = ''
2075
+ let stateDir = '/var/lib/adl'
2076
+ let protectionPath = DEFAULT_PROTECTION_PATH
2077
+ let destination = ''
2078
+
2079
+ for (let index = 0; index < argv.length; index += 1) {
2080
+ const argument = argv[index]
2081
+ if (argument === '--home') home = argv[++index] ?? ''
2082
+ else if (argument === '--mode') mode = argv[++index] ?? ''
2083
+ else if (argument === '--approval') approvalPath = argv[++index] ?? ''
2084
+ else if (argument === '--state-dir') stateDir = argv[++index] ?? ''
2085
+ else if (argument === '--protection') protectionPath = argv[++index] ?? ''
2086
+ else if (argument === '--destination') destination = argv[++index] ?? ''
2087
+ else usage()
2088
+ }
2089
+ if (home.length === 0 || !['audit', 'plan', 'copy-review', 'apply', 'enforce'].includes(mode)) usage()
2090
+ const protection = loadProtectionList(protectionPath)
2091
+
2092
+ if (mode === 'plan') {
2093
+ console.log(JSON.stringify(createApprovalPlan(home, new Date().toISOString(), protection), null, 2))
2094
+ return
2095
+ }
2096
+
2097
+ if (mode === 'copy-review') {
2098
+ if (destination.length === 0) throw new Error('copy-review mode requires --destination')
2099
+ const result = copyReviewBundle(home, destination, protection)
2100
+ console.log(`review copy: ${result.destination}`)
2101
+ console.log(`copied path candidates: ${result.copiedPaths}`)
2102
+ console.log(`copied configuration originals: ${result.copiedConfigFiles}`)
2103
+ console.log(`candidate items: ${result.candidateItems}`)
2104
+ console.log(`protected items not copied as candidates: ${result.protectedItems}`)
2105
+ console.log('global-surface-sweep: review copy complete; no live files changed')
2106
+ return
2107
+ }
2108
+
2109
+ if (mode === 'enforce') {
2110
+ const result = enforceGlobalSweep(home, stateDir, new Date().toISOString(), protection)
2111
+ for (const operation of result.applied) {
2112
+ console.log(`applied: ${operation.id}`)
2113
+ for (const removal of operation.removals) console.log(` removed: ${describeRemoval(removal)}`)
2114
+ }
2115
+ for (const operation of result.pendingApproval) {
2116
+ console.log(`pending approval: ${operation.id}`)
2117
+ for (const removal of operation.removals) console.log(` retained until approved: ${describeRemoval(removal)}`)
2118
+ }
2119
+ printBackupsThisRun(home, result.applied, result.backupRoot)
2120
+ console.log(
2121
+ `global-surface-sweep: enforced ${result.applied.length} operation(s); ` +
2122
+ `${result.protectedItems} exact protected item(s) retained; ` +
2123
+ `${result.pendingApproval.length} operation(s) pending approval`,
2124
+ )
2125
+ if (result.pendingApproval.length > 0) {
2126
+ console.error(
2127
+ `global-surface-sweep: WARNING ${result.pendingApproval.length} removal operation(s) pending explicit approval ` +
2128
+ '(generate with --mode plan, review, then --mode apply --approval PLAN.json)',
2129
+ )
2130
+ }
2131
+ return
2132
+ }
2133
+
2134
+ const plan = planGlobalSweep(home, protection)
2135
+ if (mode === 'audit') {
2136
+ for (const note of plan.notes) console.log(`${note.level}: ${note.text}`)
2137
+ for (const item of plan.protected) {
2138
+ console.log(`protected: ${describeRemoval(item.removal)} — ${item.reason}`)
2139
+ }
2140
+ for (const operation of plan.operations) {
2141
+ const suffix = operation.requiresApproval ? ' [requires explicit approval]' : ''
2142
+ for (const removal of operation.removals) console.log(`remove: ${describeRemoval(removal)}${suffix}`)
2143
+ }
2144
+ // Audit is report-only: it never writes or moves a file, so there is
2145
+ // never a backup root to point at (Tarun, 2026-09-28).
2146
+ printBackupsThisRun(home, [], null)
2147
+ console.log(
2148
+ `global-surface-sweep: ${plan.operations.length} candidate(s), ` +
2149
+ `${plan.protected.length} exact protected item(s); no changes applied`,
2150
+ )
2151
+ return
2152
+ }
2153
+
2154
+ if (approvalPath.length === 0) {
2155
+ throw new Error('apply mode requires --approval; discovery alone never authorizes deletion')
2156
+ }
2157
+ const approval = decodeApprovalPlan(JSON.parse(readFileSync(approvalPath, 'utf8')))
2158
+ const result = applyApprovedSweep(home, approval, stateDir, new Date().toISOString(), protection)
2159
+ for (const operation of result.applied) {
2160
+ console.log(`applied: ${operation.id}`)
2161
+ for (const removal of operation.removals) console.log(` removed: ${describeRemoval(removal)}`)
2162
+ }
2163
+ for (const id of result.alreadyClean) console.log(`already clean: ${id}`)
2164
+ for (const operation of result.unapproved) {
2165
+ console.log(`left unapproved: ${operation.id}`)
2166
+ for (const removal of operation.removals) console.log(` retained: ${describeRemoval(removal)}`)
2167
+ }
2168
+ printBackupsThisRun(home, result.applied, result.backupRoot)
2169
+ console.log('global-surface-sweep: apply complete')
2170
+ }
2171
+
2172
+ if (import.meta.main) {
2173
+ try {
2174
+ main(process.argv.slice(2))
2175
+ } catch (cause) {
2176
+ console.error(`global-surface-sweep: ${messageOf(cause)}`)
2177
+ process.exitCode = 1
2178
+ }
2179
+ }