@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
@@ -1,4 +1,4 @@
1
- """REP-004 — ported from `compliance_new/checks/repository/no-banned-commands/test.sh`.
1
+ """REP-004 and REP-006 — ported from `compliance_new/checks/repository/no-banned-commands/test.sh`.
2
2
 
3
3
  One corpus, nine files, one property apiece — see `fixtures/no-banned-commands/`.
4
4
  Two sets over it: the WHOLE corpus, handed straight to `graded()` (bypassing the
@@ -13,16 +13,19 @@ from __future__ import annotations
13
13
  import pathlib
14
14
 
15
15
  from adl_compliance import runner
16
- from adl_compliance.checks.repository.banned_commands import BannedCommands
16
+ from adl_compliance.checks.repository.legacy_search import LegacySearch
17
+ from adl_compliance.checks.repository.vcs_in_scripts import VcsInScripts
17
18
  from adl_compliance.core.context import Context, FileSet
19
+ from adl_compliance.core.scripts import is_shell_script
18
20
  from adl_compliance.core.verdict import Outcome, decide
19
21
  from fixture_trees import fixture_trees
20
22
  from adl_compliance.policy.registry import load
21
23
 
22
24
  CORPUS = pathlib.Path(__file__).parent / "fixtures" / "no-banned-commands"
23
- ROW = load().by_id("REP-004")
25
+ SEARCH_ROW = load().by_id("REP-004")
26
+ VCS_ROW = load().by_id("REP-006")
24
27
 
25
- # The row's real scope is `<compliance>/**`. The fixture's own subdirectory is
28
+ # The row's scope is repo-wide (PBI 599). The fixture's own subdirectory is
26
29
  # named to match whatever that resolves to TODAY (see the sibling directory on
27
30
  # disk) — resolved here rather than spelled, so a future remap fails this test
28
31
  # loudly (file not found) instead of silently narrowing to nothing.
@@ -42,76 +45,106 @@ WHOLE = (
42
45
  NARROWED = tuple(p for p in WHOLE if p.startswith(f"{COMPLIANCE}/"))
43
46
 
44
47
 
45
- def graded_directly(paths: tuple[str, ...]):
48
+ def graded_directly(check, row, paths: tuple[str, ...]):
46
49
  """Bypasses row-scope narrowing entirely — the check's OWN filtering only.
47
50
  Mirrors the shell test's direct `check.sh` invocation, which is how it
48
51
  proved check.sh performs no location filtering of its own."""
49
- ctx = Context(root=CORPUS, row=ROW)
50
- findings = tuple(BannedCommands().graded(ctx, FileSet(paths=paths)))
51
- return decide(ROW, findings, 0, ())
52
+ ctx = Context(root=CORPUS, row=row)
53
+ findings = tuple(check.graded(ctx, FileSet(paths=paths)))
54
+ return decide(row, findings, 0, ())
52
55
 
53
56
 
54
57
  def test_the_whole_set_is_graded_with_no_location_filtering() -> None:
55
- """Both elsewhere/ files are in the set, so both must be reported —
56
- including the pair a hand-rolled path filter inside check.py would have
57
- suppressed."""
58
- verdict = graded_directly(WHOLE)
59
- assert verdict.outcome is Outcome.FINDINGS
60
- paths = {f.path for f in verdict.findings}
61
- assert "elsewhere/loose-git.sh" in paths
62
- assert "elsewhere/loose-grep.sh" in paths
63
- assert sum(f.rule == "GIT-001" for f in verdict.findings) == 2
64
- assert sum(f.rule == "GRP-001" for f in verdict.findings) == 3
65
-
66
-
67
- def test_the_narrowed_set_excludes_elsewhere_by_scope_not_by_the_check() -> None:
68
- """The row's real `<compliance>/**` scope, through `runner.run()` — what
69
- production actually hands the check."""
70
- verdict = runner.run(ROW, CORPUS, NARROWED)
71
- assert verdict.outcome is Outcome.FINDINGS
72
- paths = {f.path for f in verdict.findings}
73
- assert "elsewhere/loose-git.sh" not in paths
74
- assert "elsewhere/loose-grep.sh" not in paths
75
- assert f"{COMPLIANCE}/checks/git-bad.sh" in paths
76
- assert f"{COMPLIANCE}/checks/grep-bad.sh" in paths
77
- assert f"{COMPLIANCE}/checks/grep-trailing.sh" in paths
58
+ """PBI 605: each gate reports only its own rule, including the pair a path filter in check.py would hide."""
59
+ vcs = graded_directly(VcsInScripts(), VCS_ROW, WHOLE)
60
+ assert vcs.outcome is Outcome.FINDINGS
61
+ assert {f.path for f in vcs.findings} >= {"elsewhere/loose-git.sh"}
62
+ assert "elsewhere/loose-grep.sh" not in {f.path for f in vcs.findings}
63
+ assert {f.rule for f in vcs.findings} == {"GIT-001"}
64
+ assert len(vcs.findings) == 2
65
+
66
+ search = graded_directly(LegacySearch(), SEARCH_ROW, WHOLE)
67
+ assert search.outcome is Outcome.FINDINGS
68
+ assert {f.path for f in search.findings} >= {"elsewhere/loose-grep.sh"}
69
+ assert "elsewhere/loose-git.sh" not in {f.path for f in search.findings}
70
+ assert {f.rule for f in search.findings} == {"GRP-001"}
71
+ assert len(search.findings) == 3
72
+
73
+
74
+ def test_the_two_gates_are_distinct_rows_with_their_own_addresses() -> None:
75
+ """PBI 605: REP-004 is the legacy-search gate (GRP-001); REP-006 is the version-control gate (GIT-001)."""
76
+ registry = load()
77
+ assert registry.resolve("repository.no-legacy-search").id == "REP-004"
78
+ assert registry.resolve("repository.no-vcs-in-scripts").id == "REP-006"
79
+ assert LegacySearch.id == "REP-004" and LegacySearch.rule == "GRP-001"
80
+ assert VcsInScripts.id == "REP-006" and VcsInScripts.rule == "GIT-001"
81
+ assert "repository.no-banned-commands" not in {row.address for row in registry.rows}
82
+
83
+
84
+ def test_the_two_rows_share_one_repo_wide_scope_but_for_the_managed_projections() -> None:
85
+ """REP-006 alone leaves the adl-managed projections out (see `test_scope.py`); the rest is shared."""
86
+ exempt = [entry for entry in VCS_ROW.engine.scope if entry not in SEARCH_ROW.engine.scope]
87
+ assert exempt and all(entry.startswith("!.") for entry in exempt)
88
+ assert [e for e in SEARCH_ROW.engine.scope if e not in VCS_ROW.engine.scope] == []
89
+
90
+
91
+ def test_the_row_scope_is_repo_wide_so_files_outside_compliance_are_graded() -> None:
92
+ """PBI 599/601/605: the row is repo-wide, so the `elsewhere/` scripts are reported, one assertion set per gate."""
93
+ search = runner.run(SEARCH_ROW, CORPUS, WHOLE)
94
+ assert search.outcome is Outcome.FINDINGS
95
+ search_paths = {f.path for f in search.findings}
96
+ assert "elsewhere/loose-grep.sh" in search_paths
97
+ assert f"{COMPLIANCE}/checks/grep-bad.sh" in search_paths
98
+ assert f"{COMPLIANCE}/checks/grep-trailing.sh" in search_paths
99
+
100
+ vcs = runner.run(VCS_ROW, CORPUS, WHOLE)
101
+ assert vcs.outcome is Outcome.FINDINGS
102
+ vcs_paths = {f.path for f in vcs.findings}
103
+ assert "elsewhere/loose-git.sh" in vcs_paths
104
+ assert f"{COMPLIANCE}/checks/git-bad.sh" in vcs_paths
78
105
 
79
106
 
80
107
  def test_a_whole_line_comment_naming_the_word_is_not_a_violation() -> None:
81
- verdict = runner.run(ROW, CORPUS, NARROWED)
82
- paths = {f.path for f in verdict.findings}
108
+ paths = {f.path for f in runner.run(VCS_ROW, CORPUS, NARROWED).findings}
83
109
  assert f"{COMPLIANCE}/checks/git-note.sh" not in paths
110
+ paths = {f.path for f in runner.run(SEARCH_ROW, CORPUS, NARROWED).findings}
84
111
  assert f"{COMPLIANCE}/checks/grep-note.sh" not in paths
85
112
 
86
113
 
87
114
  def test_prose_and_a_clean_script_are_not_graded() -> None:
88
- verdict = runner.run(ROW, CORPUS, NARROWED)
89
- paths = {f.path for f in verdict.findings}
90
- assert f"{COMPLIANCE}/checks/notes.md" not in paths
91
- assert f"{COMPLIANCE}/checks/good.sh" not in paths
115
+ for row in (SEARCH_ROW, VCS_ROW):
116
+ paths = {f.path for f in runner.run(row, CORPUS, NARROWED).findings}
117
+ assert f"{COMPLIANCE}/checks/notes.md" not in paths
118
+ assert f"{COMPLIANCE}/checks/good.sh" not in paths
92
119
 
93
120
 
94
121
  def test_no_set_is_could_not_run() -> None:
95
- verdict = runner.run(ROW, CORPUS, (), exhaustive=True)
96
- assert verdict.outcome is Outcome.COULD_NOT_RUN
122
+ for row in (SEARCH_ROW, VCS_ROW):
123
+ verdict = runner.run(row, CORPUS, (), exhaustive=True)
124
+ assert verdict.outcome is Outcome.COULD_NOT_RUN
97
125
 
98
126
 
99
127
  def test_this_gate_does_not_report_itself() -> None:
100
128
  """A check that forbids a string necessarily contains it. Both words are
101
129
  assembled from fragments, so neither appears in the source as one token."""
102
- source = pathlib.Path(__file__).parent / "check.py"
103
- body = source.read_text(encoding="utf-8")
104
- for entry in BannedCommands().banned():
105
- assert entry.word not in body, f"{entry.word!r} appears literally in {source.name}"
130
+ here = pathlib.Path(__file__).parent
131
+ sources = (
132
+ here / "base.py",
133
+ here.parent / "legacy_search" / "check.py",
134
+ here.parent / "vcs_in_scripts" / "check.py",
135
+ )
136
+ for source in sources:
137
+ body = source.read_text(encoding="utf-8")
138
+ for check in (LegacySearch(), VcsInScripts()):
139
+ assert check.WORD not in body, f"{check.WORD!r} appears literally in {source.name}"
106
140
 
107
141
 
108
142
  def test_the_assembled_words_are_still_the_right_ones() -> None:
109
- words = {entry.word for entry in BannedCommands().banned()}
110
- assert words == {"g" + "it", "g" + "rep"}
143
+ assert VcsInScripts.WORD == "g" + "it"
144
+ assert LegacySearch.WORD == "g" + "rep"
111
145
 
112
146
 
113
147
  def test_a_justfile_is_graded_by_name_not_suffix() -> None:
114
- check = BannedCommands()
115
- assert check._is_shell("compliance/justfile")
116
- assert check._is_shell("nested/dir/justfile")
117
- assert not check._is_shell("compliance/justfile.md")
148
+ assert is_shell_script("compliance/justfile")
149
+ assert is_shell_script("nested/dir/justfile")
150
+ assert not is_shell_script("compliance/justfile.md")
@@ -0,0 +1,71 @@
1
+ """REP-004 and REP-006 grade the shell scripts REP-007 selects; REP-006 leaves the managed `.workflows/` projection alone."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from adl_compliance import runner
10
+ from adl_compliance.core.verdict import Outcome, Verdict
11
+ from adl_compliance.policy.registry import load
12
+
13
+ SEARCH = load().by_id("REP-004")
14
+ VCS = load().by_id("REP-006")
15
+ GIT_CALL = "gi" + "t status\n"
16
+ GREP_CALL = "gr" + "ep -r x .\n"
17
+
18
+
19
+ def grade(row, tmp_path: Path, files: dict[str, str]) -> Verdict:
20
+ for rel, body in files.items():
21
+ target = tmp_path / rel
22
+ target.parent.mkdir(parents=True, exist_ok=True)
23
+ target.write_text(body, encoding="utf-8")
24
+ return runner.run(row, tmp_path, tuple(sorted(files)), exhaustive=True)
25
+
26
+
27
+ @pytest.mark.parametrize(
28
+ "name",
29
+ ["bin/tool", ".husky/pre-commit", "x.just", ".justfile", "Justfile", "bin/zsh-tool"],
30
+ )
31
+ def test_an_unsuffixed_or_alternately_named_script_is_graded(tmp_path: Path, name: str) -> None:
32
+ shebang = "#!/bin/zsh\n" if name.endswith("zsh-tool") else "#!/bin/sh\n"
33
+ body = "" if "just" in name.lower() else shebang
34
+ verdict = grade(VCS, tmp_path, {name: body + GIT_CALL})
35
+ assert verdict.outcome is Outcome.FINDINGS, verdict.render()
36
+ assert [f.path for f in verdict.findings] == [name]
37
+
38
+
39
+ def test_a_shebang_script_is_graded_by_rep_004_too(tmp_path: Path) -> None:
40
+ verdict = grade(SEARCH, tmp_path, {"bin/tool": "#!/usr/bin/env bash\n" + GREP_CALL})
41
+ assert [f.rule for f in verdict.findings] == ["GRP-001"]
42
+
43
+
44
+ def test_a_non_shell_extensionless_file_is_not_graded(tmp_path: Path) -> None:
45
+ files = {"bin/py": "#!/usr/bin/env python3\n" + GIT_CALL, "bin/plain": GIT_CALL, "scripts/ok.sh": "echo hi\n"}
46
+ verdict = grade(VCS, tmp_path, files)
47
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
48
+
49
+
50
+ PROJECTIONS = (".workflows", ".agents", ".claude", ".codex", ".cursor", ".kimi-code", ".opencode")
51
+
52
+
53
+ @pytest.mark.parametrize("root", PROJECTIONS)
54
+ def test_the_vcs_gate_exempts_each_managed_projection(tmp_path: Path, root: str) -> None:
55
+ clean = grade(VCS, tmp_path / "a", {f"{root}/hooks/x.sh": GIT_CALL, "scripts/ok.sh": "echo hi\n"})
56
+ assert clean.outcome is Outcome.CLEAN, clean.render()
57
+ flagged = grade(VCS, tmp_path / "b", {"scripts/x.sh": GIT_CALL, f"{root}/hooks/x.sh": GIT_CALL})
58
+ assert [f.path for f in flagged.findings] == ["scripts/x.sh"]
59
+
60
+
61
+ @pytest.mark.parametrize("root", PROJECTIONS)
62
+ def test_the_legacy_search_gate_still_grades_each_managed_projection(tmp_path: Path, root: str) -> None:
63
+ verdict = grade(SEARCH, tmp_path, {f"{root}/hooks/x.sh": GREP_CALL})
64
+ assert [f.path for f in verdict.findings] == [f"{root}/hooks/x.sh"]
65
+
66
+
67
+ @pytest.mark.parametrize("root", PROJECTIONS)
68
+ def test_the_multiline_comment_gate_still_grades_each_managed_projection(tmp_path: Path, root: str) -> None:
69
+ row = load().by_id("REP-007")
70
+ verdict = grade(row, tmp_path, {f"{root}/hooks/x.sh": "# one\n# two\n"})
71
+ assert [f.path for f in verdict.findings] == [f"{root}/hooks/x.sh"]
@@ -0,0 +1,7 @@
1
+ """The `repository.no-legacy-search` gate (REP-004, GRP-001). Scanner: `..banned_commands.base`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .check import LegacySearch
6
+
7
+ __all__ = ["LegacySearch"]
@@ -0,0 +1,15 @@
1
+ """REP-004 (GRP-001) on the shared `banned_commands/base.py` scanner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from ..banned_commands.base import BannedCommand
8
+
9
+
10
+ class LegacySearch(BannedCommand):
11
+ id: ClassVar[str] = "REP-004"
12
+ rule: ClassVar[str] = "GRP-001"
13
+ WORD: ClassVar[str] = "gr" + "ep"
14
+ MESSAGE: ClassVar[str] = "standalone legacy search command is forbidden; use rg"
15
+ FIX: ClassVar[str] = "replace this invocation with an equivalent rg call"
@@ -0,0 +1,32 @@
1
+ """REP-004 (GRP-001) reports its own rule and no other."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pathlib
6
+
7
+ from adl_compliance import runner
8
+ from adl_compliance.core.verdict import Outcome
9
+ from adl_compliance.policy.registry import load
10
+
11
+ CORPUS = (
12
+ pathlib.Path(__file__).parent.parent / "banned_commands" / "fixtures" / "no-banned-commands"
13
+ )
14
+ ROW = load().by_id("REP-004")
15
+
16
+
17
+ def _whole() -> tuple[str, ...]:
18
+ return tuple(
19
+ sorted(p.relative_to(CORPUS).as_posix() for p in CORPUS.rglob("*") if p.is_file())
20
+ )
21
+
22
+
23
+ def test_reports_only_its_own_rule() -> None:
24
+ verdict = runner.run(ROW, CORPUS, _whole())
25
+ assert verdict.outcome is Outcome.FINDINGS
26
+ assert {f.rule for f in verdict.findings} == {"GRP-001"}
27
+ assert len(verdict.findings) == 3
28
+ assert "loose-grep.sh" in {pathlib.PurePosixPath(f.path).name for f in verdict.findings}
29
+
30
+
31
+ def test_is_addressable_by_its_registry_name() -> None:
32
+ assert load().resolve("repository.no-legacy-search").id == "REP-004"
@@ -67,7 +67,7 @@ specification that is neither a string nor a table, `workspace = true` with no
67
67
  * A path dependency that is NOT a workspace member (any path dependency of a manifest
68
68
  with no `[workspace]`, or one outside the workspace root) is judged by its own
69
69
  presence in the lock only; its own dependencies are not read, so a dependency added
70
- to such a crate (enact-combined's `plugins/enact-keychain` is one) is not detected.
70
+ to such a crate (a repository's `plugins/keychain` crate is one) is not detected.
71
71
  Reading them would need that crate's own workspace for its `workspace = true`
72
72
  entries and cargo's feature rules for its optional ones (an optional dependency of a
73
73
  non-member is locked only when a feature activates it), and a wrong guess is a false
@@ -1,37 +1,23 @@
1
- """REP-002 — backend/frontend lockfiles must exist and Cargo must accept the
2
- backend lock without updates.
1
+ """REP-002 — lockfiles must exist and Cargo must accept its lock without updates.
3
2
 
4
3
  Ported from `compliance_new/checks/repository/lockfile-integrity/check.sh`.
5
- Entirely `structural`: check.sh opens three FIXED, NAMED paths
6
- (`backend/Cargo.lock`, `frontend/bun.lock`, `backend/Cargo.toml`) and never
7
- consults the given file set — the row's `engine.scope` decides whether this
8
- gate runs at all, not which of these three paths it reads.
9
-
10
- **Fixed 2026-09-15: these three paths, plus the workspace directory, resolve
11
- through `inputs` now — `<rust>` and `<typescript>` placeholders, exactly like
12
- `coverage.backend-ratchet`'s `current`/`config` inputs — instead of the
13
- hardcoded `ClassVar` literals (`backend`, `backend/Cargo.lock`,
14
- `backend/Cargo.toml`, `frontend/bun.lock`) this check carried until then.**
15
- Those literals bypassed tree resolution entirely (K-42): measured for real on
16
- enact-vps-m5, which declares no `rust` tree (it has none — no Cargo.toml
17
- anywhere in that checkout), `engine.scope` correctly matched under a declared
18
- `<typescript>` workspace's `package.json`, and this check then unconditionally
19
- reported `LCK-001: missing backend/Cargo.lock` — a literal path that
20
- repository never named and has no Rust code at. A row whose `engine.scope` is
21
- a union of more than one tree (this one: `<rust>` OR `<typescript>`) can run
22
- because ONE tree's scope matched, while a check that then hardcodes the
23
- OTHER tree's default path grades a repository against a path it never
24
- declared. `<rust>` resolves to `backend` and `<typescript>` resolves to
25
- `frontend` in the packaged `policy/config.toml` — those are still the
26
- defaults, just reached through `ctx.inputs` now, the same as every other
27
- check that names a consumer path.
28
-
29
- A tree declared absent (`rust = ""` or `typescript = ""` in
30
- `[controls.compliance.trees]`) drops the inputs naming it, and each half of this
31
- row is then graded on its own: a repository declaring no Rust tree is graded on
32
- its TypeScript lockfile alone and is never asked for a `Cargo.lock`. With
33
- neither tree left the row is could-not-run (see `structural`) -- it used to raise
34
- `KeyError`.
4
+ Entirely `structural`: it opens NAMED paths and never consults the given file
5
+ set — the row's `engine.scope` decides whether this gate runs at all, not which
6
+ paths it reads.
7
+
8
+ **What it grades (Tarun, 2026-10-08, PBI 598).** The `rust` and `typescript`
9
+ trees are deleted; this check no longer resolves any tree. It grades:
10
+
11
+ * the REPOSITORY-ROOT pairs, automatically: `Cargo.toml` -> `Cargo.lock` and
12
+ `package.json` -> `bun.lock`. A root manifest that is present requires its
13
+ lockfile; a root manifest that is absent requires nothing;
14
+ * every OTHER lockfile only when the repository declares it under
15
+ `[controls.compliance.lockfiles]` (see below). A nested manifest nobody
16
+ declared is not graded: the engine does not guess where a repository keeps
17
+ its workspaces.
18
+
19
+ With neither a root manifest nor a declaration there is nothing to grade, and the
20
+ row is clean (`empty_scope_is_clean`; Tarun, 2026-10-08).
35
21
 
36
22
  Retired 2026-10-01: `coverage.backend-ratchet` and `coverage.backend-crate`,
37
23
  named here as precedents, were deleted with the `coverage` domain
@@ -104,16 +90,11 @@ declared by its manifests list and its lockfiles list together:
104
90
  bun-manifests / bun-lockfiles `package.json` paths or globs / `bun.lock` paths
105
91
  cargo-manifests / cargo-lockfiles `Cargo.toml` paths or globs / `Cargo.lock` paths
106
92
 
107
- Why: the registry scope is `<typescript>/package.json` and `<typescript>/bun.lock`
108
- (and the `<rust>` pair), a tree placeholder joined to a ONE-level file name.
109
- enact-combined's `typescript` tree is a brace union of directories and its
110
- manifests sit at the repository ROOT and under `apps/*/` and `packages/*/`, so on
111
- a whole-tree run `engine.scope` matched no file and the row was could-not-run
112
- (measured live: a narrowed run passed, the exhaustive run could not). The
113
- per-gate `bun_lock` input moved the grade and never the scope.
93
+ Why: the registry scope is the four root file names, so a workspace that keeps
94
+ its manifests under `apps/*/` or `packages/*/` is invisible to it. The declaration
95
+ states those paths.
114
96
 
115
- A declared half REPLACES that half's tree defaults, in both places the paths are
116
- used:
97
+ A declared half ADDS to the root pair, in both places the paths are used:
117
98
 
118
99
  * scope (`declared_scope`): which changed files select the gate, and, because a
119
100
  restated scope is graded whole, which files `graded` is handed;
@@ -127,8 +108,7 @@ at least one file of the complete in-scope set, or the row is could-not-run
127
108
  naming the declaration and the entry. A declared glob that matches nothing would
128
109
  otherwise shrink what the gate scans to nothing and say OK.
129
110
 
130
- An undeclared half is exactly what it was: tree defaults, the `inputs`, the same
131
- early-return shape. A malformed declaration (empty, absolute, backslash, `.`/`..`
111
+ An undeclared half is exactly the root pair, with the same early-return shape. A malformed declaration (empty, absolute, backslash, `.`/`..`
132
112
  segments, placeholder or brace or negation syntax, duplicate, the wrong file
133
113
  name, a glob in a lockfile entry, or a half with only one of its two lists) is
134
114
  could-not-run naming the declaration; the engine never grades against a guess at
@@ -160,8 +140,8 @@ class Half(NamedTuple):
160
140
  """The `--declare` name carrying the literal lockfile paths."""
161
141
  manifest_name: str
162
142
  """The file name every manifest entry must end in."""
163
- tree: str
164
- """Prefix of the registry scope entries a declaration of this half replaces."""
143
+ lockfile_name: str
144
+ """The file name of this half's lockfile, beside the manifest at the root."""
165
145
 
166
146
 
167
147
  class Declared(NamedTuple):
@@ -172,22 +152,12 @@ class Declared(NamedTuple):
172
152
  class LockfileIntegrity(Check):
173
153
  id: ClassVar[str] = "REP-002"
174
154
 
175
- CARGO: ClassVar[Half] = Half("cargo-manifests", "cargo-lockfiles", "Cargo.toml", "<rust>/")
176
- BUN: ClassVar[Half] = Half("bun-manifests", "bun-lockfiles", "package.json", "<typescript>/")
155
+ CARGO: ClassVar[Half] = Half("cargo-manifests", "cargo-lockfiles", "Cargo.toml", "Cargo.lock")
156
+ BUN: ClassVar[Half] = Half("bun-manifests", "bun-lockfiles", "package.json", "bun.lock")
177
157
  # Cargo first, as the registry lists it and as `structural` grades it.
178
158
  HALVES: ClassVar[tuple[Half, ...]] = (CARGO, BUN)
179
159
 
180
- inputs: ClassVar[dict[str, str]] = {
181
- "workspace": "<rust>",
182
- "cargo_lock": "<rust>/Cargo.lock",
183
- "cargo_manifest": "<rust>/Cargo.toml",
184
- "bun_lock": "<typescript>/bun.lock",
185
- }
186
- """Named, declared, and resolved through the SAME tree map every scope
187
- pattern uses (K-42) — the fix for the hardcoded-literal bug this class's
188
- docstring describes. A repository declaring `rust = "rust-workspace"` (or
189
- no `rust` tree at all) is graded against that declaration, never the
190
- packaged `backend`/`frontend` defaults baked into Python."""
160
+ empty_scope_is_clean: ClassVar[bool] = True
191
161
 
192
162
  MISSING: ClassVar[str] = "LCK-001"
193
163
  STALE: ClassVar[str] = "LCK-002"
@@ -281,12 +251,12 @@ class LockfileIntegrity(Check):
281
251
  def declared_scope(
282
252
  cls, scope: tuple[str, ...], declarations: Mapping[str, str]
283
253
  ) -> tuple[str, ...] | None:
284
- """The registry scope with each declared half's tree defaults replaced.
254
+ """The registry scope (the root pairs) plus every declared manifest and lockfile.
285
255
 
286
- A half's defaults are the registry entries under its tree prefix
287
- (`<rust>/...`, `<typescript>/...`); `test_the_registry_scope_the_hook_
288
- replaces_is_two_entries_per_tree` pins that shape. The other half, and
289
- anything else the row lists, is kept as it was.
256
+ A declaration ADDS to the root pair: the root `Cargo.toml` and
257
+ `package.json` are graded whether or not a repository also declares other
258
+ workspaces. The scope is restated only when something is declared, and a
259
+ restated scope is graded whole (`Check.declared_scope`).
290
260
  """
291
261
  restated = scope
292
262
  any_declared = False
@@ -295,77 +265,39 @@ class LockfileIntegrity(Check):
295
265
  if declared is None:
296
266
  continue
297
267
  any_declared = True
298
- restated = (
299
- *(pattern for pattern in restated if not pattern.startswith(half.tree)),
300
- *declared.manifests,
301
- *declared.lockfiles,
302
- )
268
+ restated = (*restated, *declared.manifests, *declared.lockfiles)
303
269
  return restated if any_declared else None
304
270
 
305
271
  # ------------------------------------------------------------- structural
306
272
 
307
- # The three inputs that all name the `<rust>` tree. They are resolved or
308
- # dropped together, so seeing only some of them means a per-gate
309
- # `--input REP-002:<name>=...` replaced part of the triple.
310
- CARGO_INPUTS: ClassVar[tuple[str, ...]] = ("workspace", "cargo_lock", "cargo_manifest")
311
-
312
273
  def structural(self, ctx: Context) -> Iterable[Report]:
313
- # Decision for a tree declared absent (`rust = ""` / `typescript = ""`,
314
- # which drops every input naming the tree). Unlike an exclusion path, a
315
- # lockfile IS what this row grades, so each half is judged on its own:
316
- #
317
- # * a half whose tree is declared absent is not graded -- the owner
318
- # stated there is no such lockfile to require, which is the narrower,
319
- # honest verdict `resolve_inputs` documents ("grades a smaller scope");
320
- # * with NEITHER half left there is nothing to grade, and an empty set of
321
- # findings would read as a pass -- `CouldNotRun`, naming both inputs and
322
- # trees;
323
- # * a half-resolved Cargo triple is `CouldNotRun`, naming what is missing.
324
- #
325
- # This read `ctx.inputs["cargo_lock"]` and `["bun_lock"]` unguarded: a
326
- # `KeyError` traceback (exit 1), not a verdict, whenever either tree was
327
- # declared absent.
328
- #
329
- # A half the repository DECLARES (see the module docstring) is graded from
330
- # its declaration and never from the tree or the inputs; the rules above
331
- # apply to the halves it did not declare.
332
- cargo_declared = self.declared(self.CARGO, ctx.declarations)
333
- bun_declared = self.declared(self.BUN, ctx.declarations)
334
-
335
- present_cargo = [name for name in self.CARGO_INPUTS if name in ctx.inputs]
336
- if cargo_declared is None and present_cargo and len(present_cargo) != len(self.CARGO_INPUTS):
337
- missing = [name for name in self.CARGO_INPUTS if name not in ctx.inputs]
338
- raise CouldNotRun(
339
- f"{self.id}: the Cargo inputs resolve together from <rust>, but only "
340
- f"{present_cargo} are set; missing {missing} -- declare "
341
- "[controls.compliance.trees].rust, or override all three"
342
- )
343
-
344
274
  # (lockfile, cargo workspace, manifest) per Cargo lockfile to grade.
345
275
  cargo: list[tuple[Path, Path, Path]] = []
276
+ bun: list[Path] = []
277
+
278
+ # The repository-root pairs, graded because the root manifest is there.
279
+ root_manifest = ctx.path(self.CARGO.manifest_name)
280
+ if self.state(ctx, root_manifest) is not FileState.MISSING:
281
+ cargo.append((ctx.path(self.CARGO.lockfile_name), ctx.root, root_manifest))
282
+ if self.state(ctx, ctx.path(self.BUN.manifest_name)) is not FileState.MISSING:
283
+ bun.append(ctx.path(self.BUN.lockfile_name))
284
+
285
+ # Everything else comes only from the repository's own declaration.
286
+ cargo_declared = self.declared(self.CARGO, ctx.declarations)
346
287
  if cargo_declared is not None:
347
288
  for relative in cargo_declared.lockfiles:
348
289
  lock = ctx.path(relative)
349
- cargo.append((lock, lock.parent, lock.parent / "Cargo.toml"))
350
- elif "cargo_lock" in ctx.inputs:
351
- cargo.append(
352
- (ctx.inputs["cargo_lock"], ctx.inputs["workspace"], ctx.inputs["cargo_manifest"])
353
- )
354
-
355
- bun: list[Path] = []
290
+ if all(lock != known for known, _, _ in cargo):
291
+ cargo.append((lock, lock.parent, lock.parent / "Cargo.toml"))
292
+ bun_declared = self.declared(self.BUN, ctx.declarations)
356
293
  if bun_declared is not None:
357
- bun = [ctx.path(relative) for relative in bun_declared.lockfiles]
358
- elif "bun_lock" in ctx.inputs:
359
- bun = [ctx.inputs["bun_lock"]]
294
+ for relative in bun_declared.lockfiles:
295
+ lock = ctx.path(relative)
296
+ if lock not in bun:
297
+ bun.append(lock)
360
298
 
361
299
  if not cargo and not bun:
362
- raise CouldNotRun(
363
- f"{self.id}: no lockfile to grade -- `cargo_lock` (<rust>/Cargo.lock) and "
364
- "`bun_lock` (<typescript>/bun.lock) are both absent because the trees "
365
- "<rust> and <typescript> are both declared absent in "
366
- "[controls.compliance.trees]; a row that grades nothing is could-not-run, "
367
- "never a clean pass"
368
- )
300
+ return
369
301
 
370
302
  for lock, workspace, _manifest in cargo:
371
303
  if self.state(ctx, lock) is not FileState.PRESENT:
@@ -382,18 +314,18 @@ class LockfileIntegrity(Check):
382
314
  for lock in bun:
383
315
  if self.state(ctx, lock) is not FileState.PRESENT:
384
316
  shown = ctx.relative(lock)
385
- typescript_dir = shown.rsplit("/", 1)[0] if "/" in shown else "."
317
+ bun_dir = shown.rsplit("/", 1)[0] if "/" in shown else "."
386
318
  yield self.report(
387
319
  shown,
388
320
  f"missing {shown}",
389
- f"run: (cd {typescript_dir} && bun install) to generate the "
321
+ f"run: (cd {bun_dir} && bun install) to generate the "
390
322
  "lockfile and commit the result",
391
323
  rule=self.MISSING,
392
324
  )
393
325
  return
394
326
 
395
327
  if not cargo:
396
- # Rust declared absent: the TypeScript lockfile above was the whole row.
328
+ # No Cargo lockfile to grade: the Bun lockfiles above were the whole row.
397
329
  return
398
330
 
399
331
  if shutil.which("cargo") is None:
@@ -0,0 +1,7 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "fixture"
7
+ version = "0.1.0"
@@ -0,0 +1,3 @@
1
+ [toolchain]
2
+ channel = "1.96.1"
3
+ components = ["rustfmt", "clippy", "rust-analyzer", "llvm-tools-preview"]
@@ -0,0 +1,7 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "fixture"
7
+ version = "0.1.0"