@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.30 → 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 (192) 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/deploy/opencode-template.ts +5 -3
  9. package/packages/agent-hooks/src/hooks/crg/background.ts +7 -4
  10. package/packages/agent-hooks/src/hooks/log.ts +12 -12
  11. package/packages/agent-hooks/src/log/writer.ts +170 -24
  12. package/packages/agent-hooks/src/main.ts +1 -1
  13. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +1 -1
  14. package/packages/agent-hooks/src/rules/shell/invocation.ts +2 -1
  15. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +24 -2
  16. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +19 -45
  17. package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +9 -0
  18. package/packages/agent-skills/assets/diagnosing-bugs/scripts/hitl-loop.template.sh +1 -12
  19. package/packages/fleet/manifest.json +2 -1
  20. package/packages/fleet/src/repo-maintain.ts +5 -2
  21. package/packages/repo-controls/AGENTS.md +7 -4
  22. package/packages/repo-controls/README.md +346 -56
  23. package/packages/repo-controls/assets/GLOBAL-INVARIANTS.md +21 -0
  24. package/packages/repo-controls/assets/promote.ts +7 -85
  25. package/packages/repo-controls/assets/reconcile-guard.sh +1 -14
  26. package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +25 -0
  27. package/packages/repo-controls/assets/scaffold/docs/CLAUDE.md +1 -0
  28. package/packages/repo-controls/assets/scaffold/docs/decisions/index.md +17 -0
  29. package/packages/repo-controls/assets/scaffold/docs/notes.md +20 -0
  30. package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +28 -0
  31. package/packages/repo-controls/assets/scaffold/plans/CLAUDE.md +1 -0
  32. package/packages/repo-controls/assets/scaffold/plans/GAPS-AND-FINDINGS.md +12 -0
  33. package/packages/repo-controls/assets/scaffold/plans/LEDGER.md +8 -0
  34. package/packages/repo-controls/assets/scaffold/plans/features/.gitkeep +0 -0
  35. package/packages/repo-controls/assets/scaffold/root/AGENTS.md +22 -0
  36. package/packages/repo-controls/assets/scaffold/root/CLAUDE.md +1 -0
  37. package/packages/repo-controls/assets/scaffold/workflows/AGENTS.md +16 -0
  38. package/packages/repo-controls/assets/scaffold/workflows/CLAUDE.md +1 -0
  39. package/packages/repo-controls/assets/scaffold/workflows/pipelines/.gitkeep +0 -0
  40. package/packages/repo-controls/assets/scaffold/workflows/scripts/.gitkeep +0 -0
  41. package/packages/repo-controls/assets/version-bump.sh +7 -48
  42. package/packages/repo-controls/package/adl-compliance/AGENTS.md +25 -7
  43. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +6 -4
  44. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/check.py +1 -1
  45. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +1 -1
  46. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +7 -42
  47. 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
  48. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +10 -44
  49. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +1 -1
  50. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +1 -1
  51. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +1 -1
  52. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/check.py +1 -7
  53. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/test_check.py +12 -3
  54. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +15 -23
  55. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +0 -136
  56. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +18 -0
  57. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +2 -2
  58. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +0 -7
  59. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/check.py +2 -4
  60. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/stable-delivery.md +10 -0
  61. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/stable-recommendation.md +12 -0
  62. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/target-narration.md +10 -0
  63. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +6 -7
  64. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/test_check.py +3 -3
  65. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +10 -12
  66. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +1 -1
  67. 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
  68. 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
  69. 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
  70. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +1 -1
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +1 -1
  87. 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
  88. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +1 -1
  89. 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
  90. 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
  91. 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
  92. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +2 -2
  93. 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
  94. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +1 -1
  95. 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
  96. 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
  97. 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
  98. 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
  99. 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
  100. 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
  101. 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
  102. 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
  103. 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
  104. 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
  105. 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
  106. 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
  107. 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
  108. 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
  109. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +46 -54
  110. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +16 -76
  111. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +35 -32
  112. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +46 -213
  113. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/README.md +1 -0
  114. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +76 -194
  115. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +18 -2
  116. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +49 -0
  117. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +1 -1
  118. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/guides/links.md +20 -0
  119. 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
  120. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/guides/links.md +20 -0
  121. 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
  122. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/test_check.py +4 -4
  123. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +3 -3
  124. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.notes/declared.txt +1 -0
  125. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +8 -1
  126. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +15 -135
  127. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +98 -50
  128. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +1 -1
  129. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +1 -1
  130. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +1 -1
  131. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +14 -10
  132. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/__init__.py +3 -10
  133. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/base.py +42 -0
  134. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_check.py +82 -49
  135. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_scope.py +71 -0
  136. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/__init__.py +7 -0
  137. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/check.py +15 -0
  138. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/test_check.py +32 -0
  139. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +1 -1
  140. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +56 -124
  141. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/Cargo.lock +7 -0
  142. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/Cargo.toml +4 -0
  143. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/bun.lock +1 -0
  144. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/package.json +1 -0
  145. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/rust-toolchain.toml +3 -0
  146. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/src/main.rs +1 -0
  147. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/Cargo.lock +7 -0
  148. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/Cargo.toml +4 -0
  149. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/bun.lock +0 -0
  150. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/package.json +1 -0
  151. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/rust-toolchain.toml +3 -0
  152. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/src/main.rs +1 -0
  153. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +8 -37
  154. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +95 -130
  155. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_root_lockfiles.py +101 -0
  156. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +25 -13
  157. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/__init__.py +7 -0
  158. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/check.py +124 -0
  159. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/fixtures/valid-register.yaml +4 -0
  160. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/scanner.py +217 -0
  161. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/test_check.py +391 -0
  162. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/__init__.py +7 -0
  163. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/check.py +16 -0
  164. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/test_check.py +32 -0
  165. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +12 -1
  166. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/ast_grep.py +2 -3
  167. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +3 -0
  168. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +1 -1
  169. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/declarations.py +24 -0
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/receipt.py +2 -2
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/scripts.py +120 -0
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -1
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.toml +4 -4
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +93 -99
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +93 -99
  176. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
  177. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +6 -47
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +54 -8
  179. package/packages/repo-controls/scripts/hooks/pre-commit +12 -2
  180. package/packages/repo-controls/scripts/hooks/pre-push +27 -19
  181. package/packages/repo-controls/src/cli.ts +22 -3
  182. package/packages/repo-controls/src/compliance-config.ts +132 -65
  183. package/packages/repo-controls/src/config.ts +55 -46
  184. package/packages/repo-controls/src/constants.ts +9 -0
  185. package/packages/repo-controls/src/coverage-ratchet.ts +19 -4
  186. package/packages/repo-controls/src/doctor.ts +20 -30
  187. package/packages/repo-controls/src/findings-ratchet.ts +14 -0
  188. package/packages/repo-controls/src/gitignore-block.ts +3 -13
  189. package/packages/repo-controls/src/hook-log.ts +7 -10
  190. package/packages/repo-controls/src/hook.ts +127 -83
  191. package/packages/repo-controls/src/install-compliance.ts +112 -0
  192. package/packages/repo-controls/src/install.ts +133 -30
@@ -0,0 +1,112 @@
1
+ // Post-install grading (Bug 570); see README, "Compliance seeding".
2
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { loadComplianceConfig } from './compliance-config.ts'
6
+ import { runTool } from './hook.ts'
7
+ import { which, type Env } from './runtime.ts'
8
+
9
+ interface ComplianceFinding {
10
+ message: string
11
+ /** What the engine says to do; '' when the finding carries no fix clause. */
12
+ fix: string
13
+ }
14
+
15
+ interface ComplianceGate {
16
+ id: string
17
+ address: string
18
+ /** FAIL: graded and found wrong. ERROR: could not run. */
19
+ status: 'FAIL' | 'ERROR'
20
+ summary: string
21
+ findings: ComplianceFinding[]
22
+ }
23
+
24
+ export interface InstallCompliance {
25
+ /** True only when every enabled gate PASSED. */
26
+ ok: boolean
27
+ enabled: string[]
28
+ failing: ComplianceGate[]
29
+ /** Printable lines for an agent. */
30
+ guidance: string[]
31
+ }
32
+
33
+ const ROW = /^(\S+)\s+(\S+)\s+\S\s+(FAIL|ERROR)\s*(.*)$/
34
+ const FINDING = /^\s{4}(\S.*)$/
35
+
36
+ /** Parse the engine's failures.log into its non-passing gates. */
37
+ function parseFailuresLog(text: string): ComplianceGate[] {
38
+ const gates: ComplianceGate[] = []
39
+ for (const line of text.split('\n')) {
40
+ if (line.startsWith('#') || line.trim() === '') continue
41
+ const finding = FINDING.exec(line)
42
+ if (finding !== null) {
43
+ const current = gates.at(-1)
44
+ if (current === undefined) throw new Error(`compliance failures.log has a finding before any gate row: ${line}`)
45
+ const at = finding[1]!.lastIndexOf('; fix: ')
46
+ current.findings.push(
47
+ at === -1 ? { message: finding[1]!, fix: '' } : { message: finding[1]!.slice(0, at), fix: finding[1]!.slice(at + '; fix: '.length) },
48
+ )
49
+ continue
50
+ }
51
+ const row = ROW.exec(line)
52
+ if (row === null) throw new Error(`compliance failures.log has a line this installer cannot read: ${line}`)
53
+ gates.push({ id: row[1]!, address: row[2]!, status: row[3] as 'FAIL' | 'ERROR', summary: row[4]!.trim(), findings: [] })
54
+ }
55
+ return gates
56
+ }
57
+
58
+ const SWITCH_OFF_GUIDANCE =
59
+ "To switch a gate off, remove it from [controls.compliance] enabled in adl-config.toml \u2014 that is this repository's decision, and adl-config.toml is a protected file landed by the owner."
60
+
61
+ function guidanceFor(enabled: string[], failing: ComplianceGate[]): string[] {
62
+ if (enabled.length === 0) {
63
+ return ['adl-config.toml [controls.compliance].enabled is empty: nothing is enforced. Name the gates to arm.']
64
+ }
65
+ if (failing.length === 0) return [`This repository now enforces ${enabled.length} compliance gates; all pass.`, SWITCH_OFF_GUIDANCE]
66
+ const lines = [
67
+ `This repository now enforces ${enabled.length} compliance gates; ${failing.length} failing. Fix the findings below before committing.`,
68
+ ]
69
+ for (const gate of failing) {
70
+ if (gate.status === 'ERROR') {
71
+ lines.push(`COULD-NOT-RUN ${gate.address} (${gate.id}): ${gate.summary}`)
72
+ continue
73
+ }
74
+ lines.push(`FAIL ${gate.address} (${gate.id}): ${gate.summary}`)
75
+ for (const finding of gate.findings) {
76
+ lines.push(` - ${finding.message}`)
77
+ if (finding.fix !== '') lines.push(` fix: ${finding.fix}`)
78
+ }
79
+ }
80
+ lines.push(SWITCH_OFF_GUIDANCE)
81
+ return lines
82
+ }
83
+
84
+ export class UvMissingError extends Error {}
85
+
86
+ /** Grade `repo` with the gates its `enabled` names; throws when no verdict is known, and an empty `enabled` runs nothing. */
87
+ export function gradeInstalledRepo(repo: string, env: Env): InstallCompliance {
88
+ const enabled = loadComplianceConfig(repo).enabled
89
+ if (enabled.length === 0) return { ok: true, enabled, failing: [], guidance: guidanceFor(enabled, []) }
90
+ if (which('uv', env) === null) {
91
+ throw new UvMissingError(
92
+ 'uv not found on PATH — install wired the hooks and armed the compliance gates, but cannot grade them. Install uv (https://docs.astral.sh/uv/), then run `adl repo controls compliance run --repo ' + repo + '`',
93
+ )
94
+ }
95
+ const logDir = mkdtempSync(join(tmpdir(), 'adl-install-compliance-'))
96
+ try {
97
+ const code = runTool('compliance', repo, ['--summary', '--log-dir', logDir], { env, cwd: repo, err: () => {}, quietEngine: true })
98
+ let log: string | null = null
99
+ try {
100
+ log = readFileSync(join(logDir, 'failures.log'), 'utf8')
101
+ } catch {
102
+ log = null
103
+ }
104
+ if (code === 0) return { ok: true, enabled, failing: [], guidance: guidanceFor(enabled, []) }
105
+ if (log === null) throw new Error(`the compliance engine exited ${code} without a failures.log, so no gate verdict is known`)
106
+ const failing = parseFailuresLog(log)
107
+ if (failing.length === 0) throw new Error(`the compliance engine exited ${code} but its failures.log names no gate`)
108
+ return { ok: false, enabled, failing, guidance: guidanceFor(enabled, failing) }
109
+ } finally {
110
+ rmSync(logDir, { recursive: true, force: true })
111
+ }
112
+ }
@@ -3,13 +3,16 @@
3
3
  // every install. The only preserved bytes are pre-hard-cut hook-log evidence;
4
4
  // there is no adoption, merge, or displaced-original path for managed content.
5
5
  import { createHash } from 'node:crypto'
6
- import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
6
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
7
7
  import { dirname, join } from 'node:path'
8
- import { defaultComplianceConfigTextFor, seededComplianceAttributesFor } from './compliance-config.ts'
8
+ import type { SeededAttribute } from './config.ts'
9
+ import { DEFAULT_COMPLIANCE_CONFIG_TEXT, SEEDED_COMPLIANCE_ATTRIBUTES } from './compliance-config.ts'
10
+ import { gradeInstalledRepo, type InstallCompliance } from './install-compliance.ts'
11
+ import type { Env } from './runtime.ts'
9
12
  import {
10
13
  appendToTableSpan,
11
- DEFAULT_CONFIG_TEXT,
12
14
  extractTableSpan,
15
+ integritySeed,
13
16
  isTable,
14
17
  removeTableSpan,
15
18
  removeFromTableSpan,
@@ -21,10 +24,15 @@ import {
21
24
  ADL_NPM_INSTALL,
22
25
  CONFIG_REL,
23
26
  GITIGNORE_REL,
27
+ GLOBAL_INVARIANTS_SOURCE,
28
+ GLOBAL_INVARIANTS_TARGET,
24
29
  HOOK_NAMES,
25
30
  HOOKS_PATH,
26
31
  LOCK_REL,
27
32
  MANAGED_ASSETS,
33
+ SCAFFOLD_ROOT,
34
+ SCAFFOLD_ROOT_FILES,
35
+ SCAFFOLD_TREES,
28
36
  TOOL,
29
37
  VERSION,
30
38
  WORKFLOWS_ROOT,
@@ -183,6 +191,24 @@ class Ops {
183
191
  if (mode !== undefined) chmodSync(path, mode)
184
192
  }
185
193
 
194
+ /** Create `rel` only if nothing is there; see README, "Scaffolding". */
195
+ create(rel: string, content: string | Buffer, reason: string): void {
196
+ if (this.writtenSoFar.has(rel) || lstatSync(this.abs(rel), { throwIfNoEntry: false }) !== undefined) return
197
+ this.write(rel, content, reason)
198
+ }
199
+
200
+ /** Whether anything (file, folder or link, even dangling) is at `rel`. A non-directory in the way of a parent fails loudly. */
201
+ occupied(rel: string): boolean {
202
+ const segments = rel.split('/')
203
+ for (let end = 1; end <= segments.length; end += 1) {
204
+ const current = lstatSync(this.abs(segments.slice(0, end).join('/')), { throwIfNoEntry: false })
205
+ if (current === undefined) return false
206
+ if (current.isSymbolicLink() && statSync(this.abs(segments.slice(0, end).join('/')), { throwIfNoEntry: false })?.isDirectory() === true) continue
207
+ if (!current.isDirectory()) throw new Error(`cannot scaffold ${rel}: ${segments.slice(0, end).join('/')} exists and is not a directory`)
208
+ }
209
+ return true
210
+ }
211
+
186
212
  remove(rel: string, reason: string): void {
187
213
  const path = this.abs(rel)
188
214
  // `existsSync` follows links and reports false for a dangling link. Owned
@@ -204,12 +230,18 @@ class Ops {
204
230
 
205
231
  export interface InstallOptions {
206
232
  dryRun?: boolean
233
+ /** Grade the repository once the wiring is complete (never under dryRun); see README, "Compliance seeding". */
234
+ grade?: { env: Env }
235
+ /** Re-wire only: no config seeding, no scaffolding, no grading (fleet reconcile; see README, "Re-wiring"). */
236
+ rewire?: boolean
207
237
  }
208
238
 
209
239
  export interface InstallResult {
210
240
  repo: string
211
241
  actions: Action[]
212
242
  dryRun: boolean
243
+ /** Present only when `options.grade` was given and this was a real run. */
244
+ compliance?: InstallCompliance
213
245
  }
214
246
 
215
247
  /**
@@ -238,6 +270,7 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
238
270
  // Validate only repo-controls' declared tables before deleting its root.
239
271
  // Other owners' tables remain deliberately opaque.
240
272
  validateOwnedConfig(initialConfigText)
273
+ declaredScaffoldTrees(initialConfigText)
241
274
  // The lock is outside neither configuration nor runtime boundaries, so read
242
275
  // its bookkeeping before replacing its containing root. It is never used
243
276
  // to preserve root content.
@@ -271,32 +304,39 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
271
304
  files[rel] = { sha256: sha256(text) }
272
305
  }
273
306
 
274
- const controlsResult = installConfigTable(
275
- initialConfigText,
276
- 'controls',
277
- DEFAULT_CONFIG_TEXT,
278
- SEEDED_ATTRIBUTES,
279
- prev?.config ?? null,
280
- ops,
281
- )
282
- const complianceResult = installConfigTable(
283
- controlsResult.text,
284
- 'controls.compliance',
285
- defaultComplianceConfigTextFor(repo),
286
- seededComplianceAttributesFor(repo),
287
- prev?.complianceConfig ?? null,
288
- ops,
289
- )
290
- // Both entries' hashes are computed from the SAME final merged text (not each call's
291
- // intermediate view of it): appending [compliance] after [controls] can extend the byte
292
- // span findTableSpan reports for [controls] (e.g. a trailing blank separator line before
293
- // the next header), so hashing at each call's own intermediate text would immediately
294
- // go stale the moment a later call appends more content to the same file.
295
- const finalConfigText = complianceResult.text
296
- const config: TableConfigRecord = { created: controlsResult.created, sha256: sha256(extractTableSpan(finalConfigText, 'controls') as string) }
297
- const complianceConfig: TableConfigRecord = {
298
- created: complianceResult.created,
299
- sha256: sha256(extractTableSpan(finalConfigText, 'controls.compliance') as string),
307
+ const rewire = options.rewire === true
308
+ let config: TableConfigRecord = prev?.config ?? null
309
+ let complianceConfig: TableConfigRecord = prev?.complianceConfig ?? null
310
+ if (!rewire) {
311
+ const controlsAttributes = [...SEEDED_ATTRIBUTES, ...integrityWatchSeed(initialConfigText)]
312
+ const controlsResult = installConfigTable(
313
+ initialConfigText,
314
+ 'controls',
315
+ `[controls]${controlsAttributes.map((a) => a.text).join('')}`,
316
+ controlsAttributes,
317
+ prev?.config ?? null,
318
+ ops,
319
+ )
320
+ const complianceResult = installConfigTable(
321
+ controlsResult.text,
322
+ 'controls.compliance',
323
+ DEFAULT_COMPLIANCE_CONFIG_TEXT,
324
+ SEEDED_COMPLIANCE_ATTRIBUTES,
325
+ prev?.complianceConfig ?? null,
326
+ ops,
327
+ )
328
+ // Both entries' hashes are computed from the SAME final merged text (not each call's
329
+ // intermediate view of it): appending [compliance] after [controls] can extend the byte
330
+ // span findTableSpan reports for [controls] (e.g. a trailing blank separator line before
331
+ // the next header), so hashing at each call's own intermediate text would immediately
332
+ // go stale the moment a later call appends more content to the same file.
333
+ const finalConfigText = complianceResult.text
334
+ config = { created: controlsResult.created, sha256: sha256(extractTableSpan(finalConfigText, 'controls') as string) }
335
+ complianceConfig = {
336
+ created: complianceResult.created,
337
+ sha256: sha256(extractTableSpan(finalConfigText, 'controls.compliance') as string),
338
+ }
339
+ scaffoldStandardTrees(finalConfigText, ops)
300
340
  }
301
341
 
302
342
  const gitignore = installGitignore(repo, prev?.gitignore ?? null, ops)
@@ -314,7 +354,19 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
314
354
  gitignore,
315
355
  }
316
356
  ops.write(LOCK_REL, `${JSON.stringify(lock, null, 2)}\n`, 'install lock')
317
- return { repo, actions: ops.actions, dryRun }
357
+ // Graded last, so a missing `uv` fails loudly only after every file is wired.
358
+ const compliance = options.grade !== undefined && !dryRun && !rewire ? gradeInstalledRepo(repo, options.grade.env) : undefined
359
+ return { repo, actions: ops.actions, dryRun, ...(compliance === undefined ? {} : { compliance }) }
360
+ }
361
+
362
+ /** The `[controls] integrity` seed: watch the declared compliance register folder (see README, "Compliance seeding"). Empty when no folder is declared. */
363
+ function integrityWatchSeed(configText: string): SeededAttribute[] {
364
+ const trees = ownedTable(configText, 'controls.compliance')?.trees
365
+ // declaredScaffoldTrees ran first in install() and already rejected a non-string value.
366
+ const declared = (isTable(trees) ? (trees.compliance ?? 'compliance') : 'compliance') as string
367
+ const folder = declared.replace(/^(\.\/)+/, '').replace(/\/+$/, '')
368
+ if (folder === '') return []
369
+ return [integritySeed(folder)]
318
370
  }
319
371
 
320
372
  /** The value at a dotted table path (`controls.compliance`), or undefined if absent. */
@@ -425,6 +477,57 @@ function installConfigTable(
425
477
  return { text: appended, created: true }
426
478
  }
427
479
 
480
+ /** The scaffold trees declared in config, validated; throws on a malformed path so install can refuse before any write. */
481
+ function declaredScaffoldTrees(configText: string): { name: string; base: string }[] {
482
+ const trees: { name: string; base: string }[] = []
483
+ const declared = ownedTable(configText, 'controls.compliance')?.trees
484
+ if (!isTable(declared)) return trees
485
+ assertComplianceTree(declared.compliance)
486
+ for (const name of SCAFFOLD_TREES) {
487
+ const value = declared[name]
488
+ if (value === undefined || value === '') continue
489
+ if (typeof value !== 'string') throw new Error(`${CONFIG_REL} [controls.compliance.trees] ${name} must be a string`)
490
+ const base = value.replace(/^(\.\/)+/, '').replace(/\/+$/, '')
491
+ if (!isRelative(base)) throw new Error(`${CONFIG_REL} [controls.compliance.trees] ${name} must be a repository-relative path: ${value}`)
492
+ trees.push({ name, base })
493
+ }
494
+ return trees
495
+ }
496
+
497
+ /** The compliance register folder is not scaffolded but is seeded into `[controls] integrity`, so it takes the tree path rules plus a safe character set. */
498
+ function assertComplianceTree(value: unknown): void {
499
+ if (value === undefined || value === '') return
500
+ const where = `${CONFIG_REL} [controls.compliance.trees] compliance`
501
+ if (typeof value !== 'string') throw new Error(`${where} must be a string`)
502
+ const base = value.replace(/^(\.\/)+/, '').replace(/\/+$/, '')
503
+ if (!isRelative(base)) throw new Error(`${where} must be a repository-relative path: ${value}`)
504
+ if (!/^[A-Za-z0-9._/-]+$/.test(base)) throw new Error(`${where} may only contain letters, digits, '.', '_', '-' and '/': ${value}`)
505
+ }
506
+
507
+ /** Bootstrap the standard trees at their declared paths, only where the folder is missing; see README, "Scaffolding". */
508
+ function scaffoldStandardTrees(configText: string, ops: Ops): void {
509
+ const missing = declaredScaffoldTrees(configText).filter(({ base }) => !ops.occupied(base))
510
+ for (const rel of SCAFFOLD_ROOT_FILES) {
511
+ ops.create(rel, readFileSync(join(SCAFFOLD_ROOT, 'root', rel)), 'scaffold the repository entry files (user-owned after install)')
512
+ }
513
+ ops.create(GLOBAL_INVARIANTS_TARGET, readFileSync(GLOBAL_INVARIANTS_SOURCE), 'deploy the global invariants (user-owned after install)')
514
+ for (const { name, base } of missing) {
515
+ for (const rel of scaffoldFiles(join(SCAFFOLD_ROOT, name), '')) {
516
+ ops.create(`${base}/${rel}`, readFileSync(join(SCAFFOLD_ROOT, name, rel)), `scaffold the ${name} tree (user-owned after install)`)
517
+ }
518
+ }
519
+ }
520
+
521
+ /** Every file under `dir`, relative to it, sorted, depth-first. */
522
+ function scaffoldFiles(dir: string, prefix: string): string[] {
523
+ return readdirSync(join(dir, prefix), { withFileTypes: true })
524
+ .sort((a, b) => (a.name < b.name ? -1 : 1))
525
+ .flatMap((entry) => {
526
+ const rel = prefix === '' ? entry.name : `${prefix}/${entry.name}`
527
+ return entry.isDirectory() ? scaffoldFiles(dir, rel) : [rel]
528
+ })
529
+ }
530
+
428
531
  function installGitignore(repo: string, prev: GitignoreRecord | null, ops: Ops): GitignoreRecord {
429
532
  const path = join(repo, GITIGNORE_REL)
430
533
  const exists = existsSync(path)