@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.12 → 0.1.26

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 (405) hide show
  1. package/bin/adl +2 -2
  2. package/bin/git +16 -17
  3. package/bin/git-ado +25 -24
  4. package/bin/git-defaults +173 -0
  5. package/bin/health-check +8 -1
  6. package/bin/kimi-hooks +1 -1
  7. package/bin/lib/adl-package.sh +257 -0
  8. package/bin/lib/fleet-host.sh +8 -5
  9. package/bin/reconcile +482 -232
  10. package/bin/reconcile-host +30 -20
  11. package/bin/setup +20 -17
  12. package/bin/shell-path-policy +15 -5
  13. package/bin/sync-and-reconcile +62 -64
  14. package/bin/validate-manifest +33 -9
  15. package/package.json +2 -10
  16. package/packages/agent-hooks/README.md +14 -9
  17. package/packages/agent-hooks/assets/hooks.json +0 -20
  18. package/packages/agent-hooks/src/cli/args.ts +1 -1
  19. package/packages/agent-hooks/src/cli/doctor.ts +6 -58
  20. package/packages/agent-hooks/src/deploy/deploy.ts +12 -41
  21. package/packages/agent-hooks/src/deploy/doctor.ts +8 -11
  22. package/packages/agent-hooks/src/deploy/index.ts +14 -14
  23. package/packages/agent-hooks/src/deploy/json-merge.ts +7 -8
  24. package/packages/agent-hooks/src/deploy/legacy-lock.ts +2 -2
  25. package/packages/agent-hooks/src/deploy/list.ts +1 -1
  26. package/packages/agent-hooks/src/deploy/lock.ts +31 -12
  27. package/packages/agent-hooks/src/deploy/profile.ts +3 -4
  28. package/packages/agent-hooks/src/deploy/source.ts +1 -1
  29. package/packages/agent-hooks/src/gating/config.ts +10 -24
  30. package/packages/agent-hooks/src/gating/root.ts +3 -3
  31. package/packages/agent-hooks/src/hooks/briefing/handler.ts +2 -2
  32. package/packages/agent-hooks/src/hooks/briefing/index.ts +27 -287
  33. package/packages/agent-hooks/src/hooks/contract.ts +17 -17
  34. package/packages/agent-hooks/src/hooks/crg/background.ts +27 -0
  35. package/packages/agent-hooks/src/hooks/crg/index.ts +7 -6
  36. package/packages/agent-hooks/src/hooks/log.ts +8 -10
  37. package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +1 -1
  38. package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +1 -1
  39. package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +6 -5
  40. package/packages/agent-hooks/src/hosts/registry.ts +1 -1
  41. package/packages/agent-hooks/src/log/adl-dir.ts +44 -0
  42. package/packages/agent-hooks/src/log/writer.ts +16 -2
  43. package/packages/agent-hooks/src/main.ts +7 -41
  44. package/packages/agent-hooks/src/pre-tool-use/handler.ts +71 -82
  45. package/packages/agent-hooks/src/rules/engine.ts +13 -17
  46. package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +3 -3
  47. package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +12 -14
  48. package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +28 -28
  49. package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +8 -9
  50. package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +56 -59
  51. package/packages/agent-hooks/src/rules/impl/adl-identity.ts +16 -23
  52. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +19 -16
  53. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +2 -2
  54. package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +9 -12
  55. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +5 -7
  56. package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +411 -0
  57. package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +4 -5
  58. package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +6 -7
  59. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +77 -70
  60. package/packages/agent-hooks/src/rules/impl/deny-allowlist.ts +159 -0
  61. package/packages/agent-hooks/src/rules/impl/git-helpers.ts +21 -23
  62. package/packages/agent-hooks/src/rules/impl/git-worktree.ts +14 -28
  63. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -3
  64. package/packages/agent-hooks/src/rules/impl/messages.ts +1 -1
  65. package/packages/agent-hooks/src/rules/impl/pr-completion.ts +1 -1
  66. package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +35 -46
  67. package/packages/agent-hooks/src/rules/impl/tool-find.ts +34 -7
  68. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +34 -12
  69. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +52 -9
  70. package/packages/agent-hooks/src/rules/ports.ts +5 -7
  71. package/packages/agent-hooks/src/rules/registry.ts +4 -6
  72. package/packages/agent-hooks/src/rules/shell/git-identity.ts +86 -101
  73. package/packages/agent-hooks/src/rules/shell/git-internals.ts +1 -1
  74. package/packages/agent-hooks/src/rules/shell/invocation.ts +24 -118
  75. package/packages/agent-hooks/src/rules/shell/piped-script.ts +1 -1
  76. package/packages/agent-hooks/src/rules/shell/prefixes.ts +1 -1
  77. package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +11 -10
  78. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +26 -26
  79. package/packages/agent-hooks/src/rules/shell/substitution.ts +1 -1
  80. package/packages/agent-hooks/src/rules/shell/xargs.ts +2 -2
  81. package/packages/agent-hooks/src/rules/spec.ts +2 -2
  82. package/packages/agent-settings/README.md +287 -0
  83. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +14 -16
  84. package/packages/agent-settings/assets/agents/architect.toml +2 -0
  85. package/packages/agent-settings/assets/agents/code-reviewer.toml +2 -0
  86. package/packages/agent-settings/assets/agents/executor.toml +2 -0
  87. package/packages/agent-settings/assets/agents/explore.toml +2 -0
  88. package/packages/agent-settings/assets/agents/planner.toml +2 -0
  89. package/packages/agent-settings/assets/mcp.json +0 -4
  90. package/packages/agent-settings/deploy-profile.json +139 -0
  91. package/packages/agent-settings/deploy-profile.schema.json +147 -0
  92. package/packages/agent-settings/managed/README.md +29 -30
  93. package/packages/agent-settings/managed/codex-requirements.toml +9 -9
  94. package/packages/agent-settings/package.json +20 -0
  95. package/packages/agent-settings/src/deploy/agent-render.ts +2 -2
  96. package/packages/agent-settings/src/deploy/agent-toml.ts +1 -1
  97. package/packages/agent-settings/src/deploy/agents-deploy.ts +17 -3
  98. package/packages/agent-settings/src/deploy/doctor.ts +3 -83
  99. package/packages/agent-settings/src/deploy/index.ts +6 -33
  100. package/packages/agent-settings/src/deploy/legacy-lock.ts +6 -6
  101. package/packages/agent-settings/src/deploy/list.ts +5 -19
  102. package/packages/agent-settings/src/deploy/lock.ts +8 -18
  103. package/packages/agent-settings/src/deploy/mcp-deploy.ts +10 -5
  104. package/packages/agent-settings/src/deploy/mcp-merge.ts +22 -3
  105. package/packages/agent-settings/src/deploy/mcp-source.ts +1 -1
  106. package/packages/agent-settings/src/deploy/profile.ts +11 -35
  107. package/packages/agent-settings/src/deploy/retire-lean-ctx.ts +184 -0
  108. package/packages/agent-settings/src/deploy/settings.ts +17 -22
  109. package/packages/agent-settings/src/global-surface-sweep.ts +0 -2
  110. package/packages/agent-settings/src/kimi-hooks.ts +339 -192
  111. package/packages/agent-settings/src/opencode-hooks.ts +1 -1
  112. package/packages/agent-settings/tsconfig.json +23 -0
  113. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +120 -0
  114. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +276 -0
  115. package/packages/agent-skills/assets/debug-issue/SKILL.md +8 -7
  116. package/packages/agent-skills/assets/explore-codebase/SKILL.md +13 -13
  117. package/packages/agent-skills/assets/refactor-safely/SKILL.md +12 -11
  118. package/packages/agent-skills/assets/review-changes/SKILL.md +7 -7
  119. package/packages/agent-skills/assets/testing/references/e2e.md +37 -0
  120. package/packages/agent-skills/assets/testing/references/integration.md +37 -0
  121. package/packages/agent-skills/assets/testing/references/unit.md +32 -0
  122. package/packages/agent-skills/dist/deploy.d.ts +2 -0
  123. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  124. package/packages/agent-skills/dist/deploy.js +27 -1
  125. package/packages/agent-skills/dist/deploy.js.map +1 -1
  126. package/packages/agent-skills/dist/doctor.d.ts +0 -2
  127. package/packages/agent-skills/dist/doctor.d.ts.map +1 -1
  128. package/packages/agent-skills/dist/doctor.js +1 -1
  129. package/packages/agent-skills/dist/doctor.js.map +1 -1
  130. package/packages/agent-skills/dist/fs-utils.d.ts +0 -1
  131. package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -1
  132. package/packages/agent-skills/dist/fs-utils.js +1 -1
  133. package/packages/agent-skills/dist/fs-utils.js.map +1 -1
  134. package/packages/agent-skills/src/deploy.ts +29 -1
  135. package/packages/agent-skills/src/doctor.ts +1 -1
  136. package/packages/agent-skills/src/fs-utils.ts +1 -1
  137. package/packages/azdo/README.md +10 -9
  138. package/packages/azdo/package.json +1 -3
  139. package/packages/azdo/src/azdo-pl.ts +2 -54
  140. package/packages/azdo/src/azdo-pr-review.ts +4 -4
  141. package/packages/azdo/src/azdo-repo-doctor.ts +101 -52
  142. package/packages/azdo/src/azdo-wit.ts +41 -2
  143. package/packages/azdo/src/lib/agent-identity.ts +12 -14
  144. package/packages/azdo/src/lib/agent-token.ts +2 -2
  145. package/packages/azdo/src/lib/client.ts +0 -5
  146. package/packages/azdo/src/lib/graph-identity.ts +4 -4
  147. package/packages/azdo/src/workflow-rules-matrix.csv +85 -0
  148. package/packages/azdo/src/workflow-rules.ts +128 -0
  149. package/packages/fleet/manifest.json +35 -11
  150. package/packages/fleet/manifest.schema.json +62 -10
  151. package/packages/fleet/src/default-agent-file.ts +3 -3
  152. package/packages/fleet/src/install-adl-package.ts +73 -15
  153. package/packages/fleet/src/install-embedding-model.ts +13 -13
  154. package/packages/fleet/src/managed-settings.ts +7 -7
  155. package/packages/fleet/src/repo-maintain.ts +91 -55
  156. package/packages/fleet/src/scratch-reap.ts +5 -5
  157. package/packages/fleet/src/vscode-settings.ts +5 -5
  158. package/packages/repo-controls/README.md +141 -43
  159. package/packages/repo-controls/assets/promote.ts +4 -4
  160. package/packages/repo-controls/assets/reconcile-guard.sh +10 -46
  161. package/packages/repo-controls/assets/version-bump.sh +31 -84
  162. package/packages/repo-controls/bunfig.toml +6 -7
  163. package/packages/repo-controls/package/adl-compliance/AGENTS.md +5 -6
  164. package/packages/repo-controls/package/adl-compliance/conftest.py +3 -1
  165. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +4 -16
  166. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +9 -2
  167. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +1 -0
  168. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +11 -0
  169. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/__init__.py +7 -0
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/check.py +266 -0
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.drafts/c.md +1 -0
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.drafts/d.md +1 -0
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.hidden.md +1 -0
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/a.md +1 -0
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/b.md +1 -0
  176. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/data.csv +1 -0
  177. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/diagram.png +1 -0
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/mock.png +1 -0
  179. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/sub/nested.md +1 -0
  180. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs.md +1 -0
  181. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/grants.yaml +4 -0
  182. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p1.md +1 -0
  183. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p2.md +1 -0
  184. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p3.md +1 -0
  185. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p4.md +1 -0
  186. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p5.md +1 -0
  187. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/build.md +1 -0
  188. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/overview.md +1 -0
  189. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/install.md +1 -0
  190. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/start.md +1 -0
  191. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/grants.yaml +4 -0
  192. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-001-tenancy.md +1 -0
  193. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-002-carrier.md +1 -0
  194. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-003-cli.md +1 -0
  195. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/archive/adr-000-old.md +1 -0
  196. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/archive/adr-000-older.md +1 -0
  197. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/a.md +1 -0
  198. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/b.md +1 -0
  199. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/c.md +1 -0
  200. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/grants.yaml +4 -0
  201. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-01.md +1 -0
  202. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-02.md +1 -0
  203. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-03.md +1 -0
  204. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-04.md +1 -0
  205. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-05.md +1 -0
  206. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-06.md +1 -0
  207. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-07.md +1 -0
  208. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-08.md +1 -0
  209. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-09.md +1 -0
  210. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-10.md +1 -0
  211. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-11.md +1 -0
  212. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-12.md +1 -0
  213. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-13.md +1 -0
  214. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-14.md +1 -0
  215. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-15.md +1 -0
  216. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-16.md +1 -0
  217. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/grants.yaml +4 -0
  218. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/a.md +1 -0
  219. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/b.md +1 -0
  220. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/c.md +1 -0
  221. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/grants.yaml +4 -0
  222. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/a.md +1 -0
  223. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/b.md +1 -0
  224. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/c.md +1 -0
  225. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/grants.yaml +4 -0
  226. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/a.md +1 -0
  227. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/b.md +1 -0
  228. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/c.md +1 -0
  229. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/d.md +1 -0
  230. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/e.md +1 -0
  231. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/grants.yaml +4 -0
  232. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/a.md +1 -0
  233. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/b.md +1 -0
  234. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/c.md +1 -0
  235. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/short/only.md +1 -0
  236. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/grants.yaml +4 -0
  237. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/a.md +1 -0
  238. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/b.md +1 -0
  239. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/c.md +1 -0
  240. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/d.md +1 -0
  241. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/a.md +1 -0
  242. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/b.md +1 -0
  243. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/grants.yaml +6 -0
  244. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/a.md +1 -0
  245. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/b.md +1 -0
  246. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/c.md +1 -0
  247. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/grants.yaml +3 -0
  248. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/test_check.py +345 -0
  249. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +18 -5
  250. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +35 -0
  251. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/__init__.py +7 -0
  252. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/check.py +91 -0
  253. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/AGENTS.md +3 -0
  254. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/CLAUDE.md +3 -0
  255. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/-leading-hyphen.md +3 -0
  256. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/AGENTS.md +3 -0
  257. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/double--hyphen.md +3 -0
  258. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/getting started.md +3 -0
  259. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/nested/page-here.md +3 -0
  260. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/trailing-hyphen-.md +3 -0
  261. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +4 -0
  262. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/user_profile.md +3 -0
  263. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/UI-CONTRACT.png +2 -0
  264. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/diagram.png +1 -0
  265. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/index.md +4 -0
  266. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/notes.md +3 -0
  267. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/reference.html +1 -0
  268. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/2026-10-experiment.md +3 -0
  269. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +6 -0
  270. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs.md +5 -0
  271. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +194 -0
  272. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +14 -2
  273. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +5 -5
  274. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-shipped-register-legacy/docs/compliance/index.md +10 -0
  275. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-shipped-register-type/docs/compliance/index.md +10 -0
  276. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +58 -6
  277. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +0 -1
  278. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +64 -17
  279. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +60 -9
  280. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +17 -0
  281. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +13 -0
  282. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +0 -1
  283. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +0 -1
  284. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/no-type.md +9 -0
  285. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-experiment.md +10 -0
  286. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-research.md +10 -0
  287. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs-okf-profiles.yaml +5 -0
  288. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs/architecture/bad-status.md +0 -1
  289. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +0 -1
  290. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/experiment-ok.md +15 -0
  291. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +15 -0
  292. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +16 -0
  293. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +11 -0
  294. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +11 -0
  295. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-last-update.md +11 -0
  296. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-sources.md +10 -0
  297. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +11 -0
  298. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +11 -0
  299. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +16 -0
  300. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +14 -0
  301. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/architecture/created-date.md +21 -0
  302. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/architecture/research-shaped.md +19 -0
  303. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/operations/runbook.md +26 -0
  304. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +228 -2
  305. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +5 -0
  306. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +11 -2
  307. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +5 -3
  308. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +160 -44
  309. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +0 -2
  310. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/a.ts +1 -0
  311. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/b.ts +1 -0
  312. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/grants.yaml +3 -0
  313. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +0 -2
  314. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  315. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +5 -0
  316. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/a.ts +1 -0
  317. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/b.ts +1 -0
  318. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/c.ts +1 -0
  319. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +4 -0
  320. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  321. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +5 -0
  322. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +0 -2
  323. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  324. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/grants.yaml +3 -0
  325. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/register.py +127 -0
  326. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +265 -29
  327. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +75 -2
  328. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +194 -0
  329. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +56 -10
  330. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +256 -0
  331. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +40 -12
  332. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +108 -0
  333. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +1 -1
  334. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +2 -3
  335. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +23 -6
  336. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +76 -0
  337. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +58 -17
  338. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +161 -0
  339. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +35 -3
  340. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +108 -1
  341. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +281 -61
  342. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/absolute-path.yaml +3 -0
  343. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/backslash-path.yaml +3 -0
  344. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/baseline-is-a-list.yaml +2 -0
  345. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/boolean-size.yaml +3 -0
  346. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/dot-segment.yaml +3 -0
  347. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/duplicate-path.yaml +4 -0
  348. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-file.yaml +0 -0
  349. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-segment.yaml +3 -0
  350. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/float-size.yaml +3 -0
  351. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/negative-size.yaml +3 -0
  352. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-over-limit-baseline.yaml +1 -0
  353. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-schema-version.yaml +1 -0
  354. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/non-string-path.yaml +3 -0
  355. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/not-yaml.yaml +2 -0
  356. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/null-size.yaml +3 -0
  357. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/parent-segment.yaml +3 -0
  358. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/string-size.yaml +3 -0
  359. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/top-level-is-a-list.yaml +2 -0
  360. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/trailing-slash.yaml +3 -0
  361. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/unknown-key.yaml +3 -0
  362. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/wrong-schema-version.yaml +2 -0
  363. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/zero-size.yaml +3 -0
  364. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/valid.yaml +4 -0
  365. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +758 -45
  366. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +4 -3
  367. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +2 -2
  368. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +490 -0
  369. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +368 -55
  370. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +4 -2
  371. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +523 -0
  372. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +1179 -0
  373. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +122 -3
  374. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +33 -1
  375. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +0 -11
  376. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +16 -1
  377. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -2
  378. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +12 -1
  379. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +133 -147
  380. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +133 -147
  381. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
  382. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +52 -10
  383. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +23 -4
  384. package/packages/repo-controls/package.json +2 -9
  385. package/packages/repo-controls/scripts/hooks/post-merge +13 -0
  386. package/packages/repo-controls/scripts/hooks/pre-push +3 -3
  387. package/packages/repo-controls/src/azdo-push.ts +64 -36
  388. package/packages/repo-controls/src/azure-devops-governance.ts +7 -9
  389. package/packages/repo-controls/src/azure-devops-remote.ts +1 -1
  390. package/packages/repo-controls/src/cli.ts +29 -8
  391. package/packages/repo-controls/src/compliance-config.ts +410 -2
  392. package/packages/repo-controls/src/config.ts +133 -60
  393. package/packages/repo-controls/src/constants.ts +13 -27
  394. package/packages/repo-controls/src/coverage-ratchet.ts +4 -4
  395. package/packages/repo-controls/src/doctor.ts +46 -19
  396. package/packages/repo-controls/src/gitignore-block.ts +5 -29
  397. package/packages/repo-controls/src/hook-log.ts +1 -2
  398. package/packages/repo-controls/src/hook.ts +201 -45
  399. package/packages/repo-controls/src/install.ts +14 -8
  400. package/packages/repo-controls/src/integrity.ts +3 -3
  401. package/packages/repo-controls/src/lfs.ts +111 -0
  402. package/packages/repo-controls/src/promote.ts +8 -8
  403. package/packages/repo-controls/src/runtime.ts +9 -1
  404. package/packages/repo-controls/src/workflows-drift.ts +4 -5
  405. package/packages/repo-controls/src/worktree-guard.ts +3 -3
@@ -0,0 +1,523 @@
1
+ """REP-002 grades the manifest and lockfile paths a repository DECLARES.
2
+
3
+ `[controls.compliance.lockfiles]` in `adl-config.toml` reaches the engine as four
4
+ comma-list declarations, a half (Bun, Cargo) being declared by a manifests list
5
+ and a lockfiles list together:
6
+
7
+ bun-manifests / bun-lockfiles `package.json` paths or globs / `bun.lock` paths
8
+ cargo-manifests / cargo-lockfiles `Cargo.toml` paths or globs / `Cargo.lock` paths
9
+
10
+ Why this exists, measured on enact-combined (2026-10-01): its `typescript` tree is
11
+ a brace union of directories, and its manifests sit at the repository ROOT and
12
+ under `apps/*/` and `packages/*/`. The registry scope `<typescript>/package.json`
13
+ expands to `{apps,packages,...}/package.json`, a ONE-level path that matches
14
+ nothing, so the whole-tree run was could-not-run while a narrowed run passed. The
15
+ declaration restates the scope (which files select the gate) and the grade (which
16
+ lockfiles must exist) from the same lists.
17
+
18
+ This file is separate from `test_check.py` on purpose: that module carries a
19
+ module-level cargo skip and an autouse patch of `runner.lookup`, and nothing in it
20
+ is edited here. Every test below that needs a real `cargo` says so.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import shutil
26
+ from collections.abc import Mapping
27
+ from pathlib import Path
28
+
29
+ import pytest
30
+ from fixture_trees import fixture_trees
31
+ from helpers import corpus, corpus_files
32
+
33
+ from adl_compliance import runner
34
+ from adl_compliance.checks.repository.lockfile_integrity import LockfileIntegrity
35
+ from adl_compliance.core.context import Context, FileSet
36
+ from adl_compliance.core.errors import CouldNotRun
37
+ from adl_compliance.core.verdict import Outcome, Verdict
38
+ from adl_compliance.policy.registry import load, policy_text
39
+
40
+ ROW = load().by_id("REP-002")
41
+ CLEAN_CORPUS = corpus("repository", "lockfile-integrity") / "clean"
42
+
43
+ requires_cargo = pytest.mark.skipif(shutil.which("cargo") is None, reason="cargo is not installed")
44
+
45
+ # enact-combined's declared trees, as `adl-config.toml` states them.
46
+ ENACT_TREES = {
47
+ "rust": "",
48
+ "frontend": "",
49
+ "typescript": "{apps,packages,workflows,infra,tests,dev,live-probes}",
50
+ }
51
+ # The declaration enact-combined needs (see .scratchpad/rep002-scope/enact-combined-patch.toml).
52
+ BUN = {
53
+ "bun-manifests": "package.json,apps/*/package.json,packages/*/package.json",
54
+ "bun-lockfiles": "bun.lock",
55
+ }
56
+ CARGO = {
57
+ "cargo-manifests": "backend/Cargo.toml",
58
+ "cargo-lockfiles": "backend/Cargo.lock",
59
+ }
60
+
61
+ ENACT_FILES = {
62
+ "README.md": "# repo\n",
63
+ "package.json": "{}\n",
64
+ "bun.lock": "lock\n",
65
+ "apps/web/package.json": "{}\n",
66
+ "apps/server/package.json": "{}\n",
67
+ "packages/x/package.json": "{}\n",
68
+ }
69
+ IN_SCOPE = 5 # package.json, bun.lock, and the three nested package.json files
70
+
71
+
72
+ def write(root: Path, files: Mapping[str, str]) -> tuple[str, ...]:
73
+ for relative, body in files.items():
74
+ target = root / relative
75
+ target.parent.mkdir(parents=True, exist_ok=True)
76
+ target.write_text(body, encoding="utf-8")
77
+ return tuple(sorted(files))
78
+
79
+
80
+ def grade(
81
+ root: Path,
82
+ files: tuple[str, ...],
83
+ *,
84
+ trees: Mapping[str, str],
85
+ declarations: Mapping[str, str],
86
+ given: tuple[str, ...] | None = None,
87
+ exhaustive: bool = True,
88
+ **kwargs: object,
89
+ ) -> Verdict:
90
+ """The real dispatch path. With `given`, a NARROWED run over the complete
91
+ `files` universe; without it, the hook's shape (the whole tree, exhaustive)."""
92
+ return runner.run(
93
+ ROW,
94
+ root,
95
+ files if given is None else given,
96
+ exhaustive=exhaustive and given is None,
97
+ universe=files,
98
+ declarations=declarations,
99
+ trees=trees,
100
+ declared_trees=trees,
101
+ **kwargs, # type: ignore[arg-type]
102
+ )
103
+
104
+
105
+ # ------------------------------------------------------------ the enact-combined shape
106
+
107
+
108
+ def test_undeclared_the_enact_shape_is_still_could_not_run_on_a_whole_tree(tmp_path: Path) -> None:
109
+ """The default is unchanged: no declaration, no new behaviour. This is the
110
+ verdict that was measured live, kept as the pin that declaring nothing does
111
+ not move it."""
112
+ files = write(tmp_path, ENACT_FILES)
113
+ verdict = grade(tmp_path, files, trees=ENACT_TREES, declarations={})
114
+ assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
115
+ assert "engine.scope matches no file on disk" in verdict.message
116
+
117
+
118
+ def test_declared_the_enact_shape_is_clean_on_a_whole_tree(tmp_path: Path) -> None:
119
+ files = write(tmp_path, ENACT_FILES)
120
+ verdict = grade(tmp_path, files, trees=ENACT_TREES, declarations=BUN)
121
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
122
+ assert verdict.units == IN_SCOPE
123
+
124
+
125
+ def test_declared_the_enact_shape_is_clean_on_a_narrowed_run(tmp_path: Path) -> None:
126
+ """One changed manifest selects the gate, and the gate is graded over every
127
+ declared path in the complete universe."""
128
+ files = write(tmp_path, ENACT_FILES)
129
+ verdict = grade(
130
+ tmp_path, files, trees=ENACT_TREES, declarations=BUN, given=("apps/web/package.json",)
131
+ )
132
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
133
+ assert verdict.units == IN_SCOPE
134
+
135
+
136
+ def test_declared_a_changed_lockfile_alone_selects_the_gate(tmp_path: Path) -> None:
137
+ files = write(tmp_path, ENACT_FILES)
138
+ verdict = grade(tmp_path, files, trees=ENACT_TREES, declarations=BUN, given=("bun.lock",))
139
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
140
+ assert verdict.units == IN_SCOPE
141
+
142
+
143
+ def test_declared_a_change_outside_every_declared_path_does_not_select_the_gate(
144
+ tmp_path: Path,
145
+ ) -> None:
146
+ """Selection follows the declaration. With nothing declared changed, the gate
147
+ does not apply: the missing lockfile below is not probed on this run."""
148
+ files = write(tmp_path, {**ENACT_FILES, "bun.lock": ""})
149
+ verdict = grade(tmp_path, files, trees=ENACT_TREES, declarations=BUN, given=("README.md",))
150
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
151
+ assert verdict.findings == ()
152
+
153
+
154
+ def test_declared_the_lockfile_input_is_not_consulted(tmp_path: Path) -> None:
155
+ """The declaration drives the grade: a per-gate `bun_lock` input pointing at
156
+ nothing cannot fail a repository whose declared lockfile is present."""
157
+ files = write(tmp_path, ENACT_FILES)
158
+ verdict = grade(
159
+ tmp_path,
160
+ files,
161
+ trees=ENACT_TREES,
162
+ declarations=BUN,
163
+ inputs={"REP-002": {"bun_lock": tmp_path / "nowhere" / "bun.lock"}},
164
+ )
165
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
166
+
167
+
168
+ # ----------------------------------------------------------------- the lockfile grade
169
+
170
+
171
+ @pytest.mark.parametrize("body", [None, ""], ids=["missing", "empty"])
172
+ def test_declared_a_missing_or_empty_lockfile_is_the_same_finding(
173
+ tmp_path: Path, body: str | None
174
+ ) -> None:
175
+ files = dict(ENACT_FILES)
176
+ if body is None:
177
+ del files["bun.lock"]
178
+ else:
179
+ files["bun.lock"] = body
180
+ verdict = grade(tmp_path, write(tmp_path, files), trees=ENACT_TREES, declarations=BUN)
181
+ assert verdict.outcome is Outcome.FINDINGS, verdict.render()
182
+ [finding] = verdict.findings
183
+ assert finding.rule == "LCK-001"
184
+ assert finding.path == "bun.lock"
185
+ assert finding.message == "missing bun.lock"
186
+ assert "(cd . && bun install)" in finding.fix
187
+
188
+
189
+ def test_declared_and_undeclared_report_a_missing_lockfile_identically(tmp_path: Path) -> None:
190
+ """Same corpus, graded once through the tree default and once through the
191
+ declaration (with the tree pointed elsewhere, so only the declaration can
192
+ reach the file): the finding is equal, field for field."""
193
+ files = write(tmp_path, {"frontend/package.json": "{}\n", "frontend/bun.lock": ""})
194
+ undeclared = grade(
195
+ tmp_path, files, trees={"rust": "", "typescript": "frontend"}, declarations={}
196
+ )
197
+ declared = grade(
198
+ tmp_path,
199
+ files,
200
+ trees={"rust": "", "typescript": "elsewhere"},
201
+ declarations={"bun-manifests": "frontend/package.json", "bun-lockfiles": "frontend/bun.lock"},
202
+ )
203
+ assert undeclared.outcome is Outcome.FINDINGS, undeclared.render()
204
+ assert declared.findings == undeclared.findings
205
+
206
+
207
+ def test_declared_two_present_lockfiles_are_clean(tmp_path: Path) -> None:
208
+ declarations = {
209
+ "bun-manifests": "package.json,infra/docker/opencode-stack/package.json",
210
+ "bun-lockfiles": "bun.lock,infra/docker/opencode-stack/bun.lock",
211
+ }
212
+ both = {
213
+ **ENACT_FILES,
214
+ "infra/docker/opencode-stack/package.json": "{}\n",
215
+ "infra/docker/opencode-stack/bun.lock": "lock\n",
216
+ }
217
+ verdict = grade(tmp_path, write(tmp_path, both), trees=ENACT_TREES, declarations=declarations)
218
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
219
+
220
+
221
+ def test_declared_the_second_lockfile_missing_is_reported_by_its_own_path(tmp_path: Path) -> None:
222
+ declarations = {
223
+ "bun-manifests": "package.json,infra/docker/opencode-stack/package.json",
224
+ "bun-lockfiles": "bun.lock,infra/docker/opencode-stack/bun.lock",
225
+ }
226
+ files = {**ENACT_FILES, "infra/docker/opencode-stack/package.json": "{}\n"}
227
+ verdict = grade(tmp_path, write(tmp_path, files), trees=ENACT_TREES, declarations=declarations)
228
+ [finding] = verdict.findings
229
+ assert finding.path == "infra/docker/opencode-stack/bun.lock"
230
+ assert finding.message == "missing infra/docker/opencode-stack/bun.lock"
231
+ assert "(cd infra/docker/opencode-stack && bun install)" in finding.fix
232
+
233
+
234
+ def test_declared_both_lockfiles_missing_reports_only_the_first(tmp_path: Path) -> None:
235
+ """check.sh's shape: the first problem ends the run."""
236
+ declarations = {
237
+ "bun-manifests": "package.json,infra/docker/opencode-stack/package.json",
238
+ "bun-lockfiles": "bun.lock,infra/docker/opencode-stack/bun.lock",
239
+ }
240
+ files = {k: v for k, v in ENACT_FILES.items() if k != "bun.lock"}
241
+ files["infra/docker/opencode-stack/package.json"] = "{}\n"
242
+ verdict = grade(tmp_path, write(tmp_path, files), trees=ENACT_TREES, declarations=declarations)
243
+ assert [f.path for f in verdict.findings] == ["bun.lock"]
244
+
245
+
246
+ # ------------------------------------------------------ a declared glob that matches nothing
247
+
248
+
249
+ @pytest.mark.parametrize("narrowed", [False, True], ids=["whole tree", "narrowed run"])
250
+ @pytest.mark.parametrize("dead", ["tools/*/package.json", "nope/package.json"])
251
+ def test_a_declared_manifest_matching_no_file_is_could_not_run_naming_it(
252
+ tmp_path: Path, dead: str, narrowed: bool
253
+ ) -> None:
254
+ files = write(tmp_path, ENACT_FILES)
255
+ declarations = {**BUN, "bun-manifests": f"package.json,{dead}"}
256
+ verdict = grade(
257
+ tmp_path,
258
+ files,
259
+ trees=ENACT_TREES,
260
+ declarations=declarations,
261
+ given=("package.json",) if narrowed else None,
262
+ )
263
+ assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
264
+ assert "`bun-manifests`" in verdict.message
265
+ assert dead in verdict.message
266
+
267
+
268
+ def test_a_dead_declaration_is_loud_even_though_other_declared_paths_match(
269
+ tmp_path: Path,
270
+ ) -> None:
271
+ """The scope is non-empty (the live entries match), so the runner's own
272
+ empty-scope guard is silent; only the per-declaration check can see it."""
273
+ files = write(tmp_path, ENACT_FILES)
274
+ verdict = grade(
275
+ tmp_path,
276
+ files,
277
+ trees=ENACT_TREES,
278
+ declarations={**BUN, "bun-manifests": "package.json,services/*/package.json"},
279
+ )
280
+ assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
281
+ assert "services/*/package.json" in verdict.message
282
+ assert "matches no file on disk" not in verdict.message
283
+
284
+
285
+ def test_the_check_itself_refuses_a_dead_declaration_given_a_complete_set(tmp_path: Path) -> None:
286
+ """Not only through the runner: a caller building a Context by hand gets the
287
+ same refusal, because the check is what knows what it was told to scan."""
288
+ ctx = Context(root=tmp_path, row=ROW, declarations=BUN | {"bun-manifests": "app/*/package.json"})
289
+ write(tmp_path, ENACT_FILES)
290
+ with pytest.raises(CouldNotRun) as raised:
291
+ tuple(LockfileIntegrity().run(ctx, FileSet(paths=tuple(sorted(ENACT_FILES)))))
292
+ assert "`bun-manifests`" in str(raised.value)
293
+ assert "app/*/package.json" in str(raised.value)
294
+
295
+
296
+ # ------------------------------------------------------------- malformed declarations
297
+
298
+
299
+ def bun(manifests: str = "package.json", lockfiles: str = "bun.lock") -> dict[str, str]:
300
+ return {"bun-manifests": manifests, "bun-lockfiles": lockfiles}
301
+
302
+
303
+ def cargo(manifests: str = "backend/Cargo.toml", lockfiles: str = "backend/Cargo.lock") -> dict[str, str]:
304
+ return {"cargo-manifests": manifests, "cargo-lockfiles": lockfiles}
305
+
306
+
307
+ MALFORMED = [
308
+ pytest.param(bun("/etc/package.json"), "bun-manifests", id="manifest absolute"),
309
+ pytest.param(bun("../x/package.json"), "bun-manifests", id="manifest leading .."),
310
+ pytest.param(bun("apps/../package.json"), "bun-manifests", id="manifest embedded .."),
311
+ pytest.param(bun("./package.json"), "bun-manifests", id="manifest leading ."),
312
+ pytest.param(bun("apps//web/package.json"), "bun-manifests", id="manifest empty segment"),
313
+ pytest.param(bun("apps\\web\\package.json"), "bun-manifests", id="manifest backslash"),
314
+ pytest.param(bun(""), "bun-manifests", id="manifest empty value"),
315
+ pytest.param(bun("package.json,"), "bun-manifests", id="manifest trailing comma"),
316
+ pytest.param(bun(",package.json"), "bun-manifests", id="manifest leading comma"),
317
+ pytest.param(bun("package.json, ,apps/*/package.json"), "bun-manifests", id="manifest blank entry"),
318
+ pytest.param(bun("package.json,package.json"), "bun-manifests", id="manifest duplicate"),
319
+ pytest.param(bun("!apps/web/package.json"), "bun-manifests", id="manifest negation"),
320
+ pytest.param(bun("<typescript>/package.json"), "bun-manifests", id="manifest placeholder"),
321
+ pytest.param(bun("apps/{web,x}/package.json"), "bun-manifests", id="manifest brace union"),
322
+ pytest.param(bun("apps/web/README.md"), "bun-manifests", id="manifest wrong file name"),
323
+ pytest.param(bun("apps/web/"), "bun-manifests", id="manifest directory"),
324
+ pytest.param(bun(lockfiles="/abs/bun.lock"), "bun-lockfiles", id="lockfile absolute"),
325
+ pytest.param(bun(lockfiles="../bun.lock"), "bun-lockfiles", id="lockfile .."),
326
+ pytest.param(bun(lockfiles="a\\bun.lock"), "bun-lockfiles", id="lockfile backslash"),
327
+ pytest.param(bun(lockfiles=""), "bun-lockfiles", id="lockfile empty value"),
328
+ pytest.param(bun(lockfiles="bun.lock,bun.lock"), "bun-lockfiles", id="lockfile duplicate"),
329
+ pytest.param(bun(lockfiles="*.lock"), "bun-lockfiles", id="lockfile glob"),
330
+ pytest.param(bun(lockfiles="apps/*/bun.lock"), "bun-lockfiles", id="lockfile nested glob"),
331
+ pytest.param(cargo("/abs/Cargo.toml"), "cargo-manifests", id="cargo manifest absolute"),
332
+ pytest.param(cargo("backend/Cargo.json"), "cargo-manifests", id="cargo manifest wrong file name"),
333
+ pytest.param(cargo(lockfiles="../Cargo.lock"), "cargo-lockfiles", id="cargo lockfile .."),
334
+ pytest.param(cargo(lockfiles="backend/*/Cargo.lock"), "cargo-lockfiles", id="cargo lockfile glob"),
335
+ pytest.param({"bun-manifests": "package.json"}, "bun-lockfiles", id="bun manifests without lockfiles"),
336
+ pytest.param({"bun-lockfiles": "bun.lock"}, "bun-manifests", id="bun lockfiles without manifests"),
337
+ pytest.param({"cargo-manifests": "backend/Cargo.toml"}, "cargo-lockfiles", id="cargo manifests alone"),
338
+ pytest.param({"cargo-lockfiles": "backend/Cargo.lock"}, "cargo-manifests", id="cargo lockfiles alone"),
339
+ ]
340
+
341
+
342
+ @pytest.mark.parametrize(("declarations", "named"), MALFORMED)
343
+ def test_a_malformed_declaration_is_could_not_run_naming_it(
344
+ tmp_path: Path, declarations: dict[str, str], named: str
345
+ ) -> None:
346
+ files = write(tmp_path, ENACT_FILES)
347
+ verdict = grade(tmp_path, files, trees=ENACT_TREES, declarations=declarations)
348
+ assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
349
+ assert f"`{named}`" in verdict.message, verdict.message
350
+
351
+
352
+ @pytest.mark.parametrize(("declarations", "named"), MALFORMED)
353
+ def test_the_check_refuses_a_malformed_declaration_without_the_runner(
354
+ tmp_path: Path, declarations: dict[str, str], named: str
355
+ ) -> None:
356
+ files = write(tmp_path, ENACT_FILES)
357
+ ctx = Context(root=tmp_path, row=ROW, declarations=declarations)
358
+ with pytest.raises(CouldNotRun) as raised:
359
+ tuple(LockfileIntegrity().run(ctx, FileSet(paths=files)))
360
+ assert f"`{named}`" in str(raised.value), str(raised.value)
361
+
362
+
363
+ # ----------------------------------------------------------------- the scope hook
364
+
365
+
366
+ def test_an_absent_declaration_restates_nothing() -> None:
367
+ assert LockfileIntegrity.declared_scope(ROW.engine.scope, {}) is None
368
+ assert LockfileIntegrity.declared_scope(ROW.engine.scope, {"license": "Proprietary"}) is None
369
+
370
+
371
+ def test_a_declared_bun_half_replaces_only_the_typescript_entries() -> None:
372
+ assert LockfileIntegrity.declared_scope(ROW.engine.scope, BUN) == (
373
+ "<rust>/Cargo.toml",
374
+ "<rust>/Cargo.lock",
375
+ "package.json",
376
+ "apps/*/package.json",
377
+ "packages/*/package.json",
378
+ "bun.lock",
379
+ )
380
+
381
+
382
+ def test_a_declared_cargo_half_replaces_only_the_rust_entries() -> None:
383
+ assert LockfileIntegrity.declared_scope(ROW.engine.scope, CARGO) == (
384
+ "<typescript>/package.json",
385
+ "<typescript>/bun.lock",
386
+ "backend/Cargo.toml",
387
+ "backend/Cargo.lock",
388
+ )
389
+
390
+
391
+ def test_both_halves_declared_leave_no_tree_default() -> None:
392
+ assert LockfileIntegrity.declared_scope(ROW.engine.scope, {**BUN, **CARGO}) == (
393
+ "backend/Cargo.toml",
394
+ "backend/Cargo.lock",
395
+ "package.json",
396
+ "apps/*/package.json",
397
+ "packages/*/package.json",
398
+ "bun.lock",
399
+ )
400
+
401
+
402
+ def test_the_registry_scope_the_hook_replaces_is_two_entries_per_tree() -> None:
403
+ """The hook drops a declared half's defaults by their tree prefix, so a row
404
+ that grew an entry under a prefix would be dropped by a declaration without
405
+ anyone deciding it. This pins the shape the replacement relies on."""
406
+ scope = ROW.engine.scope
407
+ assert sorted(scope) == sorted(
408
+ ["<rust>/Cargo.toml", "<rust>/Cargo.lock", "<typescript>/package.json", "<typescript>/bun.lock"]
409
+ )
410
+
411
+
412
+ def test_both_registry_files_carry_the_same_text() -> None:
413
+ assert policy_text("gate-registry.yaml") == policy_text("gate-registry.alt.yaml")
414
+ assert load("gate-registry.alt.yaml").by_id("REP-002").engine.scope == ROW.engine.scope
415
+
416
+
417
+ # ------------------------------------------------------------------- the Cargo half
418
+
419
+
420
+ @requires_cargo
421
+ def test_declared_cargo_paths_are_graded_instead_of_the_rust_tree(tmp_path: Path) -> None:
422
+ shutil.copytree(CLEAN_CORPUS, tmp_path, dirs_exist_ok=True)
423
+ files = corpus_files(tmp_path)
424
+ trees = {**fixture_trees(), "rust": "somewhere-else"}
425
+
426
+ undeclared = grade(tmp_path, files, trees=trees, declarations={})
427
+ assert [f.path for f in undeclared.findings] == ["somewhere-else/Cargo.lock"]
428
+
429
+ declared = grade(tmp_path, files, trees=trees, declarations=CARGO)
430
+ assert declared.outcome is Outcome.CLEAN, declared.render()
431
+
432
+
433
+ @requires_cargo
434
+ def test_declared_cargo_missing_lockfile_is_the_missing_finding(tmp_path: Path) -> None:
435
+ shutil.copytree(CLEAN_CORPUS, tmp_path, dirs_exist_ok=True)
436
+ (tmp_path / "backend" / "Cargo.lock").unlink()
437
+ verdict = grade(
438
+ tmp_path,
439
+ corpus_files(tmp_path),
440
+ trees={**fixture_trees(), "rust": "somewhere-else"},
441
+ declarations=CARGO,
442
+ )
443
+ [finding] = verdict.findings
444
+ assert finding.rule == "LCK-001"
445
+ assert finding.path == "backend/Cargo.lock"
446
+ assert finding.message == "missing backend/Cargo.lock"
447
+ assert "(cd backend && cargo generate-lockfile)" in finding.fix
448
+
449
+
450
+ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory(
451
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
452
+ ) -> None:
453
+ """The cargo command, not cargo's opinion, is what this pins.
454
+
455
+ `cargo metadata --locked --offline --no-deps` (the command this row has always
456
+ run, unchanged here) accepts every lock body measured -- `version = 4` alone,
457
+ garbage, `version = 99` -- so a real stale lockfile cannot be built to make it
458
+ fail. What can be asserted is what the declaration changes: the workspace is the
459
+ declared lockfile's directory (cwd), the manifest is `<that directory>/Cargo.toml`
460
+ (`--manifest-path`), and a non-zero exit is the LCK-002 finding naming both.
461
+ """
462
+ from adl_compliance.checks.repository.lockfile_integrity import check as module
463
+
464
+ write(tmp_path, {"nested/ws/Cargo.toml": "[package]\n", "nested/ws/Cargo.lock": "lock\n", "frontend/bun.lock": "lock\n"})
465
+ calls: list[tuple[list[str], Path]] = []
466
+
467
+ def fake_run(argv: list[str], *, cwd: Path, **_: object) -> object:
468
+ calls.append((argv, cwd))
469
+ return type("Done", (), {"returncode": 0 if argv[1] == "--version" else 101})()
470
+
471
+ monkeypatch.setattr(module.shutil, "which", lambda name: "/fake/cargo")
472
+ monkeypatch.setattr(module.subprocess, "run", fake_run)
473
+ verdict = grade(
474
+ tmp_path,
475
+ corpus_files(tmp_path),
476
+ trees=fixture_trees(),
477
+ declarations=cargo("nested/*/Cargo.toml", "nested/ws/Cargo.lock"),
478
+ )
479
+ [finding] = verdict.findings
480
+ assert finding.rule == "LCK-002"
481
+ assert finding.path == "nested/ws/Cargo.lock"
482
+ assert finding.message == "nested/ws/Cargo.lock does not satisfy nested/ws/Cargo.toml"
483
+ assert "(cd nested/ws && cargo update)" in finding.fix
484
+ assert [cwd for _, cwd in calls] == [tmp_path / "nested" / "ws"] * 2
485
+ metadata = calls[1][0]
486
+ assert metadata[:3] == ["cargo", "metadata", "--locked"]
487
+ assert metadata[metadata.index("--manifest-path") + 1] == str(tmp_path / "nested" / "ws" / "Cargo.toml")
488
+
489
+
490
+ @requires_cargo
491
+ def test_declared_cargo_workspace_is_the_lockfiles_directory(tmp_path: Path) -> None:
492
+ """A workspace nested where no tree points: cargo is driven from the lock's
493
+ own directory, so the rust-toolchain override beside it applies."""
494
+ shutil.copytree(CLEAN_CORPUS / "backend", tmp_path / "crates" / "ws")
495
+ write(tmp_path, {"frontend/bun.lock": "lock\n"})
496
+ verdict = grade(
497
+ tmp_path,
498
+ corpus_files(tmp_path),
499
+ trees=fixture_trees(),
500
+ declarations=cargo("crates/*/Cargo.toml", "crates/ws/Cargo.lock"),
501
+ )
502
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
503
+
504
+
505
+ @requires_cargo
506
+ def test_an_undeclared_half_keeps_its_tree_defaults_beside_a_declared_one(tmp_path: Path) -> None:
507
+ """Bun declared, Cargo not: the Rust tree still supplies the Cargo half."""
508
+ shutil.copytree(CLEAN_CORPUS, tmp_path, dirs_exist_ok=True)
509
+ write(tmp_path, ENACT_FILES)
510
+ files = corpus_files(tmp_path)
511
+ trees = {"rust": "backend", "typescript": ENACT_TREES["typescript"]}
512
+
513
+ clean = grade(tmp_path, files, trees=trees, declarations=BUN)
514
+ assert clean.outcome is Outcome.CLEAN, clean.render()
515
+ assert clean.units == IN_SCOPE + 2 # + backend/Cargo.toml and backend/Cargo.lock
516
+
517
+ (tmp_path / "bun.lock").unlink()
518
+ broken = grade(tmp_path, corpus_files(tmp_path), trees=trees, declarations=BUN)
519
+ assert [f.path for f in broken.findings] == ["bun.lock"]
520
+
521
+ (tmp_path / "backend" / "Cargo.lock").unlink()
522
+ both = grade(tmp_path, corpus_files(tmp_path), trees=trees, declarations=BUN)
523
+ assert [f.path for f in both.findings] == ["backend/Cargo.lock"], "the Cargo half is reported first, as before"