@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
package/bin/adl CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/adl
3
- size: 82912736 bytes
4
- sha256: 2b01ff7bfab3461ee9bc678cd6c7660691505b0bebd617f922de743b0187e197
3
+ size: 82920928 bytes
4
+ sha256: f63a82963b12de92399625846d611dc9a469ceec692522e95fbae7ff3f702acb
package/bin/health-check CHANGED
@@ -17,8 +17,8 @@ fi
17
17
 
18
18
  case "$device" in
19
19
  m5)
20
- launchctl print system/vsts.agent.amsterdamdatalabs.Default.mac-m5-ci 2>/dev/null |
21
- grep -q 'state = running'
20
+ runner_state="$(launchctl print system/vsts.agent.amsterdamdatalabs.Default.mac-m5-ci 2>/dev/null)"
21
+ [[ "$runner_state" == *'state = running'* ]]
22
22
  ;;
23
23
  hostinger-vps-1)
24
24
  # One unit per call: `systemctl is-active` given several units exits 0 when at
package/bin/reconcile CHANGED
@@ -368,7 +368,26 @@ SURFACE_UPDATE_TIMEOUT_SECONDS="${RECONCILE_SURFACE_UPDATE_TIMEOUT_SECONDS:-300}
368
368
  # brew's output for a cask whose upgrade needed sudo but was not allowed to ask: Homebrew refusing
369
369
  # ("sudo is disabled by HOMEBREW_NO_SUDO."), or sudo -n failing fast. It never blocks.
370
370
  brew_output_needs_password() {
371
- grep -Eiq 'sudo is disabled by HOMEBREW_NO_SUDO|sudo: .*(password|terminal|tty|askpass)|^Password:' <<<"$1"
371
+ local lowered="${1,,}" nl=$'\n'
372
+ local sudo_re="sudo: [^${nl}]*(password|terminal|tty|askpass)"
373
+ [[ "$lowered" == *'sudo is disabled by homebrew_no_sudo'* ]] && return 0
374
+ [[ "$lowered" =~ $sudo_re ]] && return 0
375
+ [[ "$lowered" == 'password:'* || "$lowered" == *"${nl}password:"* ]]
376
+ }
377
+
378
+ # Whole-line membership of $1 in newline list $2, pure bash (runs before ripgrep exists).
379
+ list_has_line() {
380
+ local nl=$'\n'
381
+ [[ "${nl}${2}${nl}" == *"${nl}${1}${nl}"* ]]
382
+ }
383
+
384
+ # True when file $2 has a line exactly equal to $1. Pure bash, same reason.
385
+ file_has_line() {
386
+ local line
387
+ while IFS= read -r line || [[ -n "$line" ]]; do
388
+ [[ "$line" == "$1" ]] && return 0
389
+ done <"$2"
390
+ return 1
372
391
  }
373
392
 
374
393
  # Upgrades the outdated Homebrew formulae and casks, from ONE `brew outdated --json=v2` (the
@@ -430,7 +449,7 @@ run_brew_upgrade_all_body() {
430
449
  excluded_names="$(jq -r '(.config.brew_upgrade_exclude // [])[]' "$manifest")"
431
450
  while IFS=$'\t' read -r kind name installed available; do
432
451
  [[ -n "$name" ]] || continue
433
- if [[ -n "$excluded_names" ]] && grep -Fxq -- "$name" <<<"$excluded_names"; then
452
+ if [[ -n "$excluded_names" ]] && list_has_line "$name" "$excluded_names"; then
434
453
  echo "global-tools: brew upgrade skips excluded: $name ($installed → $available)"
435
454
  skipped=$((skipped + 1))
436
455
  continue
@@ -878,12 +897,12 @@ install_surface_policy_file() {
878
897
  # "v" belongs to "dev"/"rev", not a version prefix -- silently matched and
879
898
  # would have skipped a real upgrade). The RIGHT boundary is a space or a
880
899
  # string end and never accepts "v", or "0.39.3" would incorrectly match
881
- # inside "0.39.30". Dots in the version are escaped so they match literally,
882
- # not "any character," in the regex below.
900
+ # inside "0.39.30". The match is a literal, space-bounded glob (no regex), so
901
+ # dots in the version are never "any character."
883
902
  release_archive_version_matches() {
884
- local version_line="$1" version="$2" escaped
885
- escaped="$(printf '%s' "$version" | sed 's/[.[\*^$()+?{}|]/\\&/g')"
886
- grep -Eq "(^| )v?${escaped}( |\$)" <<<"$version_line"
903
+ local version_line="$1" version="$2" padded
904
+ padded=" ${version_line//$'\n'/ } "
905
+ [[ "$padded" == *" $version "* || "$padded" == *" v$version "* ]]
887
906
  }
888
907
 
889
908
  # Resolves the release tag GitHub's own "latest" redirect currently points
@@ -1040,7 +1059,7 @@ install_one_tool() {
1040
1059
  }
1041
1060
  fi
1042
1061
  if [[ "$rc" -eq 0 && -n "$tap" && "$mode" == apply ]]; then
1043
- brew_query tap | grep -Fqx "$tap" || run_brew tap "$tap" || {
1062
+ list_has_line "$tap" "$(brew_query tap)" || run_brew tap "$tap" || {
1044
1063
  echo "ERROR $tool_id: brew tap $tap failed" >&2
1045
1064
  rc=1
1046
1065
  }
@@ -1070,7 +1089,7 @@ install_one_tool() {
1070
1089
  # whole-tap grant ("Trusted taps: owner/repo") can never satisfy the
1071
1090
  # item check.
1072
1091
  if [[ "$rc" -eq 0 ]]; then
1073
- brew_query trust | awk '{$1=$1};1' | grep -Fqx "$package" || run_brew trust "--$trust_kind" "$package" || {
1092
+ list_has_line "$package" "$(brew_query trust | awk '{$1=$1};1')" || run_brew trust "--$trust_kind" "$package" || {
1074
1093
  echo "ERROR $tool_id: brew trust --$trust_kind $package failed" >&2
1075
1094
  rc=1
1076
1095
  }
@@ -1426,8 +1445,8 @@ managed_repo_script_legacy_marker='# Managed by Enact global-tools.'
1426
1445
  is_managed_repo_script() {
1427
1446
  local executable="$1"
1428
1447
  [[ -f "$executable" && ! -L "$executable" ]] || return 1
1429
- grep -Fqx "$managed_repo_script_marker" "$executable" ||
1430
- grep -Fqx "$managed_repo_script_legacy_marker" "$executable"
1448
+ file_has_line "$managed_repo_script_marker" "$executable" ||
1449
+ file_has_line "$managed_repo_script_legacy_marker" "$executable"
1431
1450
  }
1432
1451
 
1433
1452
  atomic_install_managed_repo_script() {
@@ -2094,20 +2113,21 @@ run_surface_update() {
2094
2113
  # update path, native/npm and Homebrew-cask alike, rather than one
2095
2114
  # per-surface parser.
2096
2115
  #
2097
- # Never `cmd1 | cmd2` piped straight into the caller's assignment: see the
2098
- # inner `if var=$(grep ...); then` below (not `grep | sed`) for why -- under
2099
- # this file's `set -euo pipefail`, grep's ordinary "no match" exit would
2100
- # otherwise abort the run on every surface that DIDN'T report an error. An
2101
- # `if`-tested command is exempt from that abort by design; the function's
2102
- # OWN exit status is therefore never meaningful (an `if` with no `else`
2103
- # always returns 0), so callers check the printed string's emptiness, never
2104
- # this function's exit status.
2116
+ # Pure bash (no ripgrep yet); callers check the printed string, never the exit status.
2105
2117
  surface_update_error_reason() {
2106
2118
  local output="$1" plain line
2107
2119
  # LC_ALL=C: the strip is bytewise. Under en_US.UTF-8 glibc resolves [@-~]
2108
2120
  # by collation, so the escape's final letter would never match.
2109
2121
  plain="$(LC_ALL=C sed -E $'s/\x1b\\[[0-9;]*[@-~]//g' <<<"$output")"
2110
- if line="$(grep -m1 -iE '^[[:space:]]*(✖[[:space:]]*)?error:' <<<"$plain")"; then
2122
+ local error_re='^[[:space:]]*(✖[[:space:]]*)?[eE][rR][rR][oO][rR]:' candidate
2123
+ line=""
2124
+ while IFS= read -r candidate; do
2125
+ if [[ "$candidate" =~ $error_re ]]; then
2126
+ line="$candidate"
2127
+ break
2128
+ fi
2129
+ done <<<"$plain"
2130
+ if [[ -n "$line" ]]; then
2111
2131
  sed -E 's/^[[:space:]]*(✖[[:space:]]*)?[eE][rR][rR][oO][rR]:[[:space:]]*//' <<<"$line"
2112
2132
  fi
2113
2133
  }
@@ -2309,7 +2329,7 @@ reconcile_one_surface_cli() {
2309
2329
  # the per-tool brew path in install_one_tool; the kind is certain here).
2310
2330
  # Bare names are core casks, trusted by default -- no grant.
2311
2331
  if [[ "$name" == */* ]]; then
2312
- brew_query trust | awk '{$1=$1};1' | grep -Fqx "$name" || run_brew trust --cask "$name" || {
2332
+ list_has_line "$name" "$(brew_query trust | awk '{$1=$1};1')" || run_brew trust --cask "$name" || {
2313
2333
  echo "ERROR surface $name: brew trust --cask $name failed" >&2
2314
2334
  return 1
2315
2335
  }
package/bin/setup CHANGED
@@ -112,7 +112,8 @@ if [[ "$fleet_host_platform" == linux ]]; then
112
112
  "$brew_prefix/Homebrew" \
113
113
  "$brew_prefix/Homebrew/Library/Taps/adl-tan-theta/homebrew-tap"; do
114
114
  [[ -d "$brew_repo/.git" ]] || continue
115
- if ! (cd /tmp && sudo -u "$brew_owner" -H git config --global --get-all safe.directory 2>/dev/null) | grep -Fxq "$brew_repo"; then
115
+ safe_dirs="$(cd /tmp && sudo -u "$brew_owner" -H git config --global --get-all safe.directory 2>/dev/null)" || safe_dirs=""
116
+ if [[ $'\n'"$safe_dirs"$'\n' != *$'\n'"$brew_repo"$'\n'* ]]; then
116
117
  (cd /tmp && sudo -u "$brew_owner" -H git config --global --add safe.directory "$brew_repo")
117
118
  fi
118
119
  done
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amsterdamdatalabs/adl-agent-control-linux-x64",
3
- "version": "0.1.29",
3
+ "version": "0.1.32",
4
4
  "description": "adl CLI binary for linux/x64 (agent identity + Azure DevOps front door — see docs/architecture/cli-design.md)",
5
5
  "license": "UNLICENSED",
6
6
  "os": [
@@ -78,14 +78,16 @@ adl hook doctor [--repo <path>]
78
78
  In a managed repo doctor also prints both hook-log paths with their row
79
79
  counts and echoes the last 5 deny-log rows.
80
80
 
81
- ## Hook logs: `.adl/logs/hooks.log` and `.adl/logs/hooks-deny.log`
81
+ ## Hook logs: `.adl/logs/agent-hooks/`
82
82
 
83
83
  Written only for a managed repo with hooks enabled (same gate as every hook;
84
84
  unmanaged or `[hooks] enabled = false` writes nothing). Only agent hook
85
85
  invocations are logged. Logging is strictly best-effort: the directory is
86
86
  created lazily with the first row, one append per file per invocation, and a
87
- write failure (read-only dir, full disk) never changes a hook's stdout,
88
- stderr, or exit code. Environment variables are never logged.
87
+ write failure (read-only dir, full disk) never changes a hook's stdout
88
+ or exit code; it prints one `adl agent-hooks: could not write log row: ...`
89
+ line to stderr, so a lost row is never silent (git hooks print the same with
90
+ the prefix `adl repo controls:`). Environment variables are never logged.
89
91
 
90
92
  This is a hard cut: legacy `.agents/hooks/hooks.log` and
91
93
  `.agents/hooks/hooks-activity.log` files are neither read, copied, appended,
@@ -94,7 +96,7 @@ CSV files this format replaces (docs/architecture/cli-design.md §5a, Tarun 2026
94
96
  `.enact/logs/` is no longer written at all.
95
97
 
96
98
  Both files are **JSON Lines** — one JSON object per line, written by the one
97
- shared logger every `.adl/logs/*.log` file uses
99
+ shared logger every `.adl/logs/<owner>/*.log` file uses
98
100
  (`src/log/writer.ts`'s `writeLogEntry`, moved here from `apps/cli` so
99
101
  agent-hooks "holds logs"; `apps/cli/src/log.ts` re-exports it unchanged).
100
102
  Fields are written in a fixed order and an absent field is OMITTED, never
@@ -104,10 +106,60 @@ characters (`...<truncated>`, unchanged from the CSV era) and then redacted
104
106
  (`redact()`: bearer tokens, `Authorization:` headers, JWTs, `*_TOKEN=`/
105
107
  `*_SECRET=`/… name=value pairs, `--password`-style flags, URL userinfo).
106
108
  Files live at `0600`, the directory at `0700`; a file that would exceed 10 MiB
107
- is rotated to `.1`…`.5`, dropping `.5`.
108
-
109
- **`hooks.log`** — one row per hook invocation of every kind (allow, deny,
110
- route). **`hooks-deny.log`** — the same row, additionally, only when the
109
+ is rotated to `.1`…`.5`, dropping `.5`, under a per-file lock so concurrent
110
+ hook processes normally rotate once (see "Rotation-race hardening" for the
111
+ residual cases).
112
+
113
+ Bug 600 (Tarun, 2026-10-08): `.adl/logs` has one folder per owner. This
114
+ package owns `agent-hooks/` and writes nothing else; git hooks write
115
+ `git-hooks/`, `adl run` writes `agents/`. The old top-level `hooks.log`,
116
+ `hooks-deny.log` and `crg-update.log` are never migrated, moved or deleted —
117
+ they are left where they are. `agent-hooks/crg-update.log` is the raw stdout+stderr
118
+ of the detached `code-review-graph update` and rotates by the same rule before
119
+ each spawn.
120
+
121
+ Rotation race (Bug 600): on 2026-10-08 Enact's logs showed `hooks.log.1` = 580 B
122
+ and `hooks.log.2` = 10 MB. Two hook processes both saw size > max; the first
123
+ rotated the full file to `.1` and appended, the second (stale size check)
124
+ rotated again, pushing the full file to `.2`. The per-file lock fixes this:
125
+ the size is re-read inside the lock, so a writer that lost the race finds the
126
+ file fresh and does not rotate again. Rotation is idempotent: the live file is
127
+ renamed aside first (a missing file means someone else already rotated, so it
128
+ stops), then the backups shift. An empty live file is never rotated, so one
129
+ row larger than the maximum cannot push an empty file into `.1`.
130
+ `crg-update.log` was observed unbounded (2.8 MB, no rotation) before it joined
131
+ the same rule.
132
+
133
+ Rotation-race hardening (Bug 600 verifiers, 2026-10-08): every pre-write stat
134
+ and open tolerates the live log being renamed away by another writer's
135
+ rotation (ENOENT means "fresh file": the append recreates it at 0600), so a
136
+ writer that does not itself rotate never loses its row. The lock file carries
137
+ a `pid-random` token. A stale lock (older than 5 s) is broken by unlinking it
138
+ only if it still holds the token we observed; a holder removes the lock only
139
+ if it still holds its own token. This is read-compare-unlink, so a narrow race
140
+ remains where two writers can briefly both hold the lock. No step of rotation
141
+ overwrites a backup: shifting and the final placement into `.1` are hard-link
142
+ then unlink, a slot that is already taken is shifted again (at most 5 attempts,
143
+ then rotation fails loudly and keeps the aside file), so a double lock holder
144
+ can duplicate or shuffle backups but never lose a live file. The one deliberate
145
+ loss is the oldest backup falling off `.5`. A crash between the aside rename and
146
+ the final placement leaves `<log>.rotating-<pid>-<random>`; the next rotation
147
+ (inside the lock) adopts every such file whose owner pid is dead, or whose mtime
148
+ is over 5 s old, into the chain, oldest first. A crash between the link and the
149
+ unlink can leave one duplicate of a backup.
150
+ A row is never dropped because of the lock: on a lock timeout or any other
151
+ lock error the writer appends the row WITHOUT rotating and prints one
152
+ `adl log: rotation deferred, row kept: ...` line to stderr. `adl run` guards
153
+ its own two rows the same way (`adl run: could not write log row: ...`, never
154
+ aborting the run). `agent-hooks/crg-update` is not a JSON `LogFileName`: crg
155
+ output is raw text written by `src/hooks/crg/background.ts`.
156
+
157
+ The weekly search-tool audit (`agent-search-tool-audit`) reads the per-owner
158
+ folders and still reads the legacy top-level `hooks.log` (never migrated)
159
+ without writing to it.
160
+
161
+ **`agent-hooks/hooks.log`** — one row per hook invocation of every kind (allow, deny,
162
+ route). **`agent-hooks/deny.log`** — the same row, additionally, only when the
111
163
  invocation was a blocked pre-tool-use decision.
112
164
 
113
165
  `src/hooks/log.ts`'s field mapping (the shared schema has no dedicated
@@ -414,3 +466,20 @@ patterns, and `test/unit/coverage-policy.test.ts` asserts that policy (and
414
466
  that a below-threshold run really exits non-zero — Bun ignores the singular
415
467
  `line`/`function` keys). `bun test <file>` without `--coverage` stays usable
416
468
  for targeted runs.
469
+
470
+ ## Generated OpenCode plugin
471
+
472
+ `adl install` deploys one OpenCode project plugin, `.opencode/plugins/adl-repo.ts`,
473
+ rendered by `src/deploy/opencode-template.ts` from `assets/opencode-plugin.template.ts`,
474
+ `assets/hooks.json` and the OpenCode surface in `deploy-profile.json`. Do not edit
475
+ the deployed file by hand; re-run `adl install`.
476
+
477
+ - The deployed file has no multi-line comments (Tarun 2026-10-08, Bug 602), so it
478
+ passes compliance control REP-007. Its header is a single line pointing here.
479
+ - Fallback projection: an event with no documented OpenCode equivalent runs on a
480
+ substitute event, per the surface's `eventFallback` map in
481
+ `packages/agent-hooks/deploy-profile.json` (today `SessionEnd -> SessionIdle`).
482
+ The generated file lists the map as one line, `// Event fallbacks: A -> B, C -> D`,
483
+ and emits no line at all when the map is empty.
484
+ - Source of truth for hook commands is `assets/hooks.json`; the generated arrays
485
+ carry `command`, `matcher`, `timeoutMs` and `fallbackFor` per entry.
@@ -1,9 +1,5 @@
1
- // AUTO-GENERATED by packages/agent-hooks (src/deploy/opencode-template.ts). Do not edit by hand.
2
- // Source: assets/hooks.json -> ONE OpenCode project plugin.
3
- //
4
- // Projected via fallback (owner rule: an event with no documented OpenCode
5
- // equivalent runs on a substitute event instead — see
6
- // this surface's `eventFallback` in packages/agent-hooks/deploy-profile.json):
1
+ // AUTO-GENERATED by packages/agent-hooks from assets/hooks.json; do not edit. See packages/agent-hooks/README.md "Generated OpenCode plugin".
2
+
7
3
  __ADL_FALLBACK_ENTRIES__
8
4
 
9
5
  interface HookCommandSpec {
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { readFileSync, statSync } from 'node:fs'
13
13
  import { resolve } from 'node:path'
14
- import { decodeConfig, type AdlRepoConfig } from '../gating/config.ts'
14
+ import { decodeConfig, describeConfigIssue, type AdlRepoConfig } from '../gating/config.ts'
15
15
  import { MARKER, realRootIO, resolveManagedRepo } from '../gating/root.ts'
16
16
  import type { HookResult } from '../hooks/contract.ts'
17
17
 
@@ -88,7 +88,7 @@ export function loadRepo(cwd: string, repoArg: string | undefined): LoadedRepo |
88
88
  if (managed.configPath === null) return { ok: true, state, config: defaultConfig() }
89
89
  const decoded = decodeConfig(readFileSync(managed.configPath, 'utf8'))
90
90
  if (!decoded.ok) {
91
- return { ok: false, state, error: `invalid ${managed.configPath} at ${decoded.issue.pointer}: ${decoded.issue.message}` }
91
+ return { ok: false, state, error: describeConfigIssue(managed.configPath, decoded.issue) }
92
92
  }
93
93
  return { ok: true, state, config: decoded.value }
94
94
  }
@@ -33,6 +33,7 @@ import { scanSkills } from '../hooks/user-prompt-submit/skills.ts'
33
33
  import { loadTokenizer } from '../hooks/user-prompt-submit/tokenizer.ts'
34
34
  import { loadVectorHalf, resolveGlobalArtifactDir } from '../hooks/user-prompt-submit/vector.ts'
35
35
  import { HOOKS_DENY_LOG_REL, HOOKS_LOG_REL } from '../hooks/log.ts'
36
+ import { runningAdl } from '../gating/running-adl.ts'
36
37
  import { buildEngineContext } from '../rules/engine.ts'
37
38
  import { liveEnv, liveRepoIndex } from '../rules/ports.ts'
38
39
  import { BUILTIN_RULES, requireRegistered } from '../rules/registry.ts'
@@ -67,7 +68,7 @@ function repoChecks(loaded: LoadedRepo): { readonly checks: Check[]; readonly co
67
68
  if (!loaded.ok) return { checks: [repoLine, check('FAIL', 'config', loaded.error)], config: null }
68
69
  const root = state.root as string
69
70
  const ctx = buildEngineContext(loaded.config.rules, loaded.config.repo, requireRegistered(BUILTIN_RULES), liveRepoIndex(root), liveEnv, undefined, loaded.config.agent)
70
- if (!ctx.ok) return { checks: [repoLine, check('FAIL', 'config', `${ctx.issue.pointer}: ${ctx.issue.message}`)], config: null }
71
+ if (!ctx.ok) return { checks: [repoLine, check('FAIL', 'config', `${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}`)], config: null }
71
72
  return { checks: [repoLine, check('ok', 'config', 'valid')], config: loaded.config }
72
73
  }
73
74
 
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { buildEngineContext } from '../rules/engine.ts'
15
15
  import { liveEnv, liveRepoIndex } from '../rules/ports.ts'
16
+ import { runningAdl } from '../gating/running-adl.ts'
16
17
  import { BUILTIN_RULES, requireRegistered } from '../rules/registry.ts'
17
18
  import type { AnyRuleSpec } from '../rules/spec.ts'
18
19
  import { type CliResult, type RepoState, cliError, loadRepo, parseArgs, unmanagedReason } from './args.ts'
@@ -42,7 +43,7 @@ function resolveRules(cwd: string, repoArg: string | undefined): Resolved | CliR
42
43
  const { state, config } = loaded
43
44
  const registered = requireRegistered(BUILTIN_RULES)
44
45
  const ctx = buildEngineContext(config.rules, config.repo, registered, liveRepoIndex(state.root ?? state.start), liveEnv, undefined, config.agent)
45
- if (!ctx.ok) return cliError(`${state.configPath} ${ctx.issue.pointer}: ${ctx.issue.message}`)
46
+ if (!ctx.ok) return cliError(`${state.configPath} ${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}`)
46
47
  const hooksEnabled = config.hooks.enabled
47
48
  const rules = [...registered.values()].map((spec) => ({
48
49
  spec,
@@ -3,8 +3,9 @@
3
3
  * (render.ts's renderOpencodeSlots), the profile's pluginName, and the
4
4
  * identifier derived from it — reproducing this repo's own committed
5
5
  * `.opencode/plugins/adl-repo.ts` byte-for-byte for the current
6
- * deploy-profile.json + assets/hooks.json, except the header's first two
7
- * lines (see the template's own header comment for why).
6
+ * deploy-profile.json + assets/hooks.json, except the header (a one-line
7
+ * comment plus an optional one-line fallback comment; no multi-line comments in
8
+ * deployed code, Bug 602). See README "Generated OpenCode plugin".
8
9
  */
9
10
  import { readFileSync } from 'node:fs'
10
11
  import { join } from 'node:path'
@@ -54,7 +55,8 @@ export function renderOpencodePlugin(options: RenderOpencodePluginOptions): stri
54
55
 
55
56
  if (!template.includes('__ADL_FALLBACK_ENTRIES__')) throw new Error(`opencode plugin template is missing marker __ADL_FALLBACK_ENTRIES__: ${templatePath}`)
56
57
  const fallbackEntries = Object.entries(surface.eventFallback ?? {})
57
- let out = template.replace('__ADL_FALLBACK_ENTRIES__', fallbackEntries.map(([from, to]) => `// - ${from} -> ${to}`).join('\n'))
58
+ const fallbackComment = fallbackEntries.length === 0 ? '' : `// Event fallbacks: ${fallbackEntries.map(([from, to]) => `${from} -> ${to}`).join(', ')}`
59
+ let out = template.replace('__ADL_FALLBACK_ENTRIES__', fallbackComment)
58
60
 
59
61
  const slots = renderOpencodeSlots(hooksDoc, surface)
60
62
  for (const slot of OPENCODE_SLOTS) {
@@ -62,6 +62,7 @@
62
62
  */
63
63
 
64
64
  import { MARKER } from './root.ts'
65
+ import { runningAdl } from './running-adl.ts'
65
66
 
66
67
  interface RuleSection {
67
68
  readonly enabled: boolean
@@ -146,6 +147,16 @@ export interface AdlRepoConfig {
146
147
 
147
148
  export type ConfigDecodeIssue = { readonly pointer: string; readonly message: string }
148
149
 
150
+ /**
151
+ * The one place a config-decode failure is rendered for an operator:
152
+ * `invalid <config> at <pointer>: <message> (adl <version> at <exec path>)`
153
+ * (`./running-adl.ts`). Added at rendering only — `ConfigDecodeIssue` stays
154
+ * untouched for programmatic consumers.
155
+ */
156
+ export function describeConfigIssue(configPath: string, issue: ConfigDecodeIssue): string {
157
+ return `invalid ${configPath} at ${issue.pointer}: ${issue.message}${runningAdl()}`
158
+ }
159
+
149
160
  export type ConfigDecodeResult =
150
161
  | { readonly ok: true; readonly value: AdlRepoConfig }
151
162
  | { readonly ok: false; readonly issue: ConfigDecodeIssue }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Which adl is decoding the config. A stale binary rejecting a newer config's
3
+ * keys ("unknown key(s) 'fleet'") looks identical to a bad config, so every
4
+ * config rejection an operator sees carries `(adl <version> at <exec path>)`.
5
+ *
6
+ * The version belongs to the CLI (`apps/cli/package.json`, what `adl
7
+ * --version` prints). This package must not import upward into the app, so
8
+ * `adl hook` injects it through `run(argv, adlVersion)` in `../main.ts`. A
9
+ * caller that does not inject one (the standalone agent-hooks entry, in-process
10
+ * tests) renders `version unknown` — never a made-up number.
11
+ *
12
+ * The path is `process.execPath`: the compiled binary itself (there
13
+ * `process.argv[1]` is a virtual `/$bunfs` path); under `bun run` it is bun,
14
+ * which is what is actually running.
15
+ */
16
+ let injectedVersion: string | undefined;
17
+
18
+ export function setRunningAdlVersion(version: string | undefined): void {
19
+ injectedVersion = version;
20
+ }
21
+
22
+ export function runningAdl(): string {
23
+ return ` (adl ${injectedVersion ?? "version unknown"} at ${process.execPath})`;
24
+ }
@@ -11,7 +11,8 @@
11
11
  * Protocol (all state under `<root>/.adl/`, already git-ignored):
12
12
  * - `crg-update.lock` holds the pid of the process running the update.
13
13
  * - `crg-update.pending` marker: "an edit landed while an update ran".
14
- * - `logs/crg-update.log` the update's own stdout+stderr (append).
14
+ * - `logs/agent-hooks/crg-update.log` the update's own stdout+stderr (append;
15
+ * rotated by the shared writer's rule before each spawn, Bug 600).
15
16
  *
16
17
  * `requestUpdate` (the hook side):
17
18
  * 1. Acquire the lock atomically (`wx`), holding the hook's own pid as a
@@ -33,7 +34,7 @@
33
34
  * Self-heal: if `code-review-graph update --skip-flows` exits non-zero,
34
35
  * the runner runs `code-review-graph build --skip-flows` once and logs a
35
36
  * `SELF-HEAL:` marker to stderr (captured by the spawn into
36
- * `crg-update.log`). The original error is already in the log because
37
+ * `agent-hooks/crg-update.log`). The original error is already in the log because
37
38
  * the spawn captures the update's own stderr; the marker is appended
38
39
  * after, so the failure is never silently swallowed. The build runs at
39
40
  * most once per runner invocation (a per-runner shell variable), so a
@@ -47,10 +48,11 @@ import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFi
47
48
  import { join } from 'node:path'
48
49
 
49
50
  import { ensureAdlGitignore } from '../../log/adl-dir.ts'
51
+ import { rotateIfNeeded } from '../../log/writer.ts'
50
52
 
51
53
  export const LOCK_REL = '.adl/crg-update.lock'
52
54
  export const PENDING_REL = '.adl/crg-update.pending'
53
- export const UPDATE_LOG_REL = '.adl/logs/crg-update.log'
55
+ export const UPDATE_LOG_REL = '.adl/logs/agent-hooks/crg-update.log'
54
56
 
55
57
  /** A lock older than this is presumed left by a crash, whatever its pid says. */
56
58
  export const MAX_LOCK_AGE_MS = 30 * 60 * 1000
@@ -113,6 +115,7 @@ function isPidAlive(pid: number): boolean {
113
115
  }
114
116
 
115
117
  function spawnDetached(spec: SpawnSpec): number {
118
+ rotateIfNeeded(spec.logFile, 0)
116
119
  const fd = openSync(spec.logFile, 'a', 0o600)
117
120
  try {
118
121
  const proc = Bun.spawn([spec.cmd, ...spec.args], {
@@ -185,7 +188,7 @@ function acquireAndStart(deps: BackgroundDeps, root: string, env: SpawnSpec['env
185
188
  }
186
189
 
187
190
  export function requestUpdate(deps: BackgroundDeps, root: string, env: SpawnSpec['env']): UpdateRequest {
188
- mkdirSync(join(root, '.adl', 'logs'), { recursive: true, mode: 0o700 })
191
+ mkdirSync(join(root, '.adl', 'logs', 'agent-hooks'), { recursive: true, mode: 0o700 })
189
192
  ensureAdlGitignore(join(root, '.adl'))
190
193
  const lockPath = join(root, LOCK_REL)
191
194
  const pendingPath = join(root, PENDING_REL)
@@ -1,15 +1,15 @@
1
1
  /**
2
- * The agent-hook decision logs: `.adl/logs/hooks.log` (every decision —
3
- * allow, deny, route) and `.adl/logs/hooks-deny.log` (every deny, with the
4
- * rule and the `adl` hint shown to the agent) — docs/architecture/cli-design.md §5a.
2
+ * The agent-hook decision logs: `.adl/logs/agent-hooks/hooks.log` (every
3
+ * decision — allow, deny, route) and `.adl/logs/agent-hooks/deny.log` (every
4
+ * deny, with the rule and the `adl` hint shown to the agent) — docs/architecture/cli-design.md §5a; one folder per owner (README, "Hook logs").
5
5
  *
6
6
  * BEHAVIOUR CHANGE (Tarun, 2026-09-27, §5a): this used to write two CSV
7
7
  * files under `.enact/logs/` (`adl-hook-deny.log` / `adl-hook-activity.log`,
8
8
  * ported from enact-m5's `.agents/hooks/pre-tool-use/src/deny.mjs`
9
9
  * `logDenial`). §5a's later decision replaces that with ONE JSON-Lines
10
- * format shared by all five `.adl/logs/*.log` files
11
- * (`../log/writer.ts`'s `writeLogEntry`), written to `.adl/logs/hooks.log`
12
- * and `.adl/logs/hooks-deny.log`. `.enact/logs/` is a **hard cut**: it is no
10
+ * format shared by every `.adl/logs/<owner>/*.log` file
11
+ * (`../log/writer.ts`'s `writeLogEntry`), written to `.adl/logs/agent-hooks/hooks.log`
12
+ * and `.adl/logs/agent-hooks/deny.log`. `.enact/logs/` is a **hard cut**: it is no
13
13
  * longer written (the old files, if present, are left in place — see
14
14
  * `../log/writer.ts`'s header and `writeLogEntry`'s rotation/redaction/0700
15
15
  * '0600 behaviour, which this file now delegates to instead of owning its
@@ -76,8 +76,8 @@ import type { LogEntryInput } from '../log/writer.ts'
76
76
  import { writeLogEntry } from '../log/writer.ts'
77
77
  import type { ActivityNote, HookHandler, HookInvocation, HookResult } from './contract.ts'
78
78
 
79
- export const HOOKS_LOG_REL = '.adl/logs/hooks.log'
80
- export const HOOKS_DENY_LOG_REL = '.adl/logs/hooks-deny.log'
79
+ export const HOOKS_LOG_REL = '.adl/logs/agent-hooks/hooks.log'
80
+ export const HOOKS_DENY_LOG_REL = '.adl/logs/agent-hooks/deny.log'
81
81
  const MAX_LOGGED_COMMAND_LENGTH = 2000
82
82
 
83
83
  function truncateCommand(value: string): string {
@@ -212,10 +212,10 @@ export async function runLogged(inv: HookInvocation, handler: HookHandler, now:
212
212
  const stamped = now()
213
213
  const sameNow = () => stamped
214
214
  try {
215
- writeLogEntry('hooks', entry, { dir, now: sameNow })
216
- if (outcome.denyRuleId !== undefined) writeLogEntry('hooks-deny', entry, { dir, now: sameNow })
217
- } catch {
218
- // Best-effort: a log write must never change a hook's stdout/stderr/exit code.
215
+ writeLogEntry('agent-hooks/hooks', entry, { dir, now: sameNow })
216
+ if (outcome.denyRuleId !== undefined) writeLogEntry('agent-hooks/deny', entry, { dir, now: sameNow })
217
+ } catch (error) {
218
+ process.stderr.write(`adl agent-hooks: could not write log row: ${error instanceof Error ? error.message : String(error)}\n`)
219
219
  }
220
220
  }
221
221
  let result: HookResult
@@ -15,7 +15,7 @@
15
15
  import { existsSync, readFileSync } from 'node:fs'
16
16
  import { join } from 'node:path'
17
17
  import type { HookHandler, HookInvocation } from '../contract.ts'
18
- import { decodeConfig } from '../../gating/config.ts'
18
+ import { decodeConfig, describeConfigIssue } from '../../gating/config.ts'
19
19
  import { MARKER } from '../../gating/root.ts'
20
20
  import { DEFAULT_SKILLS_DIR } from './constants.ts'
21
21
  import { skillRootsFor } from './skill-roots.ts'
@@ -52,7 +52,7 @@ function resolveSkillRoots(inv: HookInvocation): readonly string[] {
52
52
  const text = readFileSync(configPath, 'utf8')
53
53
  const decoded = decodeConfig(text)
54
54
  if (!decoded.ok) {
55
- throw new Error(`invalid ${configPath} at ${decoded.issue.pointer}: ${decoded.issue.message}`)
55
+ throw new Error(describeConfigIssue(configPath, decoded.issue))
56
56
  }
57
57
 
58
58
  const explicit = decoded.value.hooks.skillsDir