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

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 (410) hide show
  1. package/bin/adl +2 -2
  2. package/bin/git +16 -17
  3. package/bin/git-ado +25 -24
  4. package/bin/git-defaults +173 -0
  5. package/bin/health-check +8 -1
  6. package/bin/kimi-hooks +1 -1
  7. package/bin/lib/adl-package.sh +257 -0
  8. package/bin/lib/fleet-host.sh +8 -5
  9. package/bin/reconcile +482 -232
  10. package/bin/reconcile-host +30 -20
  11. package/bin/setup +20 -17
  12. package/bin/shell-path-policy +15 -5
  13. package/bin/sync-and-reconcile +62 -64
  14. package/bin/validate-manifest +33 -9
  15. package/package.json +2 -10
  16. package/packages/agent-hooks/README.md +14 -9
  17. package/packages/agent-hooks/assets/hooks.json +0 -20
  18. package/packages/agent-hooks/src/cli/args.ts +1 -1
  19. package/packages/agent-hooks/src/cli/doctor.ts +6 -58
  20. package/packages/agent-hooks/src/deploy/deploy.ts +12 -41
  21. package/packages/agent-hooks/src/deploy/doctor.ts +8 -11
  22. package/packages/agent-hooks/src/deploy/index.ts +14 -14
  23. package/packages/agent-hooks/src/deploy/json-merge.ts +7 -8
  24. package/packages/agent-hooks/src/deploy/legacy-lock.ts +2 -2
  25. package/packages/agent-hooks/src/deploy/list.ts +1 -1
  26. package/packages/agent-hooks/src/deploy/lock.ts +31 -12
  27. package/packages/agent-hooks/src/deploy/profile.ts +3 -4
  28. package/packages/agent-hooks/src/deploy/source.ts +1 -1
  29. package/packages/agent-hooks/src/gating/config.ts +12 -24
  30. package/packages/agent-hooks/src/gating/root.ts +3 -3
  31. package/packages/agent-hooks/src/hooks/briefing/handler.ts +2 -2
  32. package/packages/agent-hooks/src/hooks/briefing/index.ts +27 -287
  33. package/packages/agent-hooks/src/hooks/contract.ts +17 -17
  34. package/packages/agent-hooks/src/hooks/crg/background.ts +27 -0
  35. package/packages/agent-hooks/src/hooks/crg/index.ts +7 -6
  36. package/packages/agent-hooks/src/hooks/log.ts +8 -10
  37. package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +1 -1
  38. package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +1 -1
  39. package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +6 -5
  40. package/packages/agent-hooks/src/hosts/registry.ts +1 -1
  41. package/packages/agent-hooks/src/log/adl-dir.ts +44 -0
  42. package/packages/agent-hooks/src/log/writer.ts +16 -2
  43. package/packages/agent-hooks/src/main.ts +7 -41
  44. package/packages/agent-hooks/src/pre-tool-use/handler.ts +71 -82
  45. package/packages/agent-hooks/src/rules/engine.ts +13 -17
  46. package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +3 -3
  47. package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +12 -14
  48. package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +28 -28
  49. package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +8 -9
  50. package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +56 -59
  51. package/packages/agent-hooks/src/rules/impl/adl-identity.ts +16 -23
  52. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +19 -16
  53. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +2 -2
  54. package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +9 -12
  55. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +5 -7
  56. package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +411 -0
  57. package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +4 -5
  58. package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +6 -7
  59. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +77 -70
  60. package/packages/agent-hooks/src/rules/impl/deny-allowlist.ts +131 -0
  61. package/packages/agent-hooks/src/rules/impl/git-helpers.ts +21 -23
  62. package/packages/agent-hooks/src/rules/impl/git-worktree.ts +14 -28
  63. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -3
  64. package/packages/agent-hooks/src/rules/impl/messages.ts +1 -1
  65. package/packages/agent-hooks/src/rules/impl/pr-completion.ts +1 -1
  66. package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +35 -46
  67. package/packages/agent-hooks/src/rules/impl/tool-find.ts +34 -7
  68. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +33 -12
  69. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +52 -9
  70. package/packages/agent-hooks/src/rules/ports.ts +5 -7
  71. package/packages/agent-hooks/src/rules/registry.ts +4 -6
  72. package/packages/agent-hooks/src/rules/shell/git-identity.ts +86 -101
  73. package/packages/agent-hooks/src/rules/shell/git-internals.ts +1 -1
  74. package/packages/agent-hooks/src/rules/shell/invocation.ts +24 -118
  75. package/packages/agent-hooks/src/rules/shell/piped-script.ts +1 -1
  76. package/packages/agent-hooks/src/rules/shell/prefixes.ts +1 -1
  77. package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +11 -10
  78. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +26 -26
  79. package/packages/agent-hooks/src/rules/shell/substitution.ts +1 -1
  80. package/packages/agent-hooks/src/rules/shell/xargs.ts +2 -2
  81. package/packages/agent-hooks/src/rules/spec.ts +2 -2
  82. package/packages/agent-settings/README.md +287 -0
  83. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +14 -16
  84. package/packages/agent-settings/assets/agents/architect.toml +2 -0
  85. package/packages/agent-settings/assets/agents/code-reviewer.toml +2 -0
  86. package/packages/agent-settings/assets/agents/executor.toml +2 -0
  87. package/packages/agent-settings/assets/agents/explore.toml +2 -0
  88. package/packages/agent-settings/assets/agents/planner.toml +2 -0
  89. package/packages/agent-settings/assets/mcp.json +0 -4
  90. package/packages/agent-settings/deploy-profile.json +139 -0
  91. package/packages/agent-settings/deploy-profile.schema.json +147 -0
  92. package/packages/agent-settings/managed/README.md +29 -30
  93. package/packages/agent-settings/managed/codex-requirements.toml +9 -9
  94. package/packages/agent-settings/package.json +20 -0
  95. package/packages/agent-settings/src/deploy/agent-render.ts +2 -2
  96. package/packages/agent-settings/src/deploy/agent-toml.ts +1 -1
  97. package/packages/agent-settings/src/deploy/agents-deploy.ts +17 -3
  98. package/packages/agent-settings/src/deploy/doctor.ts +3 -83
  99. package/packages/agent-settings/src/deploy/index.ts +6 -33
  100. package/packages/agent-settings/src/deploy/legacy-lock.ts +6 -6
  101. package/packages/agent-settings/src/deploy/list.ts +5 -19
  102. package/packages/agent-settings/src/deploy/lock.ts +8 -18
  103. package/packages/agent-settings/src/deploy/mcp-deploy.ts +10 -5
  104. package/packages/agent-settings/src/deploy/mcp-merge.ts +22 -3
  105. package/packages/agent-settings/src/deploy/mcp-source.ts +1 -1
  106. package/packages/agent-settings/src/deploy/profile.ts +11 -35
  107. package/packages/agent-settings/src/deploy/retire-lean-ctx.ts +184 -0
  108. package/packages/agent-settings/src/deploy/settings.ts +17 -22
  109. package/packages/agent-settings/src/global-surface-sweep.ts +0 -2
  110. package/packages/agent-settings/src/kimi-hooks.ts +339 -192
  111. package/packages/agent-settings/src/opencode-hooks.ts +1 -1
  112. package/packages/agent-settings/tsconfig.json +23 -0
  113. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +120 -0
  114. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +276 -0
  115. package/packages/agent-skills/assets/debug-issue/SKILL.md +8 -7
  116. package/packages/agent-skills/assets/explore-codebase/SKILL.md +13 -13
  117. package/packages/agent-skills/assets/refactor-safely/SKILL.md +12 -11
  118. package/packages/agent-skills/assets/review-changes/SKILL.md +7 -7
  119. package/packages/agent-skills/assets/testing/references/e2e.md +37 -0
  120. package/packages/agent-skills/assets/testing/references/integration.md +37 -0
  121. package/packages/agent-skills/assets/testing/references/unit.md +32 -0
  122. package/packages/agent-skills/dist/deploy.d.ts +2 -0
  123. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  124. package/packages/agent-skills/dist/deploy.js +27 -1
  125. package/packages/agent-skills/dist/deploy.js.map +1 -1
  126. package/packages/agent-skills/dist/doctor.d.ts +0 -2
  127. package/packages/agent-skills/dist/doctor.d.ts.map +1 -1
  128. package/packages/agent-skills/dist/doctor.js +1 -1
  129. package/packages/agent-skills/dist/doctor.js.map +1 -1
  130. package/packages/agent-skills/dist/fs-utils.d.ts +0 -1
  131. package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -1
  132. package/packages/agent-skills/dist/fs-utils.js +1 -1
  133. package/packages/agent-skills/dist/fs-utils.js.map +1 -1
  134. package/packages/agent-skills/src/deploy.ts +29 -1
  135. package/packages/agent-skills/src/doctor.ts +1 -1
  136. package/packages/agent-skills/src/fs-utils.ts +1 -1
  137. package/packages/azdo/README.md +10 -9
  138. package/packages/azdo/package.json +1 -3
  139. package/packages/azdo/scripts/probe-ado-rules.ts +311 -0
  140. package/packages/azdo/src/azdo-pl.ts +2 -54
  141. package/packages/azdo/src/azdo-pr-review.ts +4 -4
  142. package/packages/azdo/src/azdo-repo-doctor.ts +101 -52
  143. package/packages/azdo/src/azdo-wit.ts +44 -2
  144. package/packages/azdo/src/lib/agent-identity.ts +12 -14
  145. package/packages/azdo/src/lib/agent-token.ts +2 -2
  146. package/packages/azdo/src/lib/client.ts +0 -5
  147. package/packages/azdo/src/lib/graph-identity.ts +4 -4
  148. package/packages/azdo/src/workflow-ladder.csv +241 -0
  149. package/packages/azdo/src/workflow-rules-matrix.csv +85 -0
  150. package/packages/azdo/src/workflow-rules.ts +194 -0
  151. package/packages/fleet/manifest.json +35 -11
  152. package/packages/fleet/manifest.schema.json +62 -10
  153. package/packages/fleet/src/default-agent-file.ts +3 -3
  154. package/packages/fleet/src/install-adl-package.ts +73 -15
  155. package/packages/fleet/src/install-embedding-model.ts +13 -13
  156. package/packages/fleet/src/managed-settings.ts +7 -7
  157. package/packages/fleet/src/repo-maintain.ts +91 -55
  158. package/packages/fleet/src/scratch-reap.ts +5 -5
  159. package/packages/fleet/src/vscode-settings.ts +5 -5
  160. package/packages/repo-controls/README.md +141 -43
  161. package/packages/repo-controls/assets/promote.ts +4 -4
  162. package/packages/repo-controls/assets/reconcile-guard.sh +10 -46
  163. package/packages/repo-controls/assets/version-bump.sh +31 -84
  164. package/packages/repo-controls/bunfig.toml +6 -7
  165. package/packages/repo-controls/package/adl-compliance/AGENTS.md +5 -6
  166. package/packages/repo-controls/package/adl-compliance/conftest.py +3 -1
  167. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +4 -16
  168. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +9 -2
  169. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +1 -0
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +11 -0
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/__init__.py +7 -0
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/check.py +266 -0
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.drafts/c.md +1 -0
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.drafts/d.md +1 -0
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.hidden.md +1 -0
  176. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/a.md +1 -0
  177. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/b.md +1 -0
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/data.csv +1 -0
  179. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/diagram.png +1 -0
  180. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/mock.png +1 -0
  181. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/sub/nested.md +1 -0
  182. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs.md +1 -0
  183. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/grants.yaml +4 -0
  184. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p1.md +1 -0
  185. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p2.md +1 -0
  186. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p3.md +1 -0
  187. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p4.md +1 -0
  188. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p5.md +1 -0
  189. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/build.md +1 -0
  190. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/overview.md +1 -0
  191. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/install.md +1 -0
  192. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/start.md +1 -0
  193. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/grants.yaml +4 -0
  194. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-001-tenancy.md +1 -0
  195. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-002-carrier.md +1 -0
  196. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/adr-003-cli.md +1 -0
  197. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/archive/adr-000-old.md +1 -0
  198. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/decisions/archive/adr-000-older.md +1 -0
  199. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/a.md +1 -0
  200. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/b.md +1 -0
  201. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/c.md +1 -0
  202. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/grants.yaml +4 -0
  203. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-01.md +1 -0
  204. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-02.md +1 -0
  205. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-03.md +1 -0
  206. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-04.md +1 -0
  207. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-05.md +1 -0
  208. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-06.md +1 -0
  209. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-07.md +1 -0
  210. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-08.md +1 -0
  211. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-09.md +1 -0
  212. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-10.md +1 -0
  213. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-11.md +1 -0
  214. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-12.md +1 -0
  215. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-13.md +1 -0
  216. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-14.md +1 -0
  217. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-15.md +1 -0
  218. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/docs/guide/page-16.md +1 -0
  219. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/grants.yaml +4 -0
  220. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/a.md +1 -0
  221. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/b.md +1 -0
  222. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/c.md +1 -0
  223. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/grants.yaml +4 -0
  224. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/a.md +1 -0
  225. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/b.md +1 -0
  226. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/c.md +1 -0
  227. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/grants.yaml +4 -0
  228. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/a.md +1 -0
  229. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/b.md +1 -0
  230. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/c.md +1 -0
  231. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/d.md +1 -0
  232. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/docs/guide/e.md +1 -0
  233. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/grants.yaml +4 -0
  234. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/a.md +1 -0
  235. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/b.md +1 -0
  236. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/guide/c.md +1 -0
  237. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/docs/short/only.md +1 -0
  238. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/grants.yaml +4 -0
  239. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/a.md +1 -0
  240. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/b.md +1 -0
  241. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/c.md +1 -0
  242. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/d.md +1 -0
  243. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/a.md +1 -0
  244. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/b.md +1 -0
  245. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/grants.yaml +6 -0
  246. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/a.md +1 -0
  247. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/b.md +1 -0
  248. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/c.md +1 -0
  249. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/grants.yaml +3 -0
  250. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/test_check.py +345 -0
  251. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +18 -5
  252. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +35 -0
  253. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/__init__.py +7 -0
  254. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/check.py +91 -0
  255. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/AGENTS.md +3 -0
  256. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/CLAUDE.md +3 -0
  257. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/-leading-hyphen.md +3 -0
  258. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/AGENTS.md +3 -0
  259. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/double--hyphen.md +3 -0
  260. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/getting started.md +3 -0
  261. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/nested/page-here.md +3 -0
  262. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/trailing-hyphen-.md +3 -0
  263. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +4 -0
  264. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/user_profile.md +3 -0
  265. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/UI-CONTRACT.png +2 -0
  266. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/diagram.png +1 -0
  267. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/index.md +4 -0
  268. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/notes.md +3 -0
  269. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/reference.html +1 -0
  270. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/2026-10-experiment.md +3 -0
  271. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +6 -0
  272. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs.md +5 -0
  273. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +194 -0
  274. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +14 -2
  275. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +5 -5
  276. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-shipped-register-legacy/docs/compliance/index.md +10 -0
  277. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-shipped-register-type/docs/compliance/index.md +10 -0
  278. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +58 -6
  279. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +0 -1
  280. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +64 -17
  281. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +60 -9
  282. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +17 -0
  283. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +13 -0
  284. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +0 -1
  285. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +0 -1
  286. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/no-type.md +9 -0
  287. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-experiment.md +10 -0
  288. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-research.md +10 -0
  289. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs-okf-profiles.yaml +5 -0
  290. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs/architecture/bad-status.md +0 -1
  291. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +0 -1
  292. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/experiment-ok.md +15 -0
  293. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +15 -0
  294. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +16 -0
  295. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +11 -0
  296. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +11 -0
  297. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-last-update.md +11 -0
  298. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-sources.md +10 -0
  299. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +11 -0
  300. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +11 -0
  301. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +16 -0
  302. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +14 -0
  303. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/architecture/created-date.md +21 -0
  304. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/architecture/research-shaped.md +19 -0
  305. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/operations/runbook.md +26 -0
  306. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +228 -2
  307. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +5 -0
  308. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +11 -2
  309. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +5 -3
  310. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +160 -44
  311. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +0 -2
  312. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/a.ts +1 -0
  313. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/b.ts +1 -0
  314. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/grants.yaml +3 -0
  315. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +0 -2
  316. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  317. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +5 -0
  318. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/a.ts +1 -0
  319. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/b.ts +1 -0
  320. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/c.ts +1 -0
  321. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +4 -0
  322. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  323. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +5 -0
  324. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +0 -2
  325. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
  326. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/grants.yaml +3 -0
  327. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/register.py +127 -0
  328. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +265 -29
  329. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +75 -2
  330. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +194 -0
  331. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +56 -10
  332. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +258 -2
  333. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +40 -12
  334. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +108 -0
  335. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +9 -4
  336. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +1 -1
  337. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +1 -1
  338. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +3 -3
  339. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +2 -3
  340. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +23 -6
  341. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +76 -0
  342. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +58 -17
  343. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +161 -0
  344. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +35 -3
  345. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +108 -1
  346. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +281 -61
  347. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/absolute-path.yaml +3 -0
  348. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/backslash-path.yaml +3 -0
  349. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/baseline-is-a-list.yaml +2 -0
  350. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/boolean-size.yaml +3 -0
  351. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/dot-segment.yaml +3 -0
  352. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/duplicate-path.yaml +4 -0
  353. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-file.yaml +0 -0
  354. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-segment.yaml +3 -0
  355. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/float-size.yaml +3 -0
  356. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/negative-size.yaml +3 -0
  357. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-over-limit-baseline.yaml +1 -0
  358. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-schema-version.yaml +1 -0
  359. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/non-string-path.yaml +3 -0
  360. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/not-yaml.yaml +2 -0
  361. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/null-size.yaml +3 -0
  362. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/parent-segment.yaml +3 -0
  363. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/string-size.yaml +3 -0
  364. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/top-level-is-a-list.yaml +2 -0
  365. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/trailing-slash.yaml +3 -0
  366. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/unknown-key.yaml +3 -0
  367. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/wrong-schema-version.yaml +2 -0
  368. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/zero-size.yaml +3 -0
  369. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/valid.yaml +4 -0
  370. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +758 -45
  371. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +4 -3
  372. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +2 -2
  373. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +490 -0
  374. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +368 -55
  375. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +4 -2
  376. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +523 -0
  377. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +1179 -0
  378. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +122 -3
  379. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +33 -1
  380. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +0 -11
  381. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +16 -1
  382. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -2
  383. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +12 -1
  384. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +133 -147
  385. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +133 -147
  386. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
  387. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +52 -10
  388. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +23 -4
  389. package/packages/repo-controls/package.json +2 -9
  390. package/packages/repo-controls/scripts/hooks/post-merge +13 -0
  391. package/packages/repo-controls/scripts/hooks/pre-push +3 -3
  392. package/packages/repo-controls/src/azdo-push.ts +64 -36
  393. package/packages/repo-controls/src/azure-devops-governance.ts +7 -9
  394. package/packages/repo-controls/src/azure-devops-remote.ts +1 -1
  395. package/packages/repo-controls/src/cli.ts +29 -8
  396. package/packages/repo-controls/src/compliance-config.ts +410 -2
  397. package/packages/repo-controls/src/config.ts +133 -60
  398. package/packages/repo-controls/src/constants.ts +13 -27
  399. package/packages/repo-controls/src/coverage-ratchet.ts +4 -4
  400. package/packages/repo-controls/src/doctor.ts +46 -19
  401. package/packages/repo-controls/src/gitignore-block.ts +5 -29
  402. package/packages/repo-controls/src/hook-log.ts +1 -2
  403. package/packages/repo-controls/src/hook.ts +201 -45
  404. package/packages/repo-controls/src/install.ts +14 -8
  405. package/packages/repo-controls/src/integrity.ts +3 -3
  406. package/packages/repo-controls/src/lfs.ts +111 -0
  407. package/packages/repo-controls/src/promote.ts +8 -8
  408. package/packages/repo-controls/src/runtime.ts +9 -1
  409. package/packages/repo-controls/src/workflows-drift.ts +4 -5
  410. package/packages/repo-controls/src/worktree-guard.ts +3 -3
@@ -1,18 +1,41 @@
1
1
  """LAY-004 — ported from `compliance_new/checks/layout/directory-ratchet/test.sh`.
2
2
 
3
- Three committed corpora, same names and shapes as the shell fixtures:
4
- `directory-ratchet-clean` (one baselined single-file directory, nothing else),
5
- `directory-ratchet-unbaselined` (an unbaselined single-file directory, plus a
6
- control: eleven single-file subdirectories of a parent that itself holds zero
7
- direct files), and `directory-ratchet-excluded` (the row's own `!` exclusions
8
- under test, run twice against the same files through two Row variants that
9
- differ by exactly those two patterns).
3
+ Eight committed corpora, the first six same names and shapes as the shell
4
+ fixtures with their registers reworked to the 2026-10-02 generic-ratchet
5
+ principle (v3 for this gate): `directory-ratchet-clean` (one baselined
6
+ single-file directory, nothing else), `directory-ratchet-unbaselined` (an
7
+ unbaselined single-file directory, plus a control: eleven single-file
8
+ subdirectories of a parent that itself holds zero direct files),
9
+ `directory-ratchet-excluded` (the row's own `!` exclusions under test, run
10
+ twice against the same files through two Row variants that differ by exactly
11
+ those two patterns), `directory-ratchet-missing-tree` (a ceiling-configured
12
+ tree with no directory — advisory, not failure),
13
+ `directory-ratchet-stale-entry` (DIR-003's kept reach: a baseline entry under
14
+ a live tree whose directory was deleted), `directory-ratchet-unusable-register`
15
+ (a register that exists but is not valid YAML — could-not-run, the kept
16
+ exception in every ruling shape), and two added for the generic principle:
17
+ `directory-ratchet-no-ceilings` (three files in one directory against a
18
+ register that would exempt them — with NO ceilings declared the gate has
19
+ nothing to enforce and the register is never read, so it stays clean), and
20
+ `directory-ratchet-empty-register` (a register recording ZERO state — a load
21
+ failure, because zero state is no file).
22
+
23
+ V3 (owner ruling 2026-10-02, superseding this gate's earlier "ratchets sit
24
+ ON TOP" ruling; the same principle DOC-012 v2 shipped): the per-tree
25
+ CEILINGS are CONFIGURATION and live in `[controls.compliance.directory-
26
+ ratchet].max_children`, reaching the engine as the `directory-ceilings`
27
+ declaration. The register records STATE only — over-limit and single-file
28
+ baselines. An absent register records no state, so the ceilings are enforced
29
+ bare (strict — the widening input's normal contract). Two v1 tests were
30
+ reworked and say so in their docstrings. At zero recorded state the file is
31
+ deleted; a file that EXISTS recording zero state is a load failure.
10
32
 
11
33
  Each fixture carries its own `grants.yaml` at its own root, passed as the
12
34
  `register` input override — the same mechanism `DOC-006`'s test uses for its
13
35
  packaged profile register, not a path nested to match `policy/config.toml`'s
14
36
  tree mapping, which is exactly the coupling that broke this port once already
15
- (P-14).
37
+ (P-14). The ceilings travel as the `directory-ceilings` declaration, the same
38
+ channel DOC-012's tests use for `docs-pages-per-directory`.
16
39
  """
17
40
 
18
41
  from __future__ import annotations
@@ -38,24 +61,102 @@ def corpus_files(root: pathlib.Path) -> tuple[str, ...]:
38
61
  )
39
62
 
40
63
 
41
- def run(case: str, *, scope: tuple[str, ...] | None = None) -> object:
64
+ def run(
65
+ case: str, *, scope: tuple[str, ...] | None = None, ceilings: str | None = None
66
+ ) -> object:
42
67
  directory = FIXTURES / case
43
68
  row = ROW
44
69
  if scope is not None:
45
70
  row = row.model_copy(update={"engine": row.engine.model_copy(update={"scope": scope})})
71
+ declarations = {} if ceilings is None else {DirectoryRatchet.CEILINGS_DECLARATION: ceilings}
46
72
  return runner.run(
47
73
  row, directory, corpus_files(directory),
48
74
  inputs={row.id: {"register": directory / "grants.yaml"}},
75
+ declarations=declarations,
49
76
  )
50
77
 
51
78
 
79
+ # ------------------------------------------------------- the ceilings channel
80
+
81
+
52
82
  def test_a_fully_baselined_tree_passes() -> None:
53
- verdict = run("directory-ratchet-clean")
83
+ verdict = run("directory-ratchet-clean", ceilings="frontend/packages/desktop/src:10")
84
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
85
+ assert not verdict.findings
86
+ assert not verdict.advisories
87
+
88
+
89
+ def test_no_ceilings_configured_means_nothing_to_enforce() -> None:
90
+ """The generic principle's config half: no `[controls.compliance.directory-
91
+ ratchet]` table, no ceilings declared — the gate has NOTHING to enforce
92
+ and passes clean even with a three-file directory on disk whose register
93
+ would exempt it (proving the register is not even read: its entry could
94
+ only ever REMOVE findings)."""
95
+ verdict = run("directory-ratchet-no-ceilings")
96
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
97
+ assert not verdict.findings
98
+ assert not verdict.advisories
99
+
100
+
101
+ def test_a_baseline_entry_exempts_its_directory_up_to_the_recorded_count() -> None:
102
+ """The state half: the same three-file directory, now under a declared
103
+ ceiling of 2, is CLEAN because the register records its count of 3 — an
104
+ entry forgives the directory up to the count recorded (growing past it
105
+ is DIR-002, reported separately)."""
106
+ verdict = run(
107
+ "directory-ratchet-no-ceilings", ceilings="frontend/packages/desktop/src:2"
108
+ )
54
109
  assert verdict.outcome is Outcome.CLEAN, verdict.render()
110
+ assert not verdict.findings
111
+
112
+
113
+ def test_an_over_ceiling_directory_with_no_register_is_dir_001() -> None:
114
+ """The same three-file corpus one more time, register pointed at a path
115
+ that does not exist: no recorded state means no exemptions allowed, so
116
+ the ceiling of 2 is enforced bare — DIR-001 names the directory."""
117
+ directory = FIXTURES / "directory-ratchet-no-ceilings"
118
+ verdict = runner.run(
119
+ ROW, directory, corpus_files(directory),
120
+ inputs={ROW.id: {"register": directory / "does-not-exist.yaml"}},
121
+ declarations={DirectoryRatchet.CEILINGS_DECLARATION: "frontend/packages/desktop/src:2"},
122
+ )
123
+ assert verdict.outcome is Outcome.FINDINGS
124
+ findings = [f for f in verdict.findings if f.rule == "DIR-001"]
125
+ assert len(findings) == 1, tuple(f.render() for f in verdict.findings)
126
+ assert findings[0].path == "frontend/packages/desktop/src"
127
+ assert findings[0].message == "3 direct children (max 2), no ratchet entry"
128
+
129
+
130
+ def test_a_malformed_ceilings_declaration_is_could_not_run() -> None:
131
+ for bad in ("frontend/packages/desktop/src:banana", "frontend/packages/desktop/src:0"):
132
+ verdict = run("directory-ratchet-clean", ceilings=bad)
133
+ assert verdict.outcome is Outcome.COULD_NOT_RUN, bad
134
+ assert "directory-ratchet" in verdict.message
135
+ assert "frontend/packages/desktop/src" in verdict.message
136
+ # An entry with no ceiling half at all:
137
+ verdict = run("directory-ratchet-clean", ceilings="frontend/packages/desktop/src")
138
+ assert verdict.outcome is Outcome.COULD_NOT_RUN
139
+ assert "directory-ratchet" in verdict.message
140
+
141
+
142
+ def test_a_register_recording_zero_state_is_a_load_failure() -> None:
143
+ """The ruling's deletion half, enforced loudly: a register file that
144
+ EXISTS but records no state (no over-limit baseline, no single-file
145
+ baseline, no docs_pages section) is the shape the owner deletes — read
146
+ as a load failure, never as a silent empty pass."""
147
+ verdict = run(
148
+ "directory-ratchet-empty-register",
149
+ ceilings="frontend/packages/desktop/src:2",
150
+ )
151
+ assert verdict.outcome is Outcome.COULD_NOT_RUN
152
+ assert "a register grading nothing" in verdict.message
153
+
154
+
155
+ # ------------------------------------------------------- the original corpus
55
156
 
56
157
 
57
158
  def test_an_unbaselined_single_file_directory_is_reported() -> None:
58
- verdict = run("directory-ratchet-unbaselined")
159
+ verdict = run("directory-ratchet-unbaselined", ceilings="frontend/packages/desktop/src:10")
59
160
  assert verdict.outcome is Outcome.FINDINGS
60
161
  paths = {f.path for f in verdict.findings if f.rule == "DIR-001"}
61
162
  assert "frontend/packages/desktop/src/renderer/fresh" in paths
@@ -66,14 +167,14 @@ def test_a_subdirectory_alone_never_trips_the_rule() -> None:
66
167
  """`many` holds eleven subdirectories and zero direct files of its own, so it
67
168
  needs no baseline entry; each subdirectory holds exactly one file and IS
68
169
  reported. Both halves matter, or this corpus is silent by accident."""
69
- verdict = run("directory-ratchet-unbaselined")
170
+ verdict = run("directory-ratchet-unbaselined", ceilings="frontend/packages/desktop/src:10")
70
171
  paths = {f.path for f in verdict.findings if f.rule == "DIR-001"}
71
172
  assert "frontend/packages/desktop/src/renderer/many" not in paths
72
173
  assert "frontend/packages/desktop/src/renderer/many/sub1" in paths
73
174
 
74
175
 
75
176
  def test_the_row_exclusions_silence_locales_and_vendored_cargo() -> None:
76
- verdict = run("directory-ratchet-excluded")
177
+ verdict = run("directory-ratchet-excluded", ceilings="frontend/packages/desktop/src:20")
77
178
  assert verdict.outcome is Outcome.FINDINGS
78
179
  paths = {f.path for f in verdict.findings}
79
180
  assert not any("locales" in p for p in paths)
@@ -93,7 +194,7 @@ def test_falsifiability_without_the_row_exclusions() -> None:
93
194
  )
94
195
  assert len(unscoped) == len(ROW.engine.scope) - 2
95
196
 
96
- verdict = run("directory-ratchet-excluded", scope=unscoped)
197
+ verdict = run("directory-ratchet-excluded", scope=unscoped, ceilings="frontend/packages/desktop/src:20")
97
198
  assert verdict.outcome is Outcome.FINDINGS
98
199
  paths = {f.path for f in verdict.findings if f.rule == "DIR-001"}
99
200
  assert any("locales" in p for p in paths)
@@ -104,30 +205,86 @@ def test_the_ratchet_register_is_not_a_grant_catalogue() -> None:
104
205
  """`structural_grant = True` forces the allowance to 0 regardless of the
105
206
  row's own `grants:` field. Passing the register's size as the allowance is
106
207
  the trap: a failing gate would report green."""
107
- verdict = run("directory-ratchet-unbaselined")
208
+ verdict = run("directory-ratchet-unbaselined", ceilings="frontend/packages/desktop/src:10")
108
209
  assert verdict.granted == 0
109
210
  assert verdict.outcome is Outcome.FINDINGS
110
211
 
111
212
 
112
- def test_an_absent_register_is_could_not_run_not_a_clean_pass() -> None:
113
- """The baseline is the REPOSITORY's, so a repository without one has nothing
114
- graded — and nothing graded is could-not-run.
115
-
116
- This gate shipped its register as package data: one donor repository's whole
117
- directory shape, 88 of 104 lines naming `backend/crates/enact-*`,
118
- `frontend/packages/desktop/*` and `infra/stacks/*`. No repository in this
119
- estate has any of those trees, so every one of them hit
120
- `registered tree directory is missing: backend/crates` on the first entry —
121
- a gate reporting another repo's layout as this repo's defect.
122
- """
213
+ def test_an_absent_register_enforces_the_ceilings_bare() -> None:
214
+ """REWORKED from v1's `test_an_absent_register_means_nothing_is_ratcheted`,
215
+ which asserted the opposite under the superseded "ratchets sit ON TOP"
216
+ ruling. Generic principle (2026-10-02): the register records STATE only,
217
+ so an absent register records no exemptions and the configured ceilings
218
+ are enforced bare — every single-file directory the clean corpus holds
219
+ reports DIR-001 (the corpus's `solo` is exempted only by its recorded
220
+ entry, which is gone)."""
123
221
  directory = FIXTURES / "directory-ratchet-clean"
124
222
  verdict = runner.run(
125
223
  ROW, directory, corpus_files(directory),
126
224
  inputs={ROW.id: {"register": directory / "does-not-exist.yaml"}},
225
+ declarations={DirectoryRatchet.CEILINGS_DECLARATION: "frontend/packages/desktop/src:10"},
226
+ )
227
+ assert verdict.outcome is Outcome.FINDINGS
228
+ findings = [f for f in verdict.findings if f.rule == "DIR-001"]
229
+ assert {f.path for f in findings} == {
230
+ "frontend/packages/desktop/src/renderer/solo",
231
+ }
232
+
233
+
234
+ def test_an_absent_register_with_an_empty_scope_is_still_the_runners_dead_scope() -> None:
235
+ """The C1 guard is untouched: on an exhaustive run whose scope selects
236
+ nothing, the runner's dead-scope could-not-run stands. The widening-input
237
+ change only says the CHECK must not add its own register complaint on top
238
+ of that — and it cannot even run here."""
239
+ directory = FIXTURES / "directory-ratchet-clean"
240
+ verdict = runner.run(
241
+ ROW, directory, (),
242
+ exhaustive=True,
243
+ inputs={ROW.id: {"register": directory / "does-not-exist.yaml"}},
244
+ declarations={DirectoryRatchet.CEILINGS_DECLARATION: "frontend/packages/desktop/src:10"},
245
+ )
246
+ assert verdict.outcome is Outcome.COULD_NOT_RUN
247
+ assert "matches no file on disk" in verdict.message
248
+ assert "register" not in verdict.message
249
+
250
+
251
+ def test_a_register_that_exists_but_is_unusable_is_could_not_run() -> None:
252
+ """The kept exception through every ruling shape: absent is bare
253
+ enforcement, UNUSABLE is not. A register that exists but cannot be parsed
254
+ may be mid-edit; grading it as "no baselines" would silently drop every
255
+ exemption it names."""
256
+ verdict = run("directory-ratchet-unusable-register", ceilings="frontend/packages/desktop/src:10")
257
+ assert verdict.outcome is Outcome.COULD_NOT_RUN
258
+ assert "unusable" in verdict.message
259
+
260
+
261
+ def test_a_configured_tree_that_is_gone_is_an_advisory_not_a_failure() -> None:
262
+ """Generic principle, the cleanup half: a tree the CONFIGURATION names
263
+ but the repository no longer has is cleanup, not a defect. One advisory
264
+ names what is unaccounted; the remaining trees are graded normally."""
265
+ verdict = run(
266
+ "directory-ratchet-missing-tree",
267
+ ceilings="frontend/packages/desktop/src:10,infra:10",
127
268
  )
128
- assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
129
- assert "no directory-ratchet register" in verdict.message
130
- assert "never a clean pass" in verdict.message
269
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
270
+ assert not verdict.findings
271
+ assert len(verdict.advisories) == 1
272
+ advisory = verdict.advisories[0]
273
+ assert advisory.path == "infra"
274
+ assert "no longer accounted" in advisory.message
275
+ assert "remove its register entries or restore the tree" in advisory.message
276
+
277
+
278
+ def test_a_baseline_entry_for_a_deleted_directory_is_still_reported_stale() -> None:
279
+ """The narrowing of WHO may report absence, not the register's own
280
+ hygiene: an entry under a tree that STILL exists, naming a directory the
281
+ repository deleted, is DIR-003 exactly as before. Cleanup of the CONTENT
282
+ is free; cleanup of the REGISTER is the owner's edit, and the gate still
283
+ says so."""
284
+ verdict = run("directory-ratchet-stale-entry", ceilings="frontend/packages/desktop/src:10")
285
+ assert verdict.outcome is Outcome.FINDINGS
286
+ stale = {f.path for f in verdict.findings if f.rule == "DIR-003"}
287
+ assert "frontend/packages/desktop/src/renderer/gone" in stale
131
288
 
132
289
 
133
290
  def test_the_register_is_not_packaged_data() -> None:
@@ -136,3 +293,82 @@ def test_the_register_is_not_packaged_data() -> None:
136
293
  assert "register" in DirectoryRatchet.inputs
137
294
  assert "register" not in DirectoryRatchet.resources
138
295
  assert DirectoryRatchet.inputs["register"].startswith("<")
296
+
297
+
298
+ # --------------------------------------------------------- the shared schema
299
+ # The register model dropped `max_children` in the same change (v3): ceilings
300
+ # are configuration now, and a register recording zero state is a load
301
+ # failure. These pin the schema change itself.
302
+
303
+
304
+ def test_a_baselines_only_register_is_valid() -> None:
305
+ """The register's whole job after the rework: recorded state. Over-limit
306
+ and single-file baselines, no `docs_pages` section — LAY-004's reader."""
307
+ from adl_compliance.checks.layout.directory_ratchet.register import Ratchet
308
+
309
+ ratchet = Ratchet.model_validate(
310
+ {
311
+ "schema_version": 2,
312
+ "over_children_baseline": {"frontend/packages/desktop/src/renderer": 12},
313
+ "single_file_baseline": {
314
+ "frontend/packages/desktop/src/renderer/solo": "item.ts"
315
+ },
316
+ }
317
+ )
318
+ assert ratchet.docs_pages is None
319
+
320
+
321
+ def test_a_register_carrying_max_children_is_refused() -> None:
322
+ """`max_children` was configuration; configuration moved to adl-config.toml
323
+ in the same change, so the key it lived in is `extra="forbid"`-refused —
324
+ a stale register fails loud instead of being silently read as state."""
325
+ import pytest
326
+ from pydantic import ValidationError
327
+
328
+ from adl_compliance.checks.layout.directory_ratchet.register import Ratchet
329
+
330
+ with pytest.raises(ValidationError, match="max_children"):
331
+ Ratchet.model_validate(
332
+ {
333
+ "schema_version": 2,
334
+ "max_children": {"frontend/packages/desktop/src": 10},
335
+ "over_children_baseline": {},
336
+ "single_file_baseline": {},
337
+ }
338
+ )
339
+
340
+
341
+ def test_a_docs_pages_only_register_is_valid() -> None:
342
+ """DOC-012's exemption-only shape is untouched by this rework: a register
343
+ holding only `docs_pages.baseline` parses clean."""
344
+ from adl_compliance.checks.layout.directory_ratchet.register import DocsPages, Ratchet
345
+
346
+ ratchet = Ratchet.model_validate(
347
+ {"schema_version": 2, "docs_pages": {"baseline": {"docs/operations": 10}}}
348
+ )
349
+ assert ratchet.over_children_baseline == {}
350
+ assert ratchet.docs_pages == DocsPages(baseline={"docs/operations": 10})
351
+
352
+
353
+ def test_an_all_empty_register_is_a_load_failure() -> None:
354
+ """Zero recorded state is no file — an existing file that records nothing
355
+ is either the wrong file or an emptied register mid-cleanup, and reading
356
+ it as "no baselines" would silently drop every exemption it used to
357
+ name. The cross-field validator keeps the v1 `min_length=1` intent in
358
+ the new shape: at least one of over/single/docs_pages.baseline."""
359
+ import pytest
360
+ from pydantic import ValidationError
361
+
362
+ from adl_compliance.checks.layout.directory_ratchet.register import Ratchet
363
+
364
+ with pytest.raises(ValidationError, match="a register grading nothing"):
365
+ Ratchet.model_validate({"schema_version": 2})
366
+ with pytest.raises(ValidationError, match="a register grading nothing"):
367
+ Ratchet.model_validate(
368
+ {
369
+ "schema_version": 2,
370
+ "over_children_baseline": {},
371
+ "single_file_baseline": {},
372
+ "docs_pages": {"baseline": {}},
373
+ }
374
+ )
@@ -9,6 +9,12 @@ The allowed roots are RESOLVED, not listed: `<docs>`, `<plans>`, `<compliance>`,
9
9
  graded against its own spelling. The only literals left are `*/LEDGER.md` (a
10
10
  register beside the tree it records) and the exclusion list below.
11
11
 
12
+ A repository can add further Markdown roots of its own under
13
+ `[controls.compliance.markdown] roots` (the `markdown-roots` declaration): a
14
+ directory whose Markdown is site or application content rather than
15
+ documentation. A declared root only ever WIDENS the allowlist, so declaring
16
+ nothing is the stricter grade.
17
+
12
18
  This docstring used to read "docs/, .plans/, .specs/, or .changeset/" and three
13
19
  of those four were wrong. `.specs` was deleted 2026-09-16 as a path that matched
14
20
  nothing in this estate and never could; `.plans` became the resolvable `<plans>`
@@ -40,12 +46,20 @@ trees; anything in neither list is reported.
40
46
  from __future__ import annotations
41
47
 
42
48
  import fnmatch
49
+ import glob
43
50
  from collections.abc import Iterable
44
51
  from typing import ClassVar
45
52
 
46
- from ....policy.root_shape import GOVERNANCE, GUIDES, REGISTERS
53
+ from ....policy.root_shape import (
54
+ GOVERNANCE,
55
+ GUIDES,
56
+ REGISTERS,
57
+ ROOT_FILES_DECLARATION,
58
+ declared_root_names,
59
+ )
47
60
  from ....core.check import Check
48
61
  from ....core.context import Context, FileSet
62
+ from ....core.errors import CouldNotRun
49
63
  from ....core.finding import Report
50
64
 
51
65
 
@@ -85,6 +99,9 @@ class MarkdownTrees(Check):
85
99
  # of "what may sit at a repository's root". This gate asked the same question
86
100
  # and was not brought in, so it flagged CONTRIBUTING.md and MAINTAINERS.md --
87
101
  # exactly the files d841de1 ruled belong at the root, where a newcomer looks.
102
+ # Only the STANDARD ones: a repository's own root Markdown (a STATUS.md, a
103
+ # DRIFT.md) is declared under `[controls.compliance.root] files` and added by
104
+ # `_excluded`.
88
105
  *sorted(GOVERNANCE | GUIDES | REGISTERS),
89
106
  "AGENTS.md", "CLAUDE.md", "CHANGELOG.md", "RELEASE.md",
90
107
  "INVARIANTS.md", "README.md", "CLAUDE-INVARIANTS.md",
@@ -176,8 +193,47 @@ class MarkdownTrees(Check):
176
193
  # `<specs>` only ever ADDS allowlist entries here. enact-m5 has no specs
177
194
  # tree; requiring one would push the owner to declare a directory that does
178
195
  # not exist, and a false declaration is undetectable. Absent -> stricter.
196
+ #
197
+ # This input is reachable only by a caller that builds a Context by hand (the
198
+ # unit tests do): `cli.py` routes global inputs to batch rows alone, so no
199
+ # `adl-config.toml` key ever reached `specs_root`. A repository's own extra
200
+ # Markdown roots arrive through the `markdown-roots` declaration below.
179
201
  widening_inputs: ClassVar[frozenset[str]] = frozenset({"specs_root"})
180
202
 
203
+ # `[controls.compliance.markdown].roots` -- directories a repository declares
204
+ # as homes for Markdown it authors outside every tree above, e.g. an Astro
205
+ # site's content-collection pages (`apps/marketing/src/content/**/*.md`),
206
+ # which are site content, not documentation and not movable into `docs/`
207
+ # without breaking the framework that loads them. Sent to the engine as one
208
+ # comma-separated `--declare markdown-roots=a,b`.
209
+ ROOTS_DECLARATION: ClassVar[str] = "markdown-roots"
210
+
211
+ def _declared_roots(self, ctx: Context) -> tuple[str, ...]:
212
+ """The repository-relative directories declared under `markdown-roots`.
213
+
214
+ Normalised (a trailing `/` is dropped) and VALIDATED. A declared root can
215
+ only WIDEN the allowlist, so a malformed one -- absolute, or carrying a
216
+ `.`, `..` or empty segment -- is could-not-run rather than a pattern
217
+ that admits more than the repository meant. `adl` rejects the same set
218
+ when it reads `adl-config.toml`; this is the engine refusing it too,
219
+ because the engine is also run directly with `--declare`. Whether the
220
+ directory exists is not asked: this check cannot walk, and a root that
221
+ matches no file widens nothing.
222
+ """
223
+ roots: list[str] = []
224
+ for name in sorted(declared_root_names(ctx.declarations, self.ROOTS_DECLARATION)):
225
+ root = name.rstrip("/")
226
+ if name.startswith("/") or any(part in ("", ".", "..") for part in root.split("/")):
227
+ raise CouldNotRun(
228
+ f"{self.id}: `{self.ROOTS_DECLARATION}` declares {name!r}, which is not a "
229
+ "repository-relative directory (it is absolute, or has a `.`, `..` or "
230
+ "empty path segment); fix [controls.compliance.markdown].roots in "
231
+ "adl-config.toml -- a malformed root could only widen the allowlist, "
232
+ "so the gate does not grade against it"
233
+ )
234
+ roots.append(root)
235
+ return tuple(roots)
236
+
181
237
  def _allowed(self, ctx: Context) -> tuple[str, ...]:
182
238
  allowed = self.ALLOWED
183
239
  # Absent is the fixed list, never a crash. The runner always resolves a
@@ -198,6 +254,14 @@ class MarkdownTrees(Check):
198
254
  specs_root = ctx.inputs.get("specs_root")
199
255
  if specs_root is not None:
200
256
  allowed = (*allowed, f"{ctx.relative(specs_root)}/*.md")
257
+ # A declared root admits both suffixes this gate grades, like `docs_root`
258
+ # -- admitting `.md` and reporting `.mdx` beside it would look like half
259
+ # the declaration was ignored. Escaped: `_matches_any` is `fnmatchcase`, so
260
+ # a name carrying `*`, `?` or `[` must be literal or it admits more than
261
+ # it names. Absent declaration = nothing added = stricter.
262
+ for root in self._declared_roots(ctx):
263
+ escaped = glob.escape(root)
264
+ allowed = (*allowed, f"{escaped}/*.md", f"{escaped}/*.mdx")
201
265
  return allowed
202
266
 
203
267
  def _excluded(self, ctx: Context) -> tuple[str, ...]:
@@ -206,7 +270,16 @@ class MarkdownTrees(Check):
206
270
  root = ctx.inputs.get(input_name)
207
271
  if root is not None:
208
272
  excluded = (*excluded, f"{ctx.relative(root)}/{suffix}")
209
- return excluded
273
+ # Root Markdown the repository declared under `[controls.compliance.root]
274
+ # files` (the `root-files` declaration). Matched EXACTLY: `_matches_any`
275
+ # is `fnmatchcase`, so a declared name carrying `*`, `?` or `[` must be
276
+ # escaped or it would exclude more than it names. A root name has no `/`,
277
+ # so a nested file of the same name is not the declared one.
278
+ declared = sorted(
279
+ name for name in declared_root_names(ctx.declarations, ROOT_FILES_DECLARATION)
280
+ if "/" not in name and name.endswith(self.MARKDOWN_SUFFIXES)
281
+ )
282
+ return (*excluded, *(glob.escape(name) for name in declared))
210
283
 
211
284
  def graded(self, ctx: Context, files: FileSet) -> Iterable[Report]:
212
285
  allowed = self._allowed(ctx)