@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,1083 @@
1
+ /**
2
+ * Classifying a token stream into an `Invocation`.
3
+ *
4
+ * Ported from `.agents/hooks/pre-tool-use/src/walk.mjs`. The old tree spread
5
+ * this classification across `walk.mjs`, three Sets in `constants.mjs`, and a
6
+ * prose `reason` string in `blocked-reason.mjs` that `deny.mjs` then
7
+ * regex-reparsed into a message kind. Here `walk` only classifies — it
8
+ * returns a structured `Invocation`, never a reason — and every rule's own
9
+ * `match()` (src/rules/impl/*.ts) decides whether that invocation is denied.
10
+ *
11
+ * `NON_CANONICAL_TEST_RECIPES` (constants.mjs:45) is deliberately not
12
+ * ported: the Set was empty, so `walkJustCommand`'s recipe branch
13
+ * (walk.mjs:144-151) was unreachable dead code exercised by no corpus string.
14
+ * Its config equivalent is `[rules."tool.noncanonical-test"] enabled = false`
15
+ * — there is no rule registered under that id at all, since one guarded by
16
+ * an always-empty Set is not a rule, it is a config toggle with nothing
17
+ * behind it.
18
+ */
19
+
20
+ import { basename, dynamicToken, isTrustedRepoScript, type Token, tokenValue } from './token.ts'
21
+ import { scanGitGlobals, skipPrefixes } from './prefixes.ts'
22
+ import {
23
+ gitInternalsWriteTargets,
24
+ unresolvableOperandWriteTargets,
25
+ unresolvableRedirectionWriteTargets,
26
+ withoutRedirections,
27
+ } from './git-internals.ts'
28
+ import { protectedCarrierWriteTargets } from './protected-carrier-writes.ts'
29
+ import { type HereDocument, hereDocumentPlaceholder, hereDocumentPlaceholderIndex, nestedSources, scanHereDocuments } from './substitution.ts'
30
+ import { type PieceFeed, splitShell } from './split.ts'
31
+ import { tokenize } from './token.ts'
32
+ import { uncertain, xargsInvocation } from './xargs.ts'
33
+ import { EXPRESSION_RUNNERS, stageFetcher, staticProducerOutput, wordFetcher } from './piped-script.ts'
34
+
35
+ export type Invocation =
36
+ | {
37
+ readonly kind: 'git'
38
+ readonly subcommand: string
39
+ readonly globals: readonly Token[]
40
+ readonly rest: readonly Token[]
41
+ /**
42
+ * The raw command word that invoked git — `git`/`git.exe` when run by
43
+ * bare name (the common case, and the only one `git-worktree`'s
44
+ * synthetic shortcut below ever produces), or the exact path when run
45
+ * by one (`/usr/local/bin/git`, `/usr/bin/git`, …). H2
46
+ * (`adl-git-credential-bypass.ts`) denies an ABSOLUTE path other than
47
+ * the managed front controller `/usr/local/bin/git` — an agent
48
+ * invoking git by any other absolute path bypasses `bin/git-ado`
49
+ * entirely, since that routing lives at exactly that one path. Absent
50
+ * on the `git-worktree` binary shortcut (no real "binary word" to
51
+ * report there).
52
+ */
53
+ readonly binary?: string
54
+ }
55
+ | { readonly kind: 'rm'; readonly args: readonly Token[] }
56
+ | { readonly kind: 'trash'; readonly args: readonly Token[] }
57
+ | { readonly kind: 'mv'; readonly args: readonly Token[] }
58
+ | { readonly kind: 'hook-disarm' }
59
+ /**
60
+ * A command that writes into a `.git` directory or gitdir file — delete,
61
+ * trash, move, copy/link/sync over, truncate, redirect into, tee, in-place
62
+ * edit, chmod/chown, or find -delete. `targets` are the offending paths.
63
+ * See git-internals.ts; denied by fs.git-internals.
64
+ */
65
+ | { readonly kind: 'git-internals-write'; readonly name: string; readonly targets: readonly string[] }
66
+ /**
67
+ * A command that writes into one of the seven tamper-protected carrier
68
+ * files (`.claude/settings.json`, `.codex/hooks.json`, `adl-config.toml`,
69
+ * …) via redirection, tee, mv/cp/install/ln, sed -i/perl -pi, truncate,
70
+ * trash/rm/unlink/shred, or dd. See shell/protected-carrier-writes.ts;
71
+ * denied by agent.tamper-protection. `git rm`/`checkout --`/`restore --`/
72
+ * `reset --` on one of these files is a `'git'` Invocation instead — see
73
+ * that rule's own match().
74
+ */
75
+ | { readonly kind: 'protected-carrier-write'; readonly name: string; readonly targets: readonly string[] }
76
+ | { readonly kind: 'cargo' }
77
+ /**
78
+ * `az devops|repos|pipelines|boards|artifacts ...`. `args` is everything
79
+ * after `az` itself (the subcommand group onward, e.g. `['repos', 'pr',
80
+ * 'create', '--title', 'x']`) — carried so a rule can tell a mutating
81
+ * subcommand from a read one (`adl.session-required-for-writes`); denied
82
+ * outright regardless of that distinction by tool.azure-devops, which
83
+ * ignores this field. See impl/tool-azure-devops.ts.
84
+ */
85
+ | { readonly kind: 'azure-devops'; readonly args: readonly Token[] }
86
+ | { readonly kind: 'find' }
87
+ | { readonly kind: 'grep' }
88
+ | { readonly kind: 'frontend-tool'; readonly tool: string }
89
+ /**
90
+ * `bun test` (any arguments, including a file/pattern operand) — the
91
+ * `test` subcommand specifically, after skipping bun's own `-C <dir>` /
92
+ * `--cwd <dir>` / `--cwd=<dir>` global. Tarun's decision (2026-09-27,
93
+ * feature az372): ban `bun test` only — `bun run test`, `bun run
94
+ * <script>`, `bunx`, `bun install` and every other bun subcommand stay
95
+ * unclassified here (`null` from `walkToolCommand`) and fall through to
96
+ * the ordinary `command` kind, which no rule denies. See
97
+ * impl/tool-bun-test.ts.
98
+ */
99
+ | { readonly kind: 'bun-test' }
100
+ | { readonly kind: 'uncertain'; readonly reason: string }
101
+ /**
102
+ * A shell (or `.`/`source`/`eval`/PowerShell `iex`) executing content a
103
+ * network fetcher produced — piped into its stdin, or read through a
104
+ * `<(...)`/`$(...)`/backtick. See piped-script.ts; denied by
105
+ * shell.remote-script-exec.
106
+ */
107
+ | { readonly kind: 'remote-script-exec'; readonly fetcher: string }
108
+ /**
109
+ * A command the hook never denies, but that advisory.finder wants to see:
110
+ * `grep`/`egrep`/`fgrep`/`rgrep`/`ack`/`ag` (constants.mjs's
111
+ * `SUBSTITUTABLE_FINDERS`), plus every other read-only consumer and
112
+ * introspection command that used to just vanish as `[]`. Every rule but
113
+ * advisory.finder treats this exactly like an empty invocation list — see
114
+ * its `match()`, which is the only one that inspects `name`.
115
+ */
116
+ | { readonly kind: 'read-only'; readonly name: string }
117
+ /**
118
+ * A command whose arguments reach into a tree agents must not touch
119
+ * (`cfg.deniedTrees`). Emitted for ANY command, ahead of every other
120
+ * classification, because the alternative does not exist: `cat`/`ls` and the
121
+ * other read-only consumers return `[]` here and are invisible to rules, and
122
+ * `read-only` carries only a name. Without this, a path-based deny could see
123
+ * nothing to deny. See impl/fs-canonical-checkout.ts.
124
+ */
125
+ | { readonly kind: 'denied-path'; readonly name: string; readonly path: string }
126
+ /**
127
+ * An otherwise-unclassified top-level command. Emitted alongside whatever
128
+ * `walkNested` finds inside its arguments — every built-in rule but
129
+ * `tool.lean-ctx-global-state` ignores this kind's extra `prefix` field, so
130
+ * adding it changes no existing decision; the kind itself exists purely so
131
+ * `custom.ts`'s declarative `category: "tool"` rules (name-based tool
132
+ * bans) have something to match. See custom.ts's module doc.
133
+ */
134
+ | {
135
+ readonly kind: 'command'
136
+ readonly name: string
137
+ readonly args: readonly Token[]
138
+ /**
139
+ * The tokens `shell/prefixes.ts`'s `skipPrefixes` consumed to resolve
140
+ * `name` — every leading `NAME=value` assignment word, and every
141
+ * wrapper command's own name/flags (`sudo`/`env`/`timeout`/…), in
142
+ * their original order. Present so a rule can tell whether THIS
143
+ * specific invocation carries its own inline assignment (e.g.
144
+ * `tool.lean-ctx-global-state`'s per-invocation `LEAN_CTX_CONFIG_DIR=`
145
+ * check) without re-deriving it from the whole raw command text — a
146
+ * `NAME=value` immediately before the command name, or one consumed as
147
+ * an `env NAME=value cmd` operand (`skipEnvFlags`'s own
148
+ * "value-taking flag" branch), both land here. Empty when nothing was
149
+ * skipped. Optional only so hand-built test fixtures that predate this
150
+ * field keep compiling.
151
+ */
152
+ readonly prefix?: readonly Token[]
153
+ }
154
+ /**
155
+ * A non-shell tool call — Read, Write, Edit, Grep, Glob, an MCP tool, and
156
+ * so on. The original's `decide-render.mjs:25` gated on `SHELL_TOOLS`, so
157
+ * these never reached the engine at all. Opening that gate (default OFF;
158
+ * see engine.ts) lets a `[[rules.custom]]` entry with `category:
159
+ * "tool-call"` deny a call by its tool name and input shape — e.g. an Edit
160
+ * whose `file_path` sits inside a protected tree, catching the edit
161
+ * instead of only the commit.
162
+ */
163
+ | { readonly kind: 'tool-call'; readonly tool: string; readonly input: Readonly<Record<string, unknown>> }
164
+
165
+ /** The name-vocabulary walk() needs to classify a command. Defaults mirror constants.mjs exactly. */
166
+ export interface ClassifyConfig {
167
+ readonly shellNames: ReadonlySet<string>
168
+ readonly shellExecutors: ReadonlySet<string>
169
+ readonly introspectionNames: ReadonlySet<string>
170
+ readonly subcommandTools: ReadonlySet<string>
171
+ readonly readOnlyConsumers: ReadonlySet<string>
172
+ readonly rmNames: ReadonlySet<string>
173
+ readonly trashNames: ReadonlySet<string>
174
+ readonly azureDevopsNames: ReadonlySet<string>
175
+ readonly azureDevopsSubcommands: ReadonlySet<string>
176
+ readonly cargoNames: ReadonlySet<string>
177
+ readonly justNames: ReadonlySet<string>
178
+ readonly frontendToolNames: ReadonlySet<string>
179
+ readonly bunNames: ReadonlySet<string>
180
+ readonly trustedScriptPrefixes: readonly string[]
181
+ /** `SUBSTITUTABLE_FINDERS` (constants.mjs:123) — `find` is deliberately absent; see tool-find.ts. */
182
+ readonly substitutableFinders: ReadonlySet<string>
183
+ /**
184
+ * Absolute trees no command may reference. Empty by default, so classification
185
+ * is unchanged unless an engine supplies one (engine.ts's classifyConfigFor).
186
+ */
187
+ readonly deniedTrees: readonly string[]
188
+ }
189
+
190
+ export const DEFAULT_CLASSIFY_CONFIG: ClassifyConfig = {
191
+ deniedTrees: [],
192
+ shellNames: new Set(['sh', 'bash', 'zsh', 'dash', 'ksh', 'fish']),
193
+ shellExecutors: new Set(['.', 'eval', 'source']),
194
+ introspectionNames: new Set(['echo', 'printf', 'which', 'type', 'whereis', 'whatis', 'man', 'apropos', 'info']),
195
+ subcommandTools: new Set(['docker', 'podman', 'nerdctl', 'kubectl', 'helm', 'buildah', 'crictl']),
196
+ readOnlyConsumers: new Set([
197
+ 'rg',
198
+ 'rg.exe',
199
+ 'cat',
200
+ 'cat.exe',
201
+ 'sed',
202
+ 'sed.exe',
203
+ 'head',
204
+ 'head.exe',
205
+ 'tail',
206
+ 'tail.exe',
207
+ 'less',
208
+ 'less.exe',
209
+ 'more',
210
+ 'more.exe',
211
+ 'awk',
212
+ 'awk.exe',
213
+ 'grep',
214
+ 'grep.exe',
215
+ ]),
216
+ rmNames: new Set(['rm', 'rm.exe', 'unlink', 'unlink.exe']),
217
+ trashNames: new Set(['trash', 'trash.exe']),
218
+ azureDevopsNames: new Set(['az', 'az.exe']),
219
+ azureDevopsSubcommands: new Set(['devops', 'repos', 'pipelines', 'boards', 'artifacts']),
220
+ cargoNames: new Set(['cargo', 'cargo.exe']),
221
+ justNames: new Set(['just', 'just.exe']),
222
+ frontendToolNames: new Set(['npx', 'npx.exe', 'vitest', 'vitest.exe', 'jest', 'jest.exe', 'biome', 'biome.exe', 'tsc', 'tsc.exe']),
223
+ bunNames: new Set(['bun', 'bun.exe']),
224
+ trustedScriptPrefixes: ['workflows/scripts/', '.code-quality/', '.agents/hooks/'],
225
+ substitutableFinders: new Set(['grep', 'grep.exe', 'egrep', 'fgrep', 'rgrep', 'ack', 'ag']),
226
+ }
227
+
228
+ function isClassifiedCommandName(token: Token, cfg: ClassifyConfig): boolean {
229
+ const name = basename(token)
230
+ return (
231
+ cfg.rmNames.has(name) ||
232
+ cfg.trashNames.has(name) ||
233
+ cfg.azureDevopsNames.has(name) ||
234
+ cfg.cargoNames.has(name) ||
235
+ cfg.justNames.has(name) ||
236
+ cfg.frontendToolNames.has(name) ||
237
+ cfg.bunNames.has(name) ||
238
+ name === 'git' ||
239
+ name === 'git.exe' ||
240
+ name === 'git-worktree' ||
241
+ name === 'git-worktree.exe' ||
242
+ name === 'busybox'
243
+ )
244
+ }
245
+
246
+ export function rmIsHelpOnly(args: readonly Token[]): boolean {
247
+ const flags = new Set<string>()
248
+ for (const token of args) {
249
+ const arg = tokenValue(token)
250
+ if (arg === '--') break
251
+ if (arg === '--help' || arg === '--version') flags.add('help')
252
+ if (arg === '-h' && args.length === 1) flags.add('help')
253
+ }
254
+ return flags.has('help') && args.every((token) => tokenValue(token).startsWith('-') && tokenValue(token) !== '--')
255
+ }
256
+
257
+ export function isHelpOrVersionOnly(args: readonly Token[]): boolean {
258
+ let sawHelp = false
259
+ for (const token of args) {
260
+ const arg = tokenValue(token)
261
+ if (arg === '--') return false
262
+ if (!arg.startsWith('-')) return false
263
+ if (arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V') {
264
+ sawHelp = true
265
+ continue
266
+ }
267
+ return false
268
+ }
269
+ return sawHelp
270
+ }
271
+
272
+ function isShellCommandOption(value: string): boolean {
273
+ return value === '-c' || /^-[^-]*c$/.test(value) || value === '--command' || value.startsWith('--command=')
274
+ }
275
+
276
+ function isShellNoExecFlag(token: Token): boolean {
277
+ return /^-[a-zA-Z]*n[a-zA-Z]*$/.test(tokenValue(token))
278
+ }
279
+
280
+ const LEADING_ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/
281
+
282
+ /**
283
+ * Whether `tokens` begins (after any leading `NAME=value` assignments) with
284
+ * an `env`/`env.exe` invocation that carries `-S`/`--split-string` among its
285
+ * own flags — R3 (STATUS.md 12p). Checked at `walk()`'s
286
+ * own call site BEFORE `skipPrefixes` (../prefixes.ts's `skipEnvFlags`)
287
+ * consumes `-S`/`--split-string` and its value as an opaque, value-taking
288
+ * flag argument; this mirrors that same flag-scanning loop just far enough
289
+ * to detect the flag's PRESENCE, without trying to parse or classify what
290
+ * its value names. Narrow by design: only a leading `env`, not `env` nested
291
+ * behind another wrapper (`sudo env -S …`) — the common, and only currently
292
+ * observed, shape.
293
+ */
294
+ function envUsesSplitString(tokens: readonly Token[]): boolean {
295
+ let index = 0
296
+ while (index < tokens.length && LEADING_ASSIGNMENT_RE.test(tokenValue(tokens[index] as Token))) index += 1
297
+ if (index >= tokens.length) return false
298
+ const name = basename(tokens[index] as Token)
299
+ if (name !== 'env' && name !== 'env.exe') return false
300
+ index += 1
301
+ while (index < tokens.length) {
302
+ const value = tokenValue(tokens[index] as Token)
303
+ if (value === '--') return false
304
+ if (value === '-S' || value === '--split-string') return true
305
+ if (value.startsWith('--split-string=')) return true
306
+ if (value.startsWith('--unset=') || value.startsWith('--chdir=') || value.startsWith('-') || value.includes('=')) {
307
+ index += ['-u', '--unset', '-C', '--chdir'].includes(value) ? 2 : 1
308
+ continue
309
+ }
310
+ break
311
+ }
312
+ return false
313
+ }
314
+
315
+ function walkShellArgument(
316
+ args: readonly Token[],
317
+ look: number,
318
+ cfg: ClassifyConfig,
319
+ parseScript: (script: string, feed?: StdinFeed) => readonly Invocation[],
320
+ feed: StdinFeed | undefined,
321
+ ): readonly Invocation[] | null {
322
+ const arg = args[look] as Token
323
+ const value = tokenValue(arg)
324
+ if (value === '--') return null
325
+ if (value === '-c' || /^-[^-]*c$/.test(value) || value === '--command') {
326
+ const next = args[look + 1]
327
+ return next === undefined ? uncertain('dynamic shell script') : parseScript(tokenValue(next), feed)
328
+ }
329
+ if (value.startsWith('--command=')) {
330
+ const script = value.slice('--command='.length)
331
+ return script ? parseScript(script, feed) : uncertain('dynamic shell script')
332
+ }
333
+ if (!value.startsWith('-')) {
334
+ return isTrustedRepoScript(arg, cfg.trustedScriptPrefixes) ? [] : uncertain('shell script execution')
335
+ }
336
+ return null
337
+ }
338
+
339
+ /** `-n` (read but do not execute) without a `-c` script. */
340
+ const shellRunsNothing = (args: readonly Token[]): boolean =>
341
+ !args.some((token) => isShellCommandOption(tokenValue(token))) && args.some(isShellNoExecFlag)
342
+
343
+ /** Shell options that consume the following word. */
344
+ const SHELL_OPTIONS_WITH_VALUE = new Set(['-o', '+o', '-O', '+O', '--rcfile', '--init-file'])
345
+
346
+ /**
347
+ * Whether a shell invoked with `args` (redirections removed) reads its script
348
+ * from stdin: no `-c` script and no script operand, or `-s`/`-` given.
349
+ */
350
+ function shellScriptFromStdin(args: readonly Token[]): boolean {
351
+ let fromStdin = false
352
+ for (let look = 0; look < args.length; look += 1) {
353
+ const value = tokenValue(args[look] as Token)
354
+ if (isShellCommandOption(value)) return false
355
+ if (value === '-') return true
356
+ if (value === '--') return fromStdin || look + 1 >= args.length
357
+ if (SHELL_OPTIONS_WITH_VALUE.has(value)) {
358
+ look += 1
359
+ continue
360
+ }
361
+ if (/^[-+][a-zA-Z]+$/.test(value)) {
362
+ if (value.startsWith('-') && value.includes('s')) fromStdin = true
363
+ continue
364
+ }
365
+ if (value.startsWith('--')) continue
366
+ return fromStdin
367
+ }
368
+ return true
369
+ }
370
+
371
+ /**
372
+ * Whether `xargs ... <shell> ...` builds its shell's SCRIPT from xargs input:
373
+ * no `-c` (input items become script operands), a `-c` with no script, or a
374
+ * `-c` script containing the `-I`/`-J`/`--replace` string.
375
+ */
376
+ function xargsShellReadsInput(args: readonly Token[], cfg: ClassifyConfig): boolean {
377
+ let replace: string | null = null
378
+ for (let look = 0; look < args.length; look += 1) {
379
+ const token = args[look] as Token
380
+ const value = tokenValue(token)
381
+ if (cfg.shellNames.has(basename(token))) {
382
+ const rest = withoutRedirections(args.slice(look + 1))
383
+ const option = rest.findIndex((word) => isShellCommandOption(tokenValue(word)))
384
+ if (option < 0) return true
385
+ const optionValue = tokenValue(rest[option] as Token)
386
+ const scriptToken = rest[option + 1]
387
+ const script = optionValue.startsWith('--command=') ? optionValue.slice('--command='.length) : scriptToken && tokenValue(scriptToken)
388
+ if (script === undefined || script === '') return true
389
+ return replace !== null && script.includes(replace)
390
+ }
391
+ if (value === '-I' || value === '-J') {
392
+ replace = tokenValue(args[look + 1] ?? token)
393
+ look += 1
394
+ } else if (value === '-i' || value === '--replace') replace = '{}'
395
+ else if (value.startsWith('--replace=')) replace = value.slice('--replace='.length)
396
+ else if (/^-[IJi]./.test(value)) replace = value.slice(2)
397
+ }
398
+ return false
399
+ }
400
+
401
+ /** A remote fetch executed as code — plus a fail-closed uncertain, so disabling the rule never turns it into an allow. */
402
+ const remoteExec = (fetcher: string): readonly Invocation[] => [
403
+ { kind: 'remote-script-exec', fetcher },
404
+ ...uncertain('remote content executed by a shell'),
405
+ ]
406
+
407
+ /** A shell's stdin redirected from a file or descriptor (`bash < script.sh`, `sh <&3`) is a script nobody can see. */
408
+ function readsStdinFromFile(tokens: readonly Token[]): boolean {
409
+ return tokens.some((token, index) => {
410
+ const op = token.redirect
411
+ if (op !== '<' && op !== '<>' && op !== '<&') return false
412
+ if (!/^0?</.test(tokenValue(token))) return false
413
+ return !(op === '<&' && tokenValue(tokens[index + 1] ?? token) === '-')
414
+ })
415
+ }
416
+
417
+ function walkShell(
418
+ rawArgs: readonly Token[],
419
+ cfg: ClassifyConfig,
420
+ parseScript: (script: string, feed?: StdinFeed) => readonly Invocation[],
421
+ feed: StdinFeed | undefined,
422
+ ): readonly Invocation[] {
423
+ // Redirections are not script operands; here-document/here-string stdin is walked by `shellStdinInvocations`.
424
+ const args = withoutRedirections(rawArgs)
425
+ if (shellRunsNothing(args)) return []
426
+ for (let look = 0; look < args.length; look += 1) {
427
+ const result = walkShellArgument(args, look, cfg, parseScript, feed)
428
+ if (result !== null) return result
429
+ }
430
+ return readsStdinFromFile(rawArgs) ? uncertain('shell script execution') : []
431
+ }
432
+
433
+ function staticEnvironment(tokens: readonly Token[], gitIndex: number): ReadonlyMap<string, string> {
434
+ const environment = new Map<string, string>()
435
+ for (const token of tokens.slice(0, gitIndex)) {
436
+ const assignment = tokenValue(token)
437
+ const equals = assignment.indexOf('=')
438
+ if (equals >= 0) environment.set(assignment.slice(0, equals), assignment.slice(equals + 1))
439
+ }
440
+ return environment
441
+ }
442
+
443
+ function environmentGitAlias(environment: ReadonlyMap<string, string>, subcommand: string): string | null {
444
+ let count: number | null = null
445
+ const keys = new Map<number, string>()
446
+ const values = new Map<number, string>()
447
+ for (const [name, value] of environment) {
448
+ if (name === 'GIT_CONFIG_COUNT') count = /^\d+$/.test(value) ? Number(value) : null
449
+ const key = /^GIT_CONFIG_KEY_(\d+)$/.exec(name)
450
+ if (key) keys.set(Number(key[1]), value)
451
+ const configured = /^GIT_CONFIG_VALUE_(\d+)$/.exec(name)
452
+ if (configured) values.set(Number(configured[1]), value)
453
+ }
454
+ if (count === null || !Number.isSafeInteger(count)) return null
455
+ let target: string | null = null
456
+ for (const index of [...keys.keys()].filter((candidate) => candidate < count).sort((left, right) => left - right)) {
457
+ if (keys.get(index)?.toLowerCase() === `alias.${subcommand}` && values.has(index)) target = values.get(index) as string
458
+ }
459
+ return target
460
+ }
461
+
462
+ /** Resolve alias definitions statically visible in this command; repository/global configuration is not inspectable here. */
463
+ function configuredGitAlias(tokens: readonly Token[], gitIndex: number, globals: readonly Token[], subcommand: string): string | null {
464
+ const environment = staticEnvironment(tokens, gitIndex)
465
+ let target = environmentGitAlias(environment, subcommand)
466
+ for (let index = 0; index < globals.length; index += 1) {
467
+ const value = tokenValue(globals[index] as Token)
468
+ if (value === '--config-env' || value.startsWith('--config-env=')) {
469
+ const setting = value === '--config-env' ? tokenValue(globals[index += 1] as Token) : value.slice('--config-env='.length)
470
+ const equals = setting.indexOf('=')
471
+ if (equals >= 0 && setting.slice(0, equals).toLowerCase() === `alias.${subcommand}`) {
472
+ target = environment.get(setting.slice(equals + 1)) ?? null
473
+ }
474
+ continue
475
+ }
476
+ const setting = value === '-c' ? tokenValue(globals[index += 1] as Token) : value.startsWith('-c') ? value.slice(2) : ''
477
+ const equals = setting.indexOf('=')
478
+ if (equals < 0 || setting.slice(0, equals).toLowerCase() !== `alias.${subcommand}`) continue
479
+ target = setting.slice(equals + 1).trim()
480
+ }
481
+ return target
482
+ }
483
+
484
+ function classifyGitArgv(argv: readonly Token[], binary: string): readonly Invocation[] {
485
+ const scan = scanGitGlobals(argv, 0)
486
+ if (scan.terminal) return [{ kind: 'read-only', name: 'git' }]
487
+ const subIndex = scan.index
488
+ if (subIndex >= argv.length) return uncertain('git command missing subcommand')
489
+ const subToken = argv[subIndex] as Token
490
+ if (dynamicToken(subToken)) return uncertain('dynamic git subcommand')
491
+ return [
492
+ {
493
+ kind: 'git',
494
+ subcommand: tokenValue(subToken).toLowerCase(),
495
+ globals: argv.slice(0, subIndex),
496
+ rest: argv.slice(subIndex + 1),
497
+ binary,
498
+ },
499
+ ]
500
+ }
501
+
502
+ function walkGit(tokens: readonly Token[], index: number, parser: Parser): readonly Invocation[] {
503
+ const binary = tokenValue(tokens[index] as Token)
504
+ const argv = tokens.slice(index + 1)
505
+ const scan = scanGitGlobals(argv, 0)
506
+ if (scan.terminal) return [{ kind: 'read-only', name: 'git' }]
507
+ if (scan.index >= argv.length) return uncertain('git command missing subcommand')
508
+ const subToken = argv[scan.index] as Token
509
+ if (dynamicToken(subToken)) return uncertain('dynamic git subcommand')
510
+ const globals = argv.slice(0, scan.index)
511
+ const alias = configuredGitAlias(tokens, index, globals, tokenValue(subToken).toLowerCase())
512
+ if (alias === null) return classifyGitArgv(argv, binary)
513
+ if (alias.startsWith('!')) return parser.parseScript(alias.slice(1))
514
+ const expanded = tokenize(alias)
515
+ if (expanded.invalid) return uncertain('unparsed git alias')
516
+ return classifyGitArgv([...expanded.tokens, ...argv.slice(scan.index + 1)], binary)
517
+ }
518
+
519
+ function walkShellCommand(name: string, args: readonly Token[], cfg: ClassifyConfig, parser: Parser, feed: StdinFeed | undefined): readonly Invocation[] | null {
520
+ if (cfg.shellNames.has(name)) return walkShell(args, cfg, parser.parseScript, feed)
521
+ if (cfg.shellExecutors.has(name)) return uncertain(`dynamic ${name} execution`)
522
+ if (name === 'busybox') return args.length ? parser.walk(args, feed) : []
523
+ return null
524
+ }
525
+
526
+ const LEAN_CTX_COMMAND_NAMES: ReadonlySet<string> = new Set(['lean-ctx', 'lean-ctx.exe'])
527
+ /** `lean-ctx raw`'s documented alias, plus `exec` ("Same as -c" per `lean-ctx help all`). */
528
+ const LEAN_CTX_EXEC_WORDS: ReadonlySet<string> = new Set(['exec', 'raw', 'bypass'])
529
+ /**
530
+ * lean-ctx's own shell-hook re-exec placeholder (`exec lean-ctx -c
531
+ * "$BASH_EXECUTION_STRING"`, injected into `~/.bashenv` by `lean-ctx setup`
532
+ * — see `../impl/tool-lean-ctx-global.ts`'s module doc). Bash substitutes
533
+ * the REAL command text into this variable inside the interactive shell's
534
+ * OWN DEBUG-trap machinery — never inside a Bash-tool `command` string an
535
+ * agent hands this hook. Reaching here unresolved means lean-ctx execs an
536
+ * EMPTY command (the variable is unset in every context this static
537
+ * scanner actually observes), not an unknown one, so it is recognized as
538
+ * this ONE specific, known placeholder and left alone — rather than
539
+ * failing closed on "any `$...`", which `dynamicToken` would otherwise do
540
+ * for every other unresolvable value below.
541
+ */
542
+ const LEAN_CTX_SHELL_HOOK_PLACEHOLDER = '$BASH_EXECUTION_STRING'
543
+
544
+ /**
545
+ * lean-ctx's own arbitrary-command-execution surface — independent-review
546
+ * finding 1 (HIGH, pre-existing, affects every rule): `-c [--raw]`, `-t`,
547
+ * `exec`, `raw`/`bypass <command>` all hand a shell command to a REAL
548
+ * shell, exactly like `bash -c`'s own script argument, but nothing walked
549
+ * that argument before this fix — it was an opaque word inside an
550
+ * unclassified `{kind:'command', name:'lean-ctx', ...}`.
551
+ *
552
+ * Verified against the real `lean-ctx 3.10.5` binary: `lean-ctx -c echo a
553
+ * b` prints `a b` — every remaining word is joined with a single space
554
+ * into ONE command string (not only the first), so `rest.map(tokenValue).
555
+ * join(' ')` mirrors that exactly; a single already-quoted argument
556
+ * (`-c "npm install"`) round-trips unchanged through the same join.
557
+ *
558
+ * `lean-ctx shell` (an interactive compressed shell) is the READ side of
559
+ * the same risk when its stdin is fed by a pipe or a file redirection —
560
+ * the commands it will run are whatever that feed contains, invisible to
561
+ * this static scanner, so it fails closed as `uncertain` (matching how
562
+ * every other unreadable script source in this file is handled) rather
563
+ * than silently falling through to an unclassified allow. A bare `lean-ctx
564
+ * shell` with no stdin feed is left unclassified (out of scope for this
565
+ * finding — no probe or finding names that shape, and an interactive
566
+ * session an agent cannot feed input into is not the arbitrary-command
567
+ * risk this fix closes).
568
+ *
569
+ * Redirections between `lean-ctx` and its first argument (`lean-ctx
570
+ * >/dev/null -c "cmd"`) are stripped here the same way `walkShell` strips
571
+ * them for a real shell's own `-c` — `rawArgs` (`args` in `classify()`)
572
+ * still carries them; the generic `{kind:'command', ...}` fallback and
573
+ * `tool.lean-ctx-global-state`'s own `firstPositional` scan handle THAT
574
+ * redirection-before-subcommand case for the subcommand forms this
575
+ * function does not otherwise touch.
576
+ */
577
+ function walkLeanCtxExec(
578
+ name: string,
579
+ rawArgs: readonly Token[],
580
+ rawTokens: readonly Token[],
581
+ parser: Parser,
582
+ feed: StdinFeed | undefined,
583
+ ): readonly Invocation[] | null {
584
+ if (!LEAN_CTX_COMMAND_NAMES.has(name)) return null
585
+ const args = withoutRedirections(rawArgs)
586
+ if (args.length === 0) return null
587
+ const first = tokenValue(args[0] as Token)
588
+ const firstLower = first.toLowerCase()
589
+
590
+ if (firstLower === 'shell') {
591
+ return feed !== undefined || readsStdinFromFile(rawTokens) ? uncertain('lean-ctx shell fed from stdin') : null
592
+ }
593
+
594
+ let rest: readonly Token[] | null = null
595
+ if (first === '-c') {
596
+ const second = args[1]
597
+ rest = second !== undefined && tokenValue(second) === '--raw' ? args.slice(2) : args.slice(1)
598
+ } else if (first === '-t') {
599
+ rest = args.slice(1)
600
+ } else if (LEAN_CTX_EXEC_WORDS.has(firstLower)) {
601
+ rest = args.slice(1)
602
+ }
603
+ if (rest === null) return null
604
+ if (rest.length === 0) return uncertain('dynamic lean-ctx script')
605
+ if (rest.length === 1 && tokenValue(rest[0] as Token) === LEAN_CTX_SHELL_HOOK_PLACEHOLDER) return []
606
+ const joined = rest.map(tokenValue).join(' ')
607
+ return parser.parseScript(joined, feed)
608
+ }
609
+
610
+ const isTrashDestination = (token: Token): boolean => {
611
+ const value = tokenValue(token).replace(/\/+$/, '')
612
+ return value.endsWith('/.Trash') || value === '~/.Trash' || value.endsWith('/Trash')
613
+ }
614
+ const movesIntoTrash = (args: readonly Token[]): boolean => args.some((token) => isTrashDestination(token))
615
+
616
+ function walkFileCommand(name: string, args: readonly Token[], cfg: ClassifyConfig): readonly Invocation[] | null {
617
+ if (cfg.rmNames.has(name)) return [{ kind: 'rm', args }]
618
+ if (cfg.trashNames.has(name)) return [{ kind: 'trash', args }]
619
+ if (name === 'mv' && movesIntoTrash(args)) {
620
+ return [{ kind: 'trash', args: args.filter((token) => !isTrashDestination(token)) }]
621
+ }
622
+ if (name === 'mv') return [{ kind: 'mv', args }]
623
+ if (name !== 'chmod' && name !== 'chmod.exe') return null
624
+ const disarms = args.some((token) => {
625
+ const value = tokenValue(token)
626
+ return /(^|\/)workflows\/hooks(\/|$)/.test(value) || /(^|\/)\.git\/hooks(\/|$)/.test(value)
627
+ })
628
+ return disarms ? [{ kind: 'hook-disarm' }] : null
629
+ }
630
+
631
+ function walkJustCommand(args: readonly Token[]): readonly Invocation[] | null {
632
+ // `just _cargo ...` shells out straight to cargo (walk.mjs:145) and is kept.
633
+ // The recipe-name branch that followed it (walk.mjs:146-150) is not ported
634
+ // — see the module comment for why.
635
+ if (args.some((token) => tokenValue(token) === '_cargo')) return [{ kind: 'cargo' }]
636
+ return null
637
+ }
638
+
639
+ /** bun's own global flags that take a directory value ahead of the subcommand — `bun -C <dir> test`, `bun --cwd <dir> test`. */
640
+ const BUN_CWD_FLAGS = new Set(['-C', '--cwd'])
641
+
642
+ /**
643
+ * Whether `bun <args>` invokes the `test` subcommand, after skipping bun's
644
+ * own `-C <dir>` / `--cwd <dir>` / `--cwd=<dir>` global (bun accepts that
645
+ * ahead of the subcommand to run in a different directory). Any operand
646
+ * after `test` — a file, a pattern, `--coverage`, `--help` — leaves this
647
+ * true: per Tarun's decision (2026-09-27, feature az372) `bun test` is
648
+ * denied with ANY arguments, unlike cargo/frontend-tool which carve out
649
+ * `--help`/`--version`.
650
+ */
651
+ function isBunTestSubcommand(args: readonly Token[]): boolean {
652
+ let index = 0
653
+ while (index < args.length) {
654
+ const value = tokenValue(args[index] as Token)
655
+ if (BUN_CWD_FLAGS.has(value)) {
656
+ index += 2
657
+ continue
658
+ }
659
+ if (value.startsWith('--cwd=')) {
660
+ index += 1
661
+ continue
662
+ }
663
+ break
664
+ }
665
+ const subcommand = args[index]
666
+ return subcommand !== undefined && tokenValue(subcommand) === 'test'
667
+ }
668
+
669
+ /**
670
+ * The real Azure CLI global parameters that can appear before ANY
671
+ * subcommand group — https://learn.microsoft.com/cli/azure/reference-index
672
+ * (checked 2026-09-28) — M6 (STATUS.md 12p):
673
+ * without skipping these, `az --output json devops project list` read
674
+ * `args[0]` as `--output`, not `devops`, never matched
675
+ * `azureDevopsSubcommands`, and fell through to the generic `'command'` kind
676
+ * — invisible to `tool.azure-devops`'s outright deny. `--help`/`-h`/
677
+ * `--version`/`-v` are deliberately NOT skipped here: `isHelpOrVersionOnly`
678
+ * already gates a purely-help invocation before this runs, and one
679
+ * appearing BEFORE the subcommand group is real az's own "show generic
680
+ * help, run nothing" shape — not worth threading through this scan too.
681
+ */
682
+ const AZ_GLOBAL_VALUE_FLAGS: ReadonlySet<string> = new Set(['--output', '-o', '--query', '--subscription'])
683
+ const AZ_GLOBAL_BOOLEAN_FLAGS: ReadonlySet<string> = new Set(['--debug', '--only-show-errors', '--verbose'])
684
+
685
+ /**
686
+ * Index of the first word in `args` that is not a recognized `az` global
687
+ * flag (or its value) — see `AZ_GLOBAL_VALUE_FLAGS`/`AZ_GLOBAL_BOOLEAN_FLAGS`.
688
+ * `--flag=value` is a single word and is skipped as one. Exported (not just
689
+ * used internally here) so `impl/tool-azure-devops.ts`'s own separate
690
+ * `account get-access-token`/`rest` positional checks (M6) use the SAME
691
+ * skip, rather than a second copy that could drift from this one.
692
+ */
693
+ export function skipAzGlobalFlags(args: readonly Token[]): number {
694
+ let index = 0
695
+ while (index < args.length) {
696
+ const value = tokenValue(args[index] as Token)
697
+ if (AZ_GLOBAL_VALUE_FLAGS.has(value)) {
698
+ index += 2
699
+ continue
700
+ }
701
+ const equals = value.indexOf('=')
702
+ if (equals > 0 && AZ_GLOBAL_VALUE_FLAGS.has(value.slice(0, equals))) {
703
+ index += 1
704
+ continue
705
+ }
706
+ if (AZ_GLOBAL_BOOLEAN_FLAGS.has(value)) {
707
+ index += 1
708
+ continue
709
+ }
710
+ break
711
+ }
712
+ return index
713
+ }
714
+
715
+ function walkToolCommand(name: string, args: readonly Token[], cfg: ClassifyConfig, walk: (tokens: readonly Token[]) => readonly Invocation[]): readonly Invocation[] | null {
716
+ if (cfg.azureDevopsNames.has(name)) {
717
+ if (isHelpOrVersionOnly(args)) return []
718
+ const subIndex = skipAzGlobalFlags(args)
719
+ const subcommand = subIndex < args.length ? tokenValue(args[subIndex] as Token) : ''
720
+ if (!cfg.azureDevopsSubcommands.has(subcommand)) return null
721
+ if (isHelpOrVersionOnly(args.slice(subIndex + 1))) return []
722
+ return [{ kind: 'azure-devops', args }]
723
+ }
724
+ if (cfg.cargoNames.has(name)) return isHelpOrVersionOnly(args) ? [] : [{ kind: 'cargo' }]
725
+ if (cfg.frontendToolNames.has(name)) return isHelpOrVersionOnly(args) ? [] : [{ kind: 'frontend-tool', tool: name }]
726
+ if (cfg.bunNames.has(name)) return isBunTestSubcommand(args) ? [{ kind: 'bun-test' }] : null
727
+ if (cfg.justNames.has(name)) return walkJustCommand(args)
728
+ if (name === 'xargs' || name === 'xargs.exe') return xargsInvocation(args, walk)
729
+ // `find` is denied outright, not merely inspected for -exec/-delete
730
+ // payloads: it emits PATHS, so piping it to grep/rg/awk/etc. filters
731
+ // FILENAMES, never file contents (see impl/tool-find.ts for the message).
732
+ if (name === 'find' || name === 'find.exe') return [{ kind: 'find' }]
733
+ // `grep` is denied outright, in every shape including a pure stdin filter:
734
+ // `rg` does that job too, and faster, so there is no capability to carve out
735
+ // (see impl/tool-grep.ts for the message and the replacement set).
736
+ if (GREP_NAMES.has(name)) return [{ kind: 'grep' }]
737
+ return null
738
+ }
739
+
740
+ /** The grep family, plus the Windows spellings the other tool checks accept. */
741
+ const GREP_NAMES: ReadonlySet<string> = new Set([
742
+ 'grep', 'egrep', 'fgrep', 'rgrep',
743
+ 'grep.exe', 'egrep.exe', 'fgrep.exe', 'rgrep.exe',
744
+ ])
745
+
746
+ function walkNested(tokens: readonly Token[], index: number, args: readonly Token[], cfg: ClassifyConfig, walk: (tokens: readonly Token[]) => readonly Invocation[]): readonly Invocation[] {
747
+ const nested: Invocation[] = []
748
+ for (let look = 0; look < args.length; look += 1) {
749
+ const arg = args[look] as Token
750
+ if (dynamicToken(arg) || !isClassifiedCommandName(arg, cfg)) continue
751
+ for (const invocation of walk(tokens.slice(index + 1 + look))) {
752
+ if (invocation.kind === 'uncertain' && invocation.reason === 'git command missing subcommand') continue
753
+ nested.push(invocation)
754
+ }
755
+ }
756
+ return nested
757
+ }
758
+
759
+ /** Nested shell scripts (`-c` arguments, here-document and here-string bodies fed to a shell) are walked at most this deep. */
760
+ export const MAX_SHELL_SCRIPT_DEPTH = 16
761
+
762
+ /**
763
+ * A piece's stdin when a pipe feeds it: every earlier stage of its pipeline
764
+ * (nearest last), and whether the pipeline starts at a group/subshell
765
+ * (`{ ...; } | b`) preceded by a here-document — that group's output is unknown.
766
+ */
767
+ interface StdinFeed {
768
+ readonly upstream: readonly (readonly Token[])[]
769
+ /** Raw source text of each `upstream` stage (parallel to it), for static echo/printf reconstruction. */
770
+ readonly upstreamText: readonly string[]
771
+ readonly groupAfterHereText: boolean
772
+ /** Every piece before a group/subshell that pipes into this one (`(curl x) | bash`); empty otherwise. */
773
+ readonly groupUpstream: readonly (readonly Token[])[]
774
+ }
775
+
776
+ interface Parser {
777
+ readonly walk: (tokens: readonly Token[], feed?: StdinFeed) => readonly Invocation[]
778
+ /**
779
+ * Parse a shell script that a shell will run, one nesting level deeper.
780
+ * `feed` is the outer pipe's stdin, passed through only to the script's
781
+ * own first top-level piece — a `-c` script inherits the invoking
782
+ * process's stdin, so `bash -c 'bash'` fed by a pipe reads that pipe's
783
+ * content as ITS script, exactly like a bare piped `bash` would.
784
+ */
785
+ readonly parseScript: (script: string, feed?: StdinFeed) => readonly Invocation[]
786
+ }
787
+
788
+ /** A command's tokens with its here-document placeholders detached, plus the text it reads from here-documents/strings. */
789
+ interface ShellInput {
790
+ readonly tokens: readonly Token[]
791
+ readonly documents: readonly HereDocument[]
792
+ readonly hereStrings: readonly Token[]
793
+ }
794
+
795
+ /**
796
+ * Remove each `<<`/`<<-` + placeholder pair (so every rule sees exactly the
797
+ * tokens it saw when bodies were simply stripped) and collect the bodies and
798
+ * `<<<` words the command reads.
799
+ */
800
+ function detachShellInput(tokens: readonly Token[], documents: readonly HereDocument[]): ShellInput {
801
+ const kept: Token[] = []
802
+ const attached: HereDocument[] = []
803
+ const hereStrings: Token[] = []
804
+ for (let index = 0; index < tokens.length; index += 1) {
805
+ const token = tokens[index] as Token
806
+ const word = tokens[index + 1]
807
+ if ((token.redirect === '<<' || token.redirect === '<<-') && word !== undefined) {
808
+ const placeholder = hereDocumentPlaceholderIndex(tokenValue(word))
809
+ const document = placeholder === null ? undefined : documents[placeholder]
810
+ if (document !== undefined) {
811
+ attached.push(document)
812
+ index += 1
813
+ continue
814
+ }
815
+ }
816
+ if (token.redirect === '<<<' && word !== undefined) hereStrings.push(word)
817
+ kept.push(token)
818
+ }
819
+ return { tokens: kept, documents: attached, hereStrings }
820
+ }
821
+
822
+ const readsHereText = (input: ShellInput): boolean => input.documents.length > 0 || input.hereStrings.length > 0
823
+ const hasUnquotedDocument = (input: ShellInput): boolean => input.documents.some((document) => !document.quoted)
824
+
825
+ /**
826
+ * Walk here-document bodies and here-string words as the script a shell reads
827
+ * from them. The literal text is walked FIRST, so a recognisable command
828
+ * reports under its own rule; an unquoted body or an expanding here-string is
829
+ * then still uninspectable (the outer shell expands it before the inner one
830
+ * parses it) and fails closed as before.
831
+ */
832
+ function hereTextScript(input: ShellInput, parser: Parser): readonly Invocation[] {
833
+ const found: Invocation[] = []
834
+ for (const document of input.documents) found.push(...parser.parseScript(document.body))
835
+ for (const word of input.hereStrings) found.push(...parser.parseScript(tokenValue(word)))
836
+ if (hasUnquotedDocument(input)) found.push(...uncertain('unparsed unquoted here-document'))
837
+ if (input.hereStrings.some((word) => /[$`]/.test(tokenValue(word)))) found.push(...uncertain('dynamic here-string'))
838
+ return found
839
+ }
840
+
841
+ function commandOf(input: ShellInput): { readonly name: string; readonly args: readonly Token[] } {
842
+ const index = skipPrefixes(input.tokens)
843
+ const name = index < input.tokens.length ? basename(input.tokens[index] as Token) : ''
844
+ return { name, args: withoutRedirections(input.tokens.slice(index + 1)) }
845
+ }
846
+
847
+ const pipelineReadsHereText = (feed: StdinFeed, documents: readonly HereDocument[]): boolean =>
848
+ feed.groupAfterHereText || feed.upstream.some((tokens) => readsHereText(detachShellInput(tokens, documents)))
849
+
850
+ /**
851
+ * `cat <<'EOF' | bash`: a single bare `cat` stage passes its here-text through
852
+ * unchanged, so it is walked as the script; any other stage (or more than one)
853
+ * may transform it unseen, so it fails closed.
854
+ */
855
+ function pipedHereTextScript(feed: StdinFeed, documents: readonly HereDocument[], parser: Parser): readonly Invocation[] {
856
+ if (!pipelineReadsHereText(feed, documents)) return []
857
+ const only = feed.upstream.length === 1 && !feed.groupAfterHereText ? detachShellInput(feed.upstream[0] as readonly Token[], documents) : null
858
+ if (only === null) return uncertain('here-document piped to shell')
859
+ const { name, args } = commandOf(only)
860
+ const plainCat = (name === 'cat' || name === 'cat.exe') && args.every((token) => tokenValue(token) === '-')
861
+ return plainCat ? hereTextScript(only, parser) : uncertain('here-document piped to shell')
862
+ }
863
+
864
+ const pipedFetcher = (feed: StdinFeed): string | null => {
865
+ for (const stage of [...feed.groupUpstream, ...feed.upstream]) {
866
+ const fetcher = stageFetcher(stage)
867
+ if (fetcher !== null) return fetcher
868
+ }
869
+ return null
870
+ }
871
+
872
+ /** The first fetcher run inside a substitution among a code runner's words (`bash <(curl x)`, `eval "$(curl x)"`). */
873
+ const substitutedFetcher = (tokens: readonly Token[]): string | null => {
874
+ for (const token of tokens) {
875
+ const fetcher = wordFetcher(tokenValue(token))
876
+ if (fetcher !== null) return fetcher
877
+ }
878
+ return null
879
+ }
880
+
881
+ function makeParser(cfg: ClassifyConfig): Parser & { readonly parse: (command: string) => readonly Invocation[] } {
882
+ const documents: HereDocument[] = []
883
+ let depth = 0
884
+
885
+ /**
886
+ * A shell fed by a pipe (no here-text anywhere in the pipeline): remote
887
+ * content is denied outright; otherwise, when the shell reads its script
888
+ * from stdin, a lone static echo/printf is reconstructed and walked and any
889
+ * other producer fails closed.
890
+ */
891
+ const pipedShellScript = (args: readonly Token[], input: ShellInput, feed: StdinFeed): readonly Invocation[] => {
892
+ if (pipelineReadsHereText(feed, documents)) return pipedHereTextScript(feed, documents, parser)
893
+ const fetcher = pipedFetcher(feed)
894
+ if (fetcher !== null) return remoteExec(fetcher)
895
+ if (readsHereText(input) || readsStdinFromFile(args) || !shellScriptFromStdin(withoutRedirections(args))) return []
896
+ const only = feed.upstream.length === 1 && feed.groupUpstream.length === 0 ? feed.upstreamText[0] : undefined
897
+ const script = only === undefined ? null : staticProducerOutput(only)
898
+ return script === null ? uncertain('script piped to shell') : parser.parseScript(script)
899
+ }
900
+
901
+ const stdinInvocations = (name: string, args: readonly Token[], input: ShellInput, feed: StdinFeed | undefined): readonly Invocation[] => {
902
+ const found: Invocation[] = []
903
+ const runsCode = cfg.shellNames.has(name) || cfg.shellExecutors.has(name) || EXPRESSION_RUNNERS.has(name)
904
+ const substituted = runsCode ? substitutedFetcher(args) : null
905
+ if (substituted !== null) found.push(...remoteExec(substituted))
906
+ if (EXPRESSION_RUNNERS.has(name) && feed !== undefined) {
907
+ const fetcher = pipedFetcher(feed)
908
+ found.push(...(fetcher === null ? uncertain('script piped to expression runner') : remoteExec(fetcher)))
909
+ return found
910
+ }
911
+ if (cfg.shellNames.has(name) && !shellRunsNothing(withoutRedirections(args))) {
912
+ found.push(...hereTextScript(input, parser))
913
+ if (feed !== undefined) found.push(...pipedShellScript(args, input, feed))
914
+ return found
915
+ }
916
+ const pipedHereText = feed !== undefined && pipelineReadsHereText(feed, documents)
917
+ const fedHereText = readsHereText(input) || pipedHereText
918
+ const xargsShell = (name === 'xargs' || name === 'xargs.exe') && args.some((token) => cfg.shellNames.has(basename(token)))
919
+ if (xargsShell && feed !== undefined && !pipedHereText) {
920
+ const fetcher = pipedFetcher(feed)
921
+ if (fetcher !== null) return [...found, ...remoteExec(fetcher)]
922
+ if (xargsShellReadsInput(args, cfg)) return [...found, ...uncertain('dynamic xargs execution')]
923
+ }
924
+ if (fedHereText && xargsShell) return [...found, ...uncertain('dynamic xargs execution')]
925
+ return hasUnquotedDocument(input) ? [...found, ...uncertain('unparsed unquoted here-document')] : found
926
+ }
927
+
928
+ const walk = (rawTokens: readonly Token[], feed?: StdinFeed): readonly Invocation[] => {
929
+ const input = detachShellInput(rawTokens, documents)
930
+ const tokens = input.tokens
931
+ // R3 (STATUS.md 12p): `env -S`/`--split-string` splits its
932
+ // value into argv and execs it — but `skipPrefixes`'s own `skipEnvFlags`
933
+ // (../prefixes.ts) treats that value as an opaque, value-taking flag
934
+ // argument and skips straight past it, so whatever it actually names was
935
+ // previously invisible to every rule (a bare `env -S '<value>'` made
936
+ // `invocations()` return `[]` — no Invocation, so no rule's match() ever
937
+ // ran). Checked BEFORE `skipPrefixes` runs, so this fires instead of
938
+ // silently vanishing; see `envUsesSplitString`'s own doc.
939
+ if (envUsesSplitString(tokens)) return [...uncertain('env -S/--split-string execution')]
940
+ const index = skipPrefixes(tokens)
941
+ const name = index < tokens.length ? basename(tokens[index] as Token) : ''
942
+ const args = tokens.slice(index + 1)
943
+ // Emitted FIRST, so a `.git` write reports as fs.git-internals rather than
944
+ // as the generic rm/trash/find denial the same command also trips.
945
+ const targets = gitInternalsWriteTargets(name, args, tokens)
946
+ const found: Invocation[] = targets.length > 0 ? [{ kind: 'git-internals-write', name, targets }] : []
947
+ const carrierTargets = protectedCarrierWriteTargets(name, args, tokens)
948
+ if (carrierTargets.length > 0) found.push({ kind: 'protected-carrier-write', name, targets: carrierTargets })
949
+ // A write whose target is an unresolvable command substitution fails closed, like any other dynamic construct.
950
+ if (unresolvableRedirectionWriteTargets(tokens).length > 0) found.push(...uncertain('dynamic redirection target'))
951
+ if (unresolvableOperandWriteTargets(name, args).length > 0) found.push(...uncertain('dynamic write target'))
952
+ found.push(...stdinInvocations(name, args, input, feed))
953
+ found.push(...classify(tokens, index, name, args, feed))
954
+ return found
955
+ }
956
+ const classify = (tokens: readonly Token[], index: number, name: string, args: readonly Token[], feed: StdinFeed | undefined): readonly Invocation[] => {
957
+ if (index >= tokens.length) return []
958
+ const first = tokens[index] as Token
959
+ if (name === 'git-worktree' || name === 'git-worktree.exe') return [{ kind: 'git', subcommand: 'worktree', globals: [], rest: args }]
960
+ if (dynamicToken(first)) return uncertain('dynamic shell command')
961
+
962
+ // Independent-review finding 1 (HIGH, pre-existing, affects every rule):
963
+ // lean-ctx's own -c/-t/exec/raw(/bypass) run an ARBITRARY shell command
964
+ // that nothing below this point ever inspected — see walkLeanCtxExec's
965
+ // own doc for the fix and the verified-against-the-real-binary behavior
966
+ // it relies on. Checked before walkShellCommand/walkToolCommand/the
967
+ // generic fallback, same priority as a real shell's own -c.
968
+ const leanCtxExec = walkLeanCtxExec(name, args, tokens, parser, feed)
969
+ if (leanCtxExec !== null) return leanCtxExec
970
+
971
+ const shellCommand = walkShellCommand(name, args, cfg, parser, feed)
972
+ const denied = deniedPathInvocation(name, args, cfg)
973
+ if (denied !== null) return [denied]
974
+ if (shellCommand !== null) return shellCommand
975
+ if (name === 'git' || name === 'git.exe') return walkGit(tokens, index, parser)
976
+ const fileCommand = walkFileCommand(name, args, cfg)
977
+ if (fileCommand !== null) return fileCommand
978
+ const toolCommand = walkToolCommand(name, args, cfg, walk)
979
+ if (toolCommand !== null) return toolCommand
980
+ if (cfg.substitutableFinders.has(name)) return [{ kind: 'read-only', name }]
981
+ if (cfg.introspectionNames.has(name) || cfg.subcommandTools.has(name) || cfg.readOnlyConsumers.has(name)) return []
982
+ // `prefix` is omitted entirely (rather than set to `[]`) when nothing was
983
+ // skipped, so every existing `toEqual({kind:'command', name, args})`
984
+ // fixture — none of which name a wrapper or leading assignment — keeps
985
+ // matching exactly; only an invocation that actually had one gains the
986
+ // extra field.
987
+ const prefix = tokens.slice(0, index)
988
+ const command: Invocation = prefix.length > 0 ? { kind: 'command', name, args, prefix } : { kind: 'command', name, args }
989
+ return [command, ...walkNested(tokens, index, args, cfg, walk)]
990
+ }
991
+
992
+ const feedFor = (
993
+ feeds: readonly PieceFeed[],
994
+ pieces: readonly (readonly Token[])[],
995
+ texts: readonly string[],
996
+ index: number,
997
+ ): StdinFeed | undefined => {
998
+ if (feeds[index] !== 'piece' && feeds[index] !== 'group') return undefined
999
+ let start = index
1000
+ while (start > 0 && feeds[start] === 'piece') start -= 1
1001
+ const upstream = pieces.slice(start, index)
1002
+ const groupUpstream = feeds[start] === 'group' ? pieces.slice(0, start) : []
1003
+ const groupAfterHereText = groupUpstream.some((tokens) => readsHereText(detachShellInput(tokens, documents)))
1004
+ return { upstream, upstreamText: texts.slice(start, index), groupAfterHereText, groupUpstream }
1005
+ }
1006
+
1007
+ /**
1008
+ * `outerFeed` is a `-c` script's inherited stdin (the outer pipe): it
1009
+ * applies only to the script's own first top-level piece, when that piece
1010
+ * isn't already fed by a pipe/group inside the script itself — a `-c`
1011
+ * script's first stage reads the invoking process's stdin exactly like a
1012
+ * bare piped shell would (`producer | outer -c 'bash'` reads `producer`'s
1013
+ * output as the SCRIPT `bash` then reads, same as `producer | bash`).
1014
+ * Nested `$(...)`/backtick sources never inherit it.
1015
+ */
1016
+ const parse = (command: string, outerFeed?: StdinFeed): readonly Invocation[] => {
1017
+ const offset = documents.length
1018
+ const scan = scanHereDocuments(command, (n) => `<<${hereDocumentPlaceholder(offset + n)} `)
1019
+ if (scan.invalid) return uncertain(scan.reason ?? 'unparsed here-document')
1020
+ documents.push(...scan.documents)
1021
+
1022
+ const nested = nestedSources(scan.text)
1023
+ if (nested.invalid) return uncertain(nested.reason ?? 'unclosed shell construct')
1024
+ const found: Invocation[] = []
1025
+ for (const source of nested.sources) found.push(...parse(source))
1026
+
1027
+ const split = splitShell(scan.text)
1028
+ if (split.invalid) return uncertain(split.reason ?? 'unparsed shell input')
1029
+ const pieces: (readonly Token[])[] = []
1030
+ for (const piece of split.pieces) {
1031
+ const tokenized = tokenize(piece)
1032
+ if (tokenized.invalid) return uncertain('unparsed shell token')
1033
+ pieces.push(tokenized.tokens)
1034
+ }
1035
+ pieces.forEach((tokens, index) => {
1036
+ const localFeed = feedFor(split.feeds, pieces, split.pieces, index)
1037
+ found.push(...walk(tokens, localFeed ?? (index === 0 ? outerFeed : undefined)))
1038
+ })
1039
+ return found
1040
+ }
1041
+
1042
+ const parseScript = (script: string, feed?: StdinFeed): readonly Invocation[] => {
1043
+ if (depth >= MAX_SHELL_SCRIPT_DEPTH) return uncertain('shell input nested too deeply')
1044
+ depth += 1
1045
+ try {
1046
+ return parse(script, feed)
1047
+ } finally {
1048
+ depth -= 1
1049
+ }
1050
+ }
1051
+
1052
+ const parser = { walk, parseScript, parse }
1053
+ return parser
1054
+ }
1055
+
1056
+ /** Parse and classify a raw shell command line into zero or more Invocations. */
1057
+ export function invocations(command: string, cfg: ClassifyConfig = DEFAULT_CLASSIFY_CONFIG): readonly Invocation[] {
1058
+ return makeParser(cfg).parse(command)
1059
+ }
1060
+
1061
+ /**
1062
+ * The first argument that reaches into one of `cfg.deniedTrees`, or null.
1063
+ *
1064
+ * Matches a tree EXACTLY or as a parent directory — `<tree>` and `<tree>/...`,
1065
+ * never `<tree>-m5`. A clone whose name merely starts with the denied one is a
1066
+ * different repository and stays usable; that boundary is the whole point.
1067
+ */
1068
+ function deniedPathInvocation(
1069
+ name: string,
1070
+ args: readonly Token[],
1071
+ cfg: ClassifyConfig,
1072
+ ): Invocation | null {
1073
+ if (cfg.deniedTrees.length === 0) return null
1074
+ for (const token of args) {
1075
+ const raw = tokenValue(token)
1076
+ if (raw.startsWith('-')) continue
1077
+ const path = raw.replace(/\/+$/, '')
1078
+ for (const tree of cfg.deniedTrees) {
1079
+ if (path === tree || path.startsWith(`${tree}/`)) return { kind: 'denied-path', name, path: raw }
1080
+ }
1081
+ }
1082
+ return null
1083
+ }