@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.29 → 0.1.32

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 (201) hide show
  1. package/bin/adl +2 -2
  2. package/bin/health-check +2 -2
  3. package/bin/reconcile +41 -21
  4. package/bin/setup +2 -1
  5. package/package.json +1 -1
  6. package/packages/agent-hooks/README.md +77 -8
  7. package/packages/agent-hooks/assets/opencode-plugin.template.ts +2 -6
  8. package/packages/agent-hooks/src/cli/args.ts +2 -2
  9. package/packages/agent-hooks/src/cli/doctor.ts +2 -1
  10. package/packages/agent-hooks/src/cli/rules.ts +2 -1
  11. package/packages/agent-hooks/src/deploy/opencode-template.ts +5 -3
  12. package/packages/agent-hooks/src/gating/config.ts +11 -0
  13. package/packages/agent-hooks/src/gating/running-adl.ts +24 -0
  14. package/packages/agent-hooks/src/hooks/crg/background.ts +7 -4
  15. package/packages/agent-hooks/src/hooks/log.ts +12 -12
  16. package/packages/agent-hooks/src/hooks/user-prompt-submit/handler.ts +2 -2
  17. package/packages/agent-hooks/src/log/writer.ts +170 -24
  18. package/packages/agent-hooks/src/main.ts +11 -4
  19. package/packages/agent-hooks/src/pre-tool-use/handler.ts +5 -4
  20. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +1 -1
  21. package/packages/agent-hooks/src/rules/shell/invocation.ts +2 -1
  22. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +24 -2
  23. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +19 -45
  24. package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +9 -0
  25. package/packages/agent-skills/assets/diagnosing-bugs/scripts/hitl-loop.template.sh +1 -12
  26. package/packages/azdo/src/azdo-wit.ts +8 -2
  27. package/packages/azdo/src/mcp-server.ts +1 -1
  28. package/packages/fleet/manifest.json +2 -1
  29. package/packages/fleet/src/repo-maintain.ts +5 -2
  30. package/packages/repo-controls/AGENTS.md +7 -4
  31. package/packages/repo-controls/README.md +346 -56
  32. package/packages/repo-controls/assets/GLOBAL-INVARIANTS.md +21 -0
  33. package/packages/repo-controls/assets/promote.ts +7 -85
  34. package/packages/repo-controls/assets/reconcile-guard.sh +1 -14
  35. package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +25 -0
  36. package/packages/repo-controls/assets/scaffold/docs/CLAUDE.md +1 -0
  37. package/packages/repo-controls/assets/scaffold/docs/decisions/index.md +17 -0
  38. package/packages/repo-controls/assets/scaffold/docs/notes.md +20 -0
  39. package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +28 -0
  40. package/packages/repo-controls/assets/scaffold/plans/CLAUDE.md +1 -0
  41. package/packages/repo-controls/assets/scaffold/plans/GAPS-AND-FINDINGS.md +12 -0
  42. package/packages/repo-controls/assets/scaffold/plans/LEDGER.md +8 -0
  43. package/packages/repo-controls/assets/scaffold/plans/features/.gitkeep +0 -0
  44. package/packages/repo-controls/assets/scaffold/root/AGENTS.md +22 -0
  45. package/packages/repo-controls/assets/scaffold/root/CLAUDE.md +1 -0
  46. package/packages/repo-controls/assets/scaffold/workflows/AGENTS.md +16 -0
  47. package/packages/repo-controls/assets/scaffold/workflows/CLAUDE.md +1 -0
  48. package/packages/repo-controls/assets/scaffold/workflows/pipelines/.gitkeep +0 -0
  49. package/packages/repo-controls/assets/scaffold/workflows/scripts/.gitkeep +0 -0
  50. package/packages/repo-controls/assets/version-bump.sh +7 -48
  51. package/packages/repo-controls/package/adl-compliance/AGENTS.md +25 -7
  52. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +6 -4
  53. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/check.py +1 -1
  54. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +1 -1
  55. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +7 -42
  56. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/excluded/web/i18n/locales/zh-CN/strings.ts +1 -0
  57. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +10 -44
  58. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +1 -1
  59. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +1 -1
  60. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +1 -1
  61. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/check.py +1 -7
  62. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/test_check.py +12 -3
  63. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +15 -23
  64. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +0 -136
  65. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +18 -0
  66. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +2 -2
  67. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +0 -7
  68. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/check.py +2 -4
  69. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/stable-delivery.md +10 -0
  70. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/stable-recommendation.md +12 -0
  71. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/target-narration.md +10 -0
  72. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +6 -7
  73. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/test_check.py +3 -3
  74. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +10 -12
  75. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +1 -1
  76. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/web/src/renderer/solo/item.ts +1 -0
  77. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/web/src/a.ts +1 -0
  78. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/web/src/b.ts +1 -0
  79. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +1 -1
  80. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/fresh/fresh.ts +1 -0
  81. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub1/only.ts +1 -0
  82. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub10/only.ts +1 -0
  83. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub11/only.ts +1 -0
  84. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub2/only.ts +1 -0
  85. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub3/only.ts +1 -0
  86. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub4/only.ts +1 -0
  87. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub5/only.ts +1 -0
  88. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub6/only.ts +1 -0
  89. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub7/only.ts +1 -0
  90. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub8/only.ts +1 -0
  91. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub9/only.ts +1 -0
  92. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/services/i18n/locales/de/common.json +1 -0
  93. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/solo/item.ts +1 -0
  94. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/vendored-crate/Cargo.toml +2 -0
  95. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +1 -1
  96. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/web/src/renderer/solo/item.ts +1 -0
  97. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +1 -1
  98. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/web/src/a.ts +1 -0
  99. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/web/src/b.ts +1 -0
  100. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/web/src/c.ts +1 -0
  101. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +2 -2
  102. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/web/src/renderer/solo/item.ts +1 -0
  103. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +1 -1
  104. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/fresh/fresh.ts +1 -0
  105. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub1/only.ts +1 -0
  106. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub10/only.ts +1 -0
  107. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub11/only.ts +1 -0
  108. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub2/only.ts +1 -0
  109. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub3/only.ts +1 -0
  110. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub4/only.ts +1 -0
  111. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub5/only.ts +1 -0
  112. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub6/only.ts +1 -0
  113. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub7/only.ts +1 -0
  114. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub8/only.ts +1 -0
  115. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub9/only.ts +1 -0
  116. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/solo/item.ts +1 -0
  117. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/web/src/renderer/solo/item.ts +1 -0
  118. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +46 -54
  119. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +16 -76
  120. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +35 -32
  121. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +46 -213
  122. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/README.md +1 -0
  123. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +76 -194
  124. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +18 -2
  125. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +49 -0
  126. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +1 -1
  127. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/guides/links.md +20 -0
  128. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/guides/route-target.md +8 -0
  129. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/guides/links.md +20 -0
  130. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/guides/route-target.md +8 -0
  131. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/test_check.py +4 -4
  132. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +3 -3
  133. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.notes/declared.txt +1 -0
  134. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +8 -1
  135. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +15 -135
  136. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +98 -50
  137. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +1 -1
  138. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +1 -1
  139. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +1 -1
  140. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +14 -10
  141. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/__init__.py +3 -10
  142. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/base.py +42 -0
  143. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_check.py +82 -49
  144. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_scope.py +71 -0
  145. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/__init__.py +7 -0
  146. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/check.py +15 -0
  147. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/test_check.py +32 -0
  148. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +1 -1
  149. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +56 -124
  150. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/Cargo.lock +7 -0
  151. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/Cargo.toml +4 -0
  152. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/bun.lock +1 -0
  153. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/package.json +1 -0
  154. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/rust-toolchain.toml +3 -0
  155. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/src/main.rs +1 -0
  156. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/Cargo.lock +7 -0
  157. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/Cargo.toml +4 -0
  158. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/bun.lock +0 -0
  159. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/package.json +1 -0
  160. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/rust-toolchain.toml +3 -0
  161. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/src/main.rs +1 -0
  162. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +8 -37
  163. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +95 -130
  164. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_root_lockfiles.py +101 -0
  165. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +25 -13
  166. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/__init__.py +7 -0
  167. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/check.py +124 -0
  168. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/fixtures/valid-register.yaml +4 -0
  169. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/scanner.py +217 -0
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/test_check.py +391 -0
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/__init__.py +7 -0
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/check.py +16 -0
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/test_check.py +32 -0
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +12 -1
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/ast_grep.py +2 -3
  176. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +3 -0
  177. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +1 -1
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/declarations.py +24 -0
  179. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/receipt.py +2 -2
  180. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/scripts.py +120 -0
  181. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -1
  182. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.toml +4 -4
  183. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +93 -99
  184. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +93 -99
  185. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
  186. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +6 -47
  187. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +54 -8
  188. package/packages/repo-controls/scripts/hooks/pre-commit +12 -2
  189. package/packages/repo-controls/scripts/hooks/pre-push +38 -20
  190. package/packages/repo-controls/src/cli.ts +22 -3
  191. package/packages/repo-controls/src/compliance-config.ts +132 -65
  192. package/packages/repo-controls/src/config.ts +55 -46
  193. package/packages/repo-controls/src/constants.ts +9 -0
  194. package/packages/repo-controls/src/coverage-ratchet.ts +19 -4
  195. package/packages/repo-controls/src/doctor.ts +20 -30
  196. package/packages/repo-controls/src/findings-ratchet.ts +14 -0
  197. package/packages/repo-controls/src/gitignore-block.ts +3 -13
  198. package/packages/repo-controls/src/hook-log.ts +7 -10
  199. package/packages/repo-controls/src/hook.ts +127 -83
  200. package/packages/repo-controls/src/install-compliance.ts +112 -0
  201. package/packages/repo-controls/src/install.ts +133 -30
@@ -0,0 +1,3 @@
1
+ [toolchain]
2
+ channel = "1.96.1"
3
+ components = ["rustfmt", "clippy", "rust-analyzer", "llvm-tools-preview"]
@@ -5,9 +5,9 @@ test.sh builds its fixture inline (`mktemp`, `printf`, a `cp` of the real
5
5
  `backend/rust-toolchain.toml`, a real `cargo generate-lockfile --offline`) and
6
6
  asserts exactly two things:
7
7
 
8
- 1. the built fixture — a valid `backend/Cargo.toml`/`Cargo.lock` pair plus a
9
- non-empty `frontend/bun.lock` — passes (`... | rg -F 'OK'`).
10
- 2. emptying `frontend/bun.lock` afterwards makes the gate fail.
8
+ 1. the built fixture — a valid root `Cargo.toml`/`Cargo.lock` pair plus a
9
+ non-empty root `bun.lock` beside a `package.json` — passes (`... | rg -F 'OK'`).
10
+ 2. emptying `bun.lock` afterwards makes the gate fail.
11
11
 
12
12
  Ported here as two COMMITTED fixture directories built the identical way (see
13
13
  `fixtures/clean/` and `fixtures/empty-bun-lock/`, and the git history of this
@@ -26,7 +26,6 @@ scenario test.sh does not have.
26
26
  from __future__ import annotations
27
27
 
28
28
  import shutil
29
- from pathlib import Path
30
29
 
31
30
  import pytest
32
31
  from helpers import corpus, corpus_files
@@ -34,7 +33,7 @@ from helpers import corpus, corpus_files
34
33
  from adl_compliance import runner
35
34
  from adl_compliance.checks.repository.lockfile_integrity import LockfileIntegrity
36
35
  from adl_compliance.core.verdict import Outcome
37
- from adl_compliance.policy.registry import effective_trees, load
36
+ from adl_compliance.policy.registry import load
38
37
 
39
38
  FIXTURES = corpus("repository", "lockfile-integrity")
40
39
  ROW = load().by_id("REP-002")
@@ -66,46 +65,18 @@ def test_the_row_identifies_this_check() -> None:
66
65
 
67
66
 
68
67
  def test_clean_corpus_passes() -> None:
69
- # test.sh: `bash "$gate" --root "$fixture" | rg -F 'OK'` — a real
70
- # backend/Cargo.toml + Cargo.lock pair cargo itself generated, plus a
71
- # non-empty frontend/bun.lock.
68
+ # test.sh: `bash "$gate" --root "$fixture" | rg -F 'OK'` on a cargo-generated root pair plus a root bun.lock.
72
69
  verdict = run("clean")
73
70
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
74
71
  assert verdict.findings == ()
75
72
 
76
73
 
77
- def test_empty_frontend_lockfile_is_reported() -> None:
74
+ def test_empty_bun_lockfile_is_reported() -> None:
78
75
  # test.sh: `: > "$fixture/frontend/bun.lock"` then asserts the gate fails.
79
76
  verdict = run("empty-bun-lock")
80
77
  assert verdict.outcome is Outcome.FINDINGS, verdict.render()
81
78
  assert len(verdict.findings) == 1
82
79
  finding = verdict.findings[0]
83
80
  assert finding.rule == "LCK-001"
84
- assert finding.path == "frontend/bun.lock"
85
- assert finding.message == "missing frontend/bun.lock"
86
-
87
-
88
- def test_a_redeclared_rust_tree_is_honoured(tmp_path: Path) -> None:
89
- """The bug this pins: `WORKSPACE`/`CARGO_LOCK`/`CARGO_MANIFEST`/`BUN_LOCK`
90
- used to be hardcoded `ClassVar`s (`"backend"`, `"backend/Cargo.lock"`,
91
- `"backend/Cargo.toml"`, `"frontend/bun.lock"`) that bypassed tree
92
- resolution entirely, unlike every other check's `inputs` mapping (K-42).
93
-
94
- Measured for real on enact-vps-m5 (no `rust` tree at all — no Cargo.toml
95
- anywhere in that checkout): once `<typescript>` scope matched a declared
96
- workspace's `package.json`, this check still unconditionally reported
97
- `LCK-001: missing backend/Cargo.lock` against a literal path that
98
- repository never named and has no Rust code at. A repository that
99
- declares `rust = "rust-workspace"` (and has no `backend/` at all) must be
100
- graded against `rust-workspace/Cargo.lock`, never the packaged default.
101
- """
102
- trees = effective_trees({"rust": "rust-workspace", "typescript": "ts-workspace"})
103
- # The subject here is an EMPTY tree: the lockfile is reported missing because
104
- # nothing exists, so the empty corpus is opted into explicitly.
105
- verdict = runner.run(ROW, tmp_path, corpus_files(tmp_path, allow_empty=True), trees=trees)
106
- assert verdict.outcome is Outcome.FINDINGS, verdict.render()
107
- assert len(verdict.findings) == 1
108
- finding = verdict.findings[0]
109
- assert finding.rule == "LCK-001"
110
- assert finding.path == "rust-workspace/Cargo.lock"
111
- assert finding.message == "missing rust-workspace/Cargo.lock"
81
+ assert finding.path == "bun.lock"
82
+ assert finding.message == "missing bun.lock"
@@ -7,13 +7,12 @@ and a lockfiles list together:
7
7
  bun-manifests / bun-lockfiles `package.json` paths or globs / `bun.lock` paths
8
8
  cargo-manifests / cargo-lockfiles `Cargo.toml` paths or globs / `Cargo.lock` paths
9
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.
10
+ Why this exists: a workspace keeps its manifests at the repository ROOT and under
11
+ `apps/*/` and `packages/*/`. The registry scope is the four root file names, so a
12
+ nested manifest is invisible to it unless the repository declares it. Since
13
+ PBI 598 (Tarun, 2026-10-08) the declaration ADDS to the root pair: it extends the
14
+ scope (which files select the gate) and the grade (which lockfiles must exist)
15
+ from the same lists.
17
16
 
18
17
  This file is separate from `test_check.py` on purpose: that module carries a
19
18
  module-level cargo skip and an autouse patch of `runner.lookup`, and nothing in it
@@ -27,7 +26,6 @@ from collections.abc import Mapping
27
26
  from pathlib import Path
28
27
 
29
28
  import pytest
30
- from fixture_trees import fixture_trees
31
29
  from helpers import corpus, corpus_files
32
30
 
33
31
  from adl_compliance import runner
@@ -42,13 +40,9 @@ CLEAN_CORPUS = corpus("repository", "lockfile-integrity") / "clean"
42
40
 
43
41
  requires_cargo = pytest.mark.skipif(shutil.which("cargo") is None, reason="cargo is not installed")
44
42
 
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).
43
+ # No trees: REP-002 resolves none (PBI 598).
44
+ NO_TREES: dict[str, str] = {}
45
+ # The declaration a repository with nested workspaces writes.
52
46
  BUN = {
53
47
  "bun-manifests": "package.json,apps/*/package.json,packages/*/package.json",
54
48
  "bun-lockfiles": "bun.lock",
@@ -58,7 +52,7 @@ CARGO = {
58
52
  "cargo-lockfiles": "backend/Cargo.lock",
59
53
  }
60
54
 
61
- ENACT_FILES = {
55
+ WORKSPACE_FILES = {
62
56
  "README.md": "# repo\n",
63
57
  "package.json": "{}\n",
64
58
  "bun.lock": "lock\n",
@@ -102,40 +96,38 @@ def grade(
102
96
  )
103
97
 
104
98
 
105
- # ------------------------------------------------------------ the enact-combined shape
99
+ # ------------------------------------------------------------ the nested-workspace shape
106
100
 
107
101
 
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
102
+ def test_undeclared_nested_manifests_are_not_graded_on_a_whole_tree(tmp_path: Path) -> None:
103
+ """Declaring nothing grades the root pair alone; nested package.json files are not asked for a lockfile."""
104
+ files = write(tmp_path, WORKSPACE_FILES)
105
+ verdict = grade(tmp_path, files, trees=NO_TREES, declarations={})
106
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
107
+ assert verdict.units == 2 # package.json and bun.lock at the root
116
108
 
117
109
 
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)
110
+ def test_declared_the_nested_workspace_shape_is_clean_on_a_whole_tree(tmp_path: Path) -> None:
111
+ files = write(tmp_path, WORKSPACE_FILES)
112
+ verdict = grade(tmp_path, files, trees=NO_TREES, declarations=BUN)
121
113
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
122
114
  assert verdict.units == IN_SCOPE
123
115
 
124
116
 
125
- def test_declared_the_enact_shape_is_clean_on_a_narrowed_run(tmp_path: Path) -> None:
117
+ def test_declared_the_nested_workspace_shape_is_clean_on_a_narrowed_run(tmp_path: Path) -> None:
126
118
  """One changed manifest selects the gate, and the gate is graded over every
127
119
  declared path in the complete universe."""
128
- files = write(tmp_path, ENACT_FILES)
120
+ files = write(tmp_path, WORKSPACE_FILES)
129
121
  verdict = grade(
130
- tmp_path, files, trees=ENACT_TREES, declarations=BUN, given=("apps/web/package.json",)
122
+ tmp_path, files, trees=NO_TREES, declarations=BUN, given=("apps/web/package.json",)
131
123
  )
132
124
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
133
125
  assert verdict.units == IN_SCOPE
134
126
 
135
127
 
136
128
  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",))
129
+ files = write(tmp_path, WORKSPACE_FILES)
130
+ verdict = grade(tmp_path, files, trees=NO_TREES, declarations=BUN, given=("bun.lock",))
139
131
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
140
132
  assert verdict.units == IN_SCOPE
141
133
 
@@ -145,26 +137,12 @@ def test_declared_a_change_outside_every_declared_path_does_not_select_the_gate(
145
137
  ) -> None:
146
138
  """Selection follows the declaration. With nothing declared changed, the gate
147
139
  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",))
140
+ files = write(tmp_path, {**WORKSPACE_FILES, "bun.lock": ""})
141
+ verdict = grade(tmp_path, files, trees=NO_TREES, declarations=BUN, given=("README.md",))
150
142
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
151
143
  assert verdict.findings == ()
152
144
 
153
145
 
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
146
  # ----------------------------------------------------------------- the lockfile grade
169
147
 
170
148
 
@@ -172,12 +150,12 @@ def test_declared_the_lockfile_input_is_not_consulted(tmp_path: Path) -> None:
172
150
  def test_declared_a_missing_or_empty_lockfile_is_the_same_finding(
173
151
  tmp_path: Path, body: str | None
174
152
  ) -> None:
175
- files = dict(ENACT_FILES)
153
+ files = dict(WORKSPACE_FILES)
176
154
  if body is None:
177
155
  del files["bun.lock"]
178
156
  else:
179
157
  files["bun.lock"] = body
180
- verdict = grade(tmp_path, write(tmp_path, files), trees=ENACT_TREES, declarations=BUN)
158
+ verdict = grade(tmp_path, write(tmp_path, files), trees=NO_TREES, declarations=BUN)
181
159
  assert verdict.outcome is Outcome.FINDINGS, verdict.render()
182
160
  [finding] = verdict.findings
183
161
  assert finding.rule == "LCK-001"
@@ -187,18 +165,12 @@ def test_declared_a_missing_or_empty_lockfile_is_the_same_finding(
187
165
 
188
166
 
189
167
  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
- )
168
+ """The automatic root pair and an explicit declaration of the same files give equal findings."""
169
+ files = write(tmp_path, {"package.json": "{}\n", "bun.lock": ""})
170
+ undeclared = grade(tmp_path, files, trees=NO_TREES, declarations={})
197
171
  declared = grade(
198
- tmp_path,
199
- files,
200
- trees={"rust": "", "typescript": "elsewhere"},
201
- declarations={"bun-manifests": "frontend/package.json", "bun-lockfiles": "frontend/bun.lock"},
172
+ tmp_path, files, trees=NO_TREES,
173
+ declarations={"bun-manifests": "package.json", "bun-lockfiles": "bun.lock"},
202
174
  )
203
175
  assert undeclared.outcome is Outcome.FINDINGS, undeclared.render()
204
176
  assert declared.findings == undeclared.findings
@@ -206,40 +178,40 @@ def test_declared_and_undeclared_report_a_missing_lockfile_identically(tmp_path:
206
178
 
207
179
  def test_declared_two_present_lockfiles_are_clean(tmp_path: Path) -> None:
208
180
  declarations = {
209
- "bun-manifests": "package.json,infra/docker/opencode-stack/package.json",
210
- "bun-lockfiles": "bun.lock,infra/docker/opencode-stack/bun.lock",
181
+ "bun-manifests": "package.json,services/web/package.json",
182
+ "bun-lockfiles": "bun.lock,services/web/bun.lock",
211
183
  }
212
184
  both = {
213
- **ENACT_FILES,
214
- "infra/docker/opencode-stack/package.json": "{}\n",
215
- "infra/docker/opencode-stack/bun.lock": "lock\n",
185
+ **WORKSPACE_FILES,
186
+ "services/web/package.json": "{}\n",
187
+ "services/web/bun.lock": "lock\n",
216
188
  }
217
- verdict = grade(tmp_path, write(tmp_path, both), trees=ENACT_TREES, declarations=declarations)
189
+ verdict = grade(tmp_path, write(tmp_path, both), trees=NO_TREES, declarations=declarations)
218
190
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
219
191
 
220
192
 
221
193
  def test_declared_the_second_lockfile_missing_is_reported_by_its_own_path(tmp_path: Path) -> None:
222
194
  declarations = {
223
- "bun-manifests": "package.json,infra/docker/opencode-stack/package.json",
224
- "bun-lockfiles": "bun.lock,infra/docker/opencode-stack/bun.lock",
195
+ "bun-manifests": "package.json,services/web/package.json",
196
+ "bun-lockfiles": "bun.lock,services/web/bun.lock",
225
197
  }
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)
198
+ files = {**WORKSPACE_FILES, "services/web/package.json": "{}\n"}
199
+ verdict = grade(tmp_path, write(tmp_path, files), trees=NO_TREES, declarations=declarations)
228
200
  [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
201
+ assert finding.path == "services/web/bun.lock"
202
+ assert finding.message == "missing services/web/bun.lock"
203
+ assert "(cd services/web && bun install)" in finding.fix
232
204
 
233
205
 
234
206
  def test_declared_both_lockfiles_missing_reports_only_the_first(tmp_path: Path) -> None:
235
207
  """check.sh's shape: the first problem ends the run."""
236
208
  declarations = {
237
- "bun-manifests": "package.json,infra/docker/opencode-stack/package.json",
238
- "bun-lockfiles": "bun.lock,infra/docker/opencode-stack/bun.lock",
209
+ "bun-manifests": "package.json,services/web/package.json",
210
+ "bun-lockfiles": "bun.lock,services/web/bun.lock",
239
211
  }
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)
212
+ files = {k: v for k, v in WORKSPACE_FILES.items() if k != "bun.lock"}
213
+ files["services/web/package.json"] = "{}\n"
214
+ verdict = grade(tmp_path, write(tmp_path, files), trees=NO_TREES, declarations=declarations)
243
215
  assert [f.path for f in verdict.findings] == ["bun.lock"]
244
216
 
245
217
 
@@ -251,12 +223,12 @@ def test_declared_both_lockfiles_missing_reports_only_the_first(tmp_path: Path)
251
223
  def test_a_declared_manifest_matching_no_file_is_could_not_run_naming_it(
252
224
  tmp_path: Path, dead: str, narrowed: bool
253
225
  ) -> None:
254
- files = write(tmp_path, ENACT_FILES)
226
+ files = write(tmp_path, WORKSPACE_FILES)
255
227
  declarations = {**BUN, "bun-manifests": f"package.json,{dead}"}
256
228
  verdict = grade(
257
229
  tmp_path,
258
230
  files,
259
- trees=ENACT_TREES,
231
+ trees=NO_TREES,
260
232
  declarations=declarations,
261
233
  given=("package.json",) if narrowed else None,
262
234
  )
@@ -270,11 +242,11 @@ def test_a_dead_declaration_is_loud_even_though_other_declared_paths_match(
270
242
  ) -> None:
271
243
  """The scope is non-empty (the live entries match), so the runner's own
272
244
  empty-scope guard is silent; only the per-declaration check can see it."""
273
- files = write(tmp_path, ENACT_FILES)
245
+ files = write(tmp_path, WORKSPACE_FILES)
274
246
  verdict = grade(
275
247
  tmp_path,
276
248
  files,
277
- trees=ENACT_TREES,
249
+ trees=NO_TREES,
278
250
  declarations={**BUN, "bun-manifests": "package.json,services/*/package.json"},
279
251
  )
280
252
  assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
@@ -286,9 +258,9 @@ def test_the_check_itself_refuses_a_dead_declaration_given_a_complete_set(tmp_pa
286
258
  """Not only through the runner: a caller building a Context by hand gets the
287
259
  same refusal, because the check is what knows what it was told to scan."""
288
260
  ctx = Context(root=tmp_path, row=ROW, declarations=BUN | {"bun-manifests": "app/*/package.json"})
289
- write(tmp_path, ENACT_FILES)
261
+ write(tmp_path, WORKSPACE_FILES)
290
262
  with pytest.raises(CouldNotRun) as raised:
291
- tuple(LockfileIntegrity().run(ctx, FileSet(paths=tuple(sorted(ENACT_FILES)))))
263
+ tuple(LockfileIntegrity().run(ctx, FileSet(paths=tuple(sorted(WORKSPACE_FILES)))))
292
264
  assert "`bun-manifests`" in str(raised.value)
293
265
  assert "app/*/package.json" in str(raised.value)
294
266
 
@@ -317,7 +289,7 @@ MALFORMED = [
317
289
  pytest.param(bun("package.json, ,apps/*/package.json"), "bun-manifests", id="manifest blank entry"),
318
290
  pytest.param(bun("package.json,package.json"), "bun-manifests", id="manifest duplicate"),
319
291
  pytest.param(bun("!apps/web/package.json"), "bun-manifests", id="manifest negation"),
320
- pytest.param(bun("<typescript>/package.json"), "bun-manifests", id="manifest placeholder"),
292
+ pytest.param(bun("<tree>/package.json"), "bun-manifests", id="manifest placeholder"),
321
293
  pytest.param(bun("apps/{web,x}/package.json"), "bun-manifests", id="manifest brace union"),
322
294
  pytest.param(bun("apps/web/README.md"), "bun-manifests", id="manifest wrong file name"),
323
295
  pytest.param(bun("apps/web/"), "bun-manifests", id="manifest directory"),
@@ -343,8 +315,8 @@ MALFORMED = [
343
315
  def test_a_malformed_declaration_is_could_not_run_naming_it(
344
316
  tmp_path: Path, declarations: dict[str, str], named: str
345
317
  ) -> None:
346
- files = write(tmp_path, ENACT_FILES)
347
- verdict = grade(tmp_path, files, trees=ENACT_TREES, declarations=declarations)
318
+ files = write(tmp_path, WORKSPACE_FILES)
319
+ verdict = grade(tmp_path, files, trees=NO_TREES, declarations=declarations)
348
320
  assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
349
321
  assert f"`{named}`" in verdict.message, verdict.message
350
322
 
@@ -353,7 +325,7 @@ def test_a_malformed_declaration_is_could_not_run_naming_it(
353
325
  def test_the_check_refuses_a_malformed_declaration_without_the_runner(
354
326
  tmp_path: Path, declarations: dict[str, str], named: str
355
327
  ) -> None:
356
- files = write(tmp_path, ENACT_FILES)
328
+ files = write(tmp_path, WORKSPACE_FILES)
357
329
  ctx = Context(root=tmp_path, row=ROW, declarations=declarations)
358
330
  with pytest.raises(CouldNotRun) as raised:
359
331
  tuple(LockfileIntegrity().run(ctx, FileSet(paths=files)))
@@ -368,10 +340,12 @@ def test_an_absent_declaration_restates_nothing() -> None:
368
340
  assert LockfileIntegrity.declared_scope(ROW.engine.scope, {"license": "Proprietary"}) is None
369
341
 
370
342
 
371
- def test_a_declared_bun_half_replaces_only_the_typescript_entries() -> None:
343
+ ROOT_SCOPE = ("Cargo.toml", "Cargo.lock", "package.json", "bun.lock")
344
+
345
+
346
+ def test_a_declared_bun_half_adds_to_the_root_pairs() -> None:
372
347
  assert LockfileIntegrity.declared_scope(ROW.engine.scope, BUN) == (
373
- "<rust>/Cargo.toml",
374
- "<rust>/Cargo.lock",
348
+ *ROOT_SCOPE,
375
349
  "package.json",
376
350
  "apps/*/package.json",
377
351
  "packages/*/package.json",
@@ -379,17 +353,17 @@ def test_a_declared_bun_half_replaces_only_the_typescript_entries() -> None:
379
353
  )
380
354
 
381
355
 
382
- def test_a_declared_cargo_half_replaces_only_the_rust_entries() -> None:
356
+ def test_a_declared_cargo_half_adds_to_the_root_pairs() -> None:
383
357
  assert LockfileIntegrity.declared_scope(ROW.engine.scope, CARGO) == (
384
- "<typescript>/package.json",
385
- "<typescript>/bun.lock",
358
+ *ROOT_SCOPE,
386
359
  "backend/Cargo.toml",
387
360
  "backend/Cargo.lock",
388
361
  )
389
362
 
390
363
 
391
- def test_both_halves_declared_leave_no_tree_default() -> None:
364
+ def test_both_halves_declared_still_keep_the_root_pairs() -> None:
392
365
  assert LockfileIntegrity.declared_scope(ROW.engine.scope, {**BUN, **CARGO}) == (
366
+ *ROOT_SCOPE,
393
367
  "backend/Cargo.toml",
394
368
  "backend/Cargo.lock",
395
369
  "package.json",
@@ -399,14 +373,9 @@ def test_both_halves_declared_leave_no_tree_default() -> None:
399
373
  )
400
374
 
401
375
 
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
- )
376
+ def test_the_registry_scope_is_the_four_root_files() -> None:
377
+ """Root-literal names with no tree placeholder: the pairs graded automatically."""
378
+ assert tuple(ROW.engine.scope) == ROOT_SCOPE
410
379
 
411
380
 
412
381
  def test_both_registry_files_carry_the_same_text() -> None:
@@ -418,26 +387,25 @@ def test_both_registry_files_carry_the_same_text() -> None:
418
387
 
419
388
 
420
389
  @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)
390
+ def test_declared_cargo_paths_are_graded_and_undeclared_nested_ones_are_not(tmp_path: Path) -> None:
391
+ shutil.copytree(CLEAN_CORPUS, tmp_path / "backend")
423
392
  files = corpus_files(tmp_path)
424
- trees = {**fixture_trees(), "rust": "somewhere-else"}
425
393
 
426
- undeclared = grade(tmp_path, files, trees=trees, declarations={})
427
- assert [f.path for f in undeclared.findings] == ["somewhere-else/Cargo.lock"]
394
+ undeclared = grade(tmp_path, files, trees=NO_TREES, declarations={})
395
+ assert undeclared.outcome is Outcome.CLEAN, undeclared.render()
428
396
 
429
- declared = grade(tmp_path, files, trees=trees, declarations=CARGO)
397
+ declared = grade(tmp_path, files, trees=NO_TREES, declarations=CARGO)
430
398
  assert declared.outcome is Outcome.CLEAN, declared.render()
431
399
 
432
400
 
433
401
  @requires_cargo
434
402
  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)
403
+ shutil.copytree(CLEAN_CORPUS, tmp_path / "backend")
436
404
  (tmp_path / "backend" / "Cargo.lock").unlink()
437
405
  verdict = grade(
438
406
  tmp_path,
439
407
  corpus_files(tmp_path),
440
- trees={**fixture_trees(), "rust": "somewhere-else"},
408
+ trees=NO_TREES,
441
409
  declarations=CARGO,
442
410
  )
443
411
  [finding] = verdict.findings
@@ -461,7 +429,7 @@ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory
461
429
  """
462
430
  from adl_compliance.checks.repository.lockfile_integrity import check as module
463
431
 
464
- write(tmp_path, {"nested/ws/Cargo.toml": "[package]\n", "nested/ws/Cargo.lock": "lock\n", "frontend/bun.lock": "lock\n"})
432
+ write(tmp_path, {"nested/ws/Cargo.toml": "[package]\n", "nested/ws/Cargo.lock": "lock\n"})
465
433
  calls: list[tuple[list[str], Path]] = []
466
434
 
467
435
  def fake_run(argv: list[str], *, cwd: Path, **_: object) -> object:
@@ -473,7 +441,7 @@ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory
473
441
  verdict = grade(
474
442
  tmp_path,
475
443
  corpus_files(tmp_path),
476
- trees=fixture_trees(),
444
+ trees=NO_TREES,
477
445
  declarations=cargo("nested/*/Cargo.toml", "nested/ws/Cargo.lock"),
478
446
  )
479
447
  [finding] = verdict.findings
@@ -489,35 +457,32 @@ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory
489
457
 
490
458
  @requires_cargo
491
459
  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"})
460
+ """A nested workspace: cargo runs from the lock's directory, so its rust-toolchain override applies."""
461
+ shutil.copytree(CLEAN_CORPUS, tmp_path / "crates" / "ws")
496
462
  verdict = grade(
497
463
  tmp_path,
498
464
  corpus_files(tmp_path),
499
- trees=fixture_trees(),
465
+ trees=NO_TREES,
500
466
  declarations=cargo("crates/*/Cargo.toml", "crates/ws/Cargo.lock"),
501
467
  )
502
468
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
503
469
 
504
470
 
505
471
  @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."""
472
+ def test_the_root_cargo_pair_is_graded_beside_a_declared_bun_half(tmp_path: Path) -> None:
473
+ """Bun declared, Cargo not: the root Cargo pair is still graded automatically."""
508
474
  shutil.copytree(CLEAN_CORPUS, tmp_path, dirs_exist_ok=True)
509
- write(tmp_path, ENACT_FILES)
475
+ write(tmp_path, WORKSPACE_FILES)
510
476
  files = corpus_files(tmp_path)
511
- trees = {"rust": "backend", "typescript": ENACT_TREES["typescript"]}
512
477
 
513
- clean = grade(tmp_path, files, trees=trees, declarations=BUN)
478
+ clean = grade(tmp_path, files, trees=NO_TREES, declarations=BUN)
514
479
  assert clean.outcome is Outcome.CLEAN, clean.render()
515
- assert clean.units == IN_SCOPE + 2 # + backend/Cargo.toml and backend/Cargo.lock
480
+ assert clean.units == IN_SCOPE + 2 # + Cargo.toml and Cargo.lock at the root
516
481
 
517
482
  (tmp_path / "bun.lock").unlink()
518
- broken = grade(tmp_path, corpus_files(tmp_path), trees=trees, declarations=BUN)
483
+ broken = grade(tmp_path, corpus_files(tmp_path), trees=NO_TREES, declarations=BUN)
519
484
  assert [f.path for f in broken.findings] == ["bun.lock"]
520
485
 
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"
486
+ (tmp_path / "Cargo.lock").unlink()
487
+ both = grade(tmp_path, corpus_files(tmp_path), trees=NO_TREES, declarations=BUN)
488
+ assert [f.path for f in both.findings] == ["Cargo.lock"], "the Cargo half is reported first, as before"