@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.10 → 0.1.26

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 (413) hide show
  1. package/bin/adl +2 -2
  2. package/bin/git +17 -18
  3. package/bin/git-ado +27 -26
  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/bun-compile-build.sh +1 -1
  9. package/bin/lib/fleet-host.sh +62 -0
  10. package/bin/lib/lean-ctx-observe-hooks.sh +9 -9
  11. package/bin/reconcile +693 -470
  12. package/bin/reconcile-host +121 -0
  13. package/bin/setup +219 -0
  14. package/bin/shell-path-policy +15 -5
  15. package/bin/sync-and-reconcile +79 -68
  16. package/bin/validate-manifest +46 -51
  17. package/package.json +2 -12
  18. package/packages/agent-hooks/README.md +14 -9
  19. package/packages/agent-hooks/assets/hooks.json +0 -20
  20. package/packages/agent-hooks/package.json +1 -2
  21. package/packages/agent-hooks/src/cli/args.ts +1 -1
  22. package/packages/agent-hooks/src/cli/doctor.ts +6 -58
  23. package/packages/agent-hooks/src/deploy/deploy.ts +12 -41
  24. package/packages/agent-hooks/src/deploy/doctor.ts +8 -11
  25. package/packages/agent-hooks/src/deploy/index.ts +14 -14
  26. package/packages/agent-hooks/src/deploy/json-merge.ts +7 -8
  27. package/packages/agent-hooks/src/deploy/legacy-lock.ts +2 -2
  28. package/packages/agent-hooks/src/deploy/list.ts +1 -1
  29. package/packages/agent-hooks/src/deploy/lock.ts +31 -12
  30. package/packages/agent-hooks/src/deploy/profile.ts +3 -4
  31. package/packages/agent-hooks/src/deploy/source.ts +1 -1
  32. package/packages/agent-hooks/src/gating/config.ts +10 -24
  33. package/packages/agent-hooks/src/gating/root.ts +3 -3
  34. package/packages/agent-hooks/src/hooks/briefing/handler.ts +2 -2
  35. package/packages/agent-hooks/src/hooks/briefing/index.ts +27 -287
  36. package/packages/agent-hooks/src/hooks/contract.ts +17 -17
  37. package/packages/agent-hooks/src/hooks/crg/background.ts +27 -0
  38. package/packages/agent-hooks/src/hooks/crg/index.ts +7 -6
  39. package/packages/agent-hooks/src/hooks/log.ts +8 -10
  40. package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +1 -1
  41. package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +1 -1
  42. package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +6 -5
  43. package/packages/agent-hooks/src/hosts/registry.ts +1 -1
  44. package/packages/agent-hooks/src/log/adl-dir.ts +44 -0
  45. package/packages/agent-hooks/src/log/writer.ts +16 -2
  46. package/packages/agent-hooks/src/main.ts +7 -41
  47. package/packages/agent-hooks/src/pre-tool-use/handler.ts +71 -82
  48. package/packages/agent-hooks/src/rules/engine.ts +13 -17
  49. package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +3 -3
  50. package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +12 -14
  51. package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +28 -28
  52. package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +8 -9
  53. package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +56 -59
  54. package/packages/agent-hooks/src/rules/impl/adl-identity.ts +16 -23
  55. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +19 -16
  56. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +2 -2
  57. package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +9 -12
  58. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +5 -7
  59. package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +411 -0
  60. package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +4 -5
  61. package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +6 -7
  62. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +77 -70
  63. package/packages/agent-hooks/src/rules/impl/deny-allowlist.ts +159 -0
  64. package/packages/agent-hooks/src/rules/impl/fs-trash.ts +2 -2
  65. package/packages/agent-hooks/src/rules/impl/git-helpers.ts +21 -23
  66. package/packages/agent-hooks/src/rules/impl/git-worktree.ts +14 -28
  67. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -3
  68. package/packages/agent-hooks/src/rules/impl/messages.ts +1 -1
  69. package/packages/agent-hooks/src/rules/impl/pr-completion.ts +1 -1
  70. package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +35 -46
  71. package/packages/agent-hooks/src/rules/impl/tool-find.ts +34 -7
  72. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +34 -12
  73. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +52 -9
  74. package/packages/agent-hooks/src/rules/ports.ts +5 -7
  75. package/packages/agent-hooks/src/rules/registry.ts +4 -6
  76. package/packages/agent-hooks/src/rules/shell/git-identity.ts +86 -101
  77. package/packages/agent-hooks/src/rules/shell/git-internals.ts +1 -1
  78. package/packages/agent-hooks/src/rules/shell/invocation.ts +24 -118
  79. package/packages/agent-hooks/src/rules/shell/piped-script.ts +1 -1
  80. package/packages/agent-hooks/src/rules/shell/prefixes.ts +1 -1
  81. package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +11 -10
  82. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +26 -26
  83. package/packages/agent-hooks/src/rules/shell/substitution.ts +1 -1
  84. package/packages/agent-hooks/src/rules/shell/xargs.ts +2 -2
  85. package/packages/agent-hooks/src/rules/spec.ts +2 -2
  86. package/packages/agent-settings/README.md +287 -0
  87. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +18 -18
  88. package/packages/agent-settings/assets/agents/architect.toml +2 -0
  89. package/packages/agent-settings/assets/agents/code-reviewer.toml +2 -0
  90. package/packages/agent-settings/assets/agents/executor.toml +2 -0
  91. package/packages/agent-settings/assets/agents/explore.toml +2 -0
  92. package/packages/agent-settings/assets/agents/planner.toml +2 -0
  93. package/packages/agent-settings/assets/mcp.json +0 -4
  94. package/packages/agent-settings/deploy-profile.json +139 -0
  95. package/packages/agent-settings/deploy-profile.schema.json +147 -0
  96. package/packages/agent-settings/managed/README.md +29 -30
  97. package/packages/agent-settings/managed/codex-requirements.toml +9 -9
  98. package/packages/agent-settings/package.json +20 -0
  99. package/packages/agent-settings/src/deploy/agent-render.ts +2 -2
  100. package/packages/agent-settings/src/deploy/agent-toml.ts +1 -1
  101. package/packages/agent-settings/src/deploy/agents-deploy.ts +17 -3
  102. package/packages/agent-settings/src/deploy/doctor.ts +3 -83
  103. package/packages/agent-settings/src/deploy/index.ts +6 -33
  104. package/packages/agent-settings/src/deploy/legacy-lock.ts +6 -6
  105. package/packages/agent-settings/src/deploy/list.ts +5 -19
  106. package/packages/agent-settings/src/deploy/lock.ts +8 -18
  107. package/packages/agent-settings/src/deploy/mcp-deploy.ts +10 -5
  108. package/packages/agent-settings/src/deploy/mcp-merge.ts +22 -3
  109. package/packages/agent-settings/src/deploy/mcp-source.ts +1 -1
  110. package/packages/agent-settings/src/deploy/profile.ts +11 -35
  111. package/packages/agent-settings/src/deploy/retire-lean-ctx.ts +184 -0
  112. package/packages/agent-settings/src/deploy/settings.ts +17 -22
  113. package/packages/agent-settings/src/global-surface-sweep.ts +5 -17
  114. package/packages/agent-settings/src/kimi-hooks.ts +339 -192
  115. package/packages/agent-settings/src/opencode-hooks.ts +1 -1
  116. package/packages/agent-settings/tsconfig.json +23 -0
  117. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +120 -0
  118. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +276 -0
  119. package/packages/agent-skills/assets/debug-issue/SKILL.md +8 -7
  120. package/packages/agent-skills/assets/explore-codebase/SKILL.md +13 -13
  121. package/packages/agent-skills/assets/refactor-safely/SKILL.md +12 -11
  122. package/packages/agent-skills/assets/review-changes/SKILL.md +7 -7
  123. package/packages/agent-skills/assets/testing/references/e2e.md +37 -0
  124. package/packages/agent-skills/assets/testing/references/integration.md +37 -0
  125. package/packages/agent-skills/assets/testing/references/unit.md +32 -0
  126. package/packages/agent-skills/dist/deploy.d.ts +2 -0
  127. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  128. package/packages/agent-skills/dist/deploy.js +27 -1
  129. package/packages/agent-skills/dist/deploy.js.map +1 -1
  130. package/packages/agent-skills/dist/doctor.d.ts +0 -2
  131. package/packages/agent-skills/dist/doctor.d.ts.map +1 -1
  132. package/packages/agent-skills/dist/doctor.js +1 -1
  133. package/packages/agent-skills/dist/doctor.js.map +1 -1
  134. package/packages/agent-skills/dist/fs-utils.d.ts +0 -1
  135. package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -1
  136. package/packages/agent-skills/dist/fs-utils.js +1 -1
  137. package/packages/agent-skills/dist/fs-utils.js.map +1 -1
  138. package/packages/agent-skills/package.json +1 -1
  139. package/packages/agent-skills/src/deploy.ts +29 -1
  140. package/packages/agent-skills/src/doctor.ts +1 -1
  141. package/packages/agent-skills/src/fs-utils.ts +1 -1
  142. package/packages/azdo/README.md +10 -9
  143. package/packages/azdo/package.json +2 -5
  144. package/packages/azdo/src/azdo-pl.ts +2 -54
  145. package/packages/azdo/src/azdo-pr-review.ts +4 -4
  146. package/packages/azdo/src/azdo-repo-doctor.ts +101 -52
  147. package/packages/azdo/src/azdo-wit.ts +41 -2
  148. package/packages/azdo/src/lib/agent-identity.ts +12 -14
  149. package/packages/azdo/src/lib/agent-token.ts +2 -2
  150. package/packages/azdo/src/lib/client.ts +0 -5
  151. package/packages/azdo/src/lib/graph-identity.ts +4 -4
  152. package/packages/azdo/src/workflow-rules-matrix.csv +85 -0
  153. package/packages/azdo/src/workflow-rules.ts +128 -0
  154. package/packages/fleet/manifest.json +38 -33
  155. package/packages/fleet/manifest.schema.json +67 -65
  156. package/packages/fleet/src/default-agent-file.ts +3 -3
  157. package/packages/fleet/src/install-adl-package.ts +73 -15
  158. package/packages/fleet/src/install-embedding-model.ts +14 -14
  159. package/packages/fleet/src/managed-settings.ts +7 -7
  160. package/packages/fleet/src/repo-maintain.ts +439 -0
  161. package/packages/fleet/src/scratch-reap.ts +6 -6
  162. package/packages/fleet/src/vscode-settings.ts +5 -5
  163. package/packages/repo-controls/README.md +141 -43
  164. package/packages/repo-controls/assets/promote.ts +4 -4
  165. package/packages/repo-controls/assets/reconcile-guard.sh +10 -46
  166. package/packages/repo-controls/assets/version-bump.sh +31 -84
  167. package/packages/repo-controls/bunfig.toml +6 -7
  168. package/packages/repo-controls/package/adl-compliance/AGENTS.md +5 -6
  169. package/packages/repo-controls/package/adl-compliance/conftest.py +3 -1
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +4 -16
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +9 -2
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +1 -0
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +11 -0
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/__init__.py +7 -0
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/check.py +266 -0
  176. 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
  177. 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
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.hidden.md +1 -0
  179. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/a.md +1 -0
  180. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/b.md +1 -0
  181. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/data.csv +1 -0
  182. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/diagram.png +1 -0
  183. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/mock.png +1 -0
  184. 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
  185. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs.md +1 -0
  186. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/grants.yaml +4 -0
  187. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p1.md +1 -0
  188. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p2.md +1 -0
  189. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p3.md +1 -0
  190. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p4.md +1 -0
  191. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p5.md +1 -0
  192. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/build.md +1 -0
  193. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/overview.md +1 -0
  194. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/install.md +1 -0
  195. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/start.md +1 -0
  196. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/grants.yaml +4 -0
  197. 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
  198. 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
  199. 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
  200. 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
  201. 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
  202. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/a.md +1 -0
  203. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/b.md +1 -0
  204. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/c.md +1 -0
  205. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/grants.yaml +4 -0
  206. 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
  207. 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
  208. 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
  209. 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
  210. 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
  211. 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
  212. 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
  213. 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
  214. 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
  215. 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
  216. 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
  217. 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
  218. 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
  219. 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
  220. 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
  221. 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
  222. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/grants.yaml +4 -0
  223. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/a.md +1 -0
  224. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/b.md +1 -0
  225. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/c.md +1 -0
  226. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/grants.yaml +4 -0
  227. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/a.md +1 -0
  228. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/b.md +1 -0
  229. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/c.md +1 -0
  230. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/grants.yaml +4 -0
  231. 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
  232. 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
  233. 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
  234. 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
  235. 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
  236. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/grants.yaml +4 -0
  237. 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
  238. 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
  239. 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
  240. 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
  241. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/grants.yaml +4 -0
  242. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/a.md +1 -0
  243. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/b.md +1 -0
  244. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/c.md +1 -0
  245. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/d.md +1 -0
  246. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/a.md +1 -0
  247. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/b.md +1 -0
  248. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/grants.yaml +6 -0
  249. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/a.md +1 -0
  250. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/b.md +1 -0
  251. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/c.md +1 -0
  252. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/grants.yaml +3 -0
  253. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/test_check.py +345 -0
  254. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +18 -5
  255. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +35 -0
  256. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/__init__.py +7 -0
  257. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/check.py +91 -0
  258. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/AGENTS.md +3 -0
  259. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/CLAUDE.md +3 -0
  260. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/-leading-hyphen.md +3 -0
  261. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/AGENTS.md +3 -0
  262. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/double--hyphen.md +3 -0
  263. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/getting started.md +3 -0
  264. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/nested/page-here.md +3 -0
  265. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/trailing-hyphen-.md +3 -0
  266. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +4 -0
  267. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/user_profile.md +3 -0
  268. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/UI-CONTRACT.png +2 -0
  269. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/diagram.png +1 -0
  270. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/index.md +4 -0
  271. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/notes.md +3 -0
  272. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/reference.html +1 -0
  273. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/2026-10-experiment.md +3 -0
  274. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +6 -0
  275. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs.md +5 -0
  276. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +194 -0
  277. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +14 -2
  278. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +5 -5
  279. 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
  280. 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
  281. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +58 -6
  282. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +0 -1
  283. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +64 -17
  284. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +60 -9
  285. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +17 -0
  286. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +13 -0
  287. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +0 -1
  288. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +0 -1
  289. 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
  290. 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
  291. 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
  292. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs-okf-profiles.yaml +5 -0
  293. 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
  294. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +0 -1
  295. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/experiment-ok.md +15 -0
  296. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +15 -0
  297. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +16 -0
  298. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +11 -0
  299. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +11 -0
  300. 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
  301. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-sources.md +10 -0
  302. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +11 -0
  303. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +11 -0
  304. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +16 -0
  305. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +14 -0
  306. 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
  307. 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
  308. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/operations/runbook.md +26 -0
  309. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +228 -2
  310. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +5 -0
  311. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +11 -2
  312. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +5 -3
  313. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +160 -44
  314. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +0 -2
  315. 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
  316. 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
  317. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/grants.yaml +3 -0
  318. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +0 -2
  319. 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
  320. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +5 -0
  321. 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
  322. 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
  323. 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
  324. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +4 -0
  325. 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
  326. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +5 -0
  327. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +0 -2
  328. 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
  329. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/grants.yaml +3 -0
  330. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/register.py +127 -0
  331. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +265 -29
  332. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +75 -2
  333. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +194 -0
  334. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +56 -10
  335. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +256 -0
  336. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +40 -12
  337. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +108 -0
  338. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +1 -1
  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 -10
  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
  411. package/packages/fleet/jobs/git-maintain/src/main.ts +0 -848
  412. package/packages/fleet/systemd/adl-azure-agent-update.service +0 -13
  413. package/packages/fleet/systemd/adl-azure-agent-update.timer +0 -10
@@ -22,9 +22,13 @@ from __future__ import annotations
22
22
 
23
23
  import pathlib
24
24
 
25
+ import pytest
26
+
27
+ from adl_compliance import runner
25
28
  from adl_compliance.checks.layout.markdown_trees import MarkdownTrees
26
29
  from adl_compliance.core import scope
27
30
  from adl_compliance.core.context import Context, FileSet
31
+ from adl_compliance.core.errors import CouldNotRun
28
32
  from adl_compliance.core.verdict import Outcome, decide
29
33
  from adl_compliance.policy.registry import load
30
34
 
@@ -218,3 +222,193 @@ def test_a_redeclared_estate_still_allows_and_excludes_the_resolved_entries(
218
222
  f"real files under redeclared roots were wrongly reported outside "
219
223
  f"the allowed docs trees: {findings}"
220
224
  )
225
+
226
+
227
+ # ---------------------------------------------- per-repository root declarations
228
+ def graded_declaring(tmp_path, declarations: dict[str, str], given: tuple[str, ...]):
229
+ """`graded`, plus what the repository declared about its own root
230
+ (`[controls.compliance.root]` arrives as the `root-files` / `root-dirs`
231
+ declarations)."""
232
+ ctx = Context(root=tmp_path, row=ROW, declarations=declarations)
233
+ findings = tuple(MarkdownTrees().graded(ctx, FileSet(paths=given)))
234
+ return decide(ROW, findings, 0, ())
235
+
236
+
237
+ def test_a_non_standard_root_markdown_file_is_reported_without_a_declaration(tmp_path) -> None:
238
+ """STATUS.md and DRIFT.md are not on the central list any more, so the
239
+ exclusion they used to ride in on is gone."""
240
+ verdict = graded_declaring(tmp_path, {}, ("STATUS.md", "DRIFT.md"))
241
+ assert sorted(f.path for f in verdict.findings) == ["DRIFT.md", "STATUS.md"], verdict.render()
242
+ assert all(
243
+ f.message == "documentation file outside the allowed docs trees" for f in verdict.findings
244
+ )
245
+
246
+
247
+ def test_a_declared_root_markdown_file_is_excluded(tmp_path) -> None:
248
+ verdict = graded_declaring(
249
+ tmp_path, {"root-files": "STATUS.md, DRIFT.md"}, ("STATUS.md", "DRIFT.md"),
250
+ )
251
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
252
+
253
+
254
+ def test_a_declaration_excludes_only_the_names_it_lists(tmp_path) -> None:
255
+ """Without this, the case above also passes against a gate that stops
256
+ grading Markdown as soon as any declaration is present."""
257
+ verdict = graded_declaring(
258
+ tmp_path, {"root-files": "STATUS.md"}, ("STATUS.md", "STRAY.md", "backend/notes.md"),
259
+ )
260
+ assert sorted(f.path for f in verdict.findings) == ["STRAY.md", "backend/notes.md"], (
261
+ verdict.render()
262
+ )
263
+
264
+
265
+ def test_a_declared_root_name_is_excluded_at_the_root_only(tmp_path) -> None:
266
+ """`fnmatch`'s `*` crosses `/`, but the declared name is matched EXACTLY: a
267
+ nested file of the same name is not the declared root file."""
268
+ verdict = graded_declaring(tmp_path, {"root-files": "STATUS.md"}, ("backend/STATUS.md",))
269
+ assert [f.path for f in verdict.findings] == ["backend/STATUS.md"], verdict.render()
270
+
271
+
272
+ def test_a_declared_name_is_matched_literally_not_as_a_glob(tmp_path) -> None:
273
+ """`_matches_any` uses `fnmatchcase`, so a declared name carrying glob
274
+ characters must be escaped: `a[b].md` excludes that file and not `ab.md`."""
275
+ verdict = graded_declaring(tmp_path, {"root-files": "a[b].md"}, ("a[b].md", "ab.md"))
276
+ assert [f.path for f in verdict.findings] == ["ab.md"], verdict.render()
277
+ verdict = graded_declaring(tmp_path, {"root-files": "*.md"}, ("anything.md",))
278
+ assert [f.path for f in verdict.findings] == ["anything.md"], verdict.render()
279
+
280
+
281
+ def test_a_root_dirs_declaration_does_not_exclude_a_root_markdown_file(tmp_path) -> None:
282
+ verdict = graded_declaring(tmp_path, {"root-dirs": "STATUS.md"}, ("STATUS.md",))
283
+ assert [f.path for f in verdict.findings] == ["STATUS.md"], verdict.render()
284
+
285
+
286
+ def test_a_declared_root_mdx_file_is_excluded_too(tmp_path) -> None:
287
+ """This gate grades `.md` and `.mdx`; a declared root name in either
288
+ extension must be honoured or the declaration looks ignored."""
289
+ verdict = graded_declaring(tmp_path, {"root-files": "STATUS.mdx"}, ("STATUS.mdx",))
290
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
291
+
292
+
293
+ # ---------------------------------------- per-repository Markdown roots
294
+ # `[controls.compliance.markdown].roots` arrives as one `markdown-roots`
295
+ # declaration, a comma-separated list of repository-relative directories. A
296
+ # declared root only ever ADDS allowlist entries, so absent means stricter --
297
+ # the same direction `specs_root` has always had, reached through the config
298
+ # route instead of an input only a hand-built Context could carry.
299
+ DECLARED_ROOTS = "markdown-roots"
300
+ CONTENT_PAGES = (
301
+ "apps/marketing/src/content/use-cases/a.md",
302
+ "apps/marketing/index.md",
303
+ "apps/platform/src/content/docs/docs/b.md",
304
+ )
305
+
306
+
307
+ def test_without_a_markdown_roots_declaration_content_pages_are_reported(tmp_path) -> None:
308
+ """The measured finding this declaration exists for: Astro content-collection
309
+ pages sit under `apps/`, outside every tree LAY-006 knows. Absent the
310
+ declaration they are still reported -- the declaration widens, nothing else does."""
311
+ verdict = graded_declaring(tmp_path, {}, CONTENT_PAGES)
312
+ assert sorted(f.path for f in verdict.findings) == sorted(CONTENT_PAGES), verdict.render()
313
+
314
+
315
+ def test_a_declared_markdown_root_admits_every_page_beneath_it(tmp_path) -> None:
316
+ verdict = graded_declaring(
317
+ tmp_path,
318
+ {DECLARED_ROOTS: "apps/marketing,apps/platform/src/content"},
319
+ CONTENT_PAGES,
320
+ )
321
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
322
+
323
+
324
+ def test_a_declared_markdown_root_admits_mdx_too(tmp_path) -> None:
325
+ """This gate grades `.md` and `.mdx`; a root admitting one suffix and
326
+ reporting the other would look like the declaration was half ignored."""
327
+ verdict = graded_declaring(
328
+ tmp_path,
329
+ {DECLARED_ROOTS: "apps/marketing"},
330
+ ("apps/marketing/src/content/page.mdx", "apps/marketing/page.md"),
331
+ )
332
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
333
+
334
+
335
+ def test_each_declared_markdown_root_admits_only_itself(tmp_path) -> None:
336
+ """A declared root must not become a hole: a sibling directory sharing its
337
+ prefix, a different subtree of the same parent, and an undeclared root are
338
+ all still reported."""
339
+ stray = (
340
+ "apps/marketing-old/a.md",
341
+ "apps/other/b.md",
342
+ "apps/platform/src/other/c.md",
343
+ "apps/platform/src/content-extra/d.md",
344
+ "marketing/e.md",
345
+ )
346
+ verdict = graded_declaring(
347
+ tmp_path,
348
+ {DECLARED_ROOTS: "apps/marketing,apps/platform/src/content"},
349
+ (*CONTENT_PAGES, *stray),
350
+ )
351
+ assert sorted(f.path for f in verdict.findings) == sorted(stray), verdict.render()
352
+
353
+
354
+ def test_declaring_only_one_root_leaves_the_other_reported(tmp_path) -> None:
355
+ verdict = graded_declaring(tmp_path, {DECLARED_ROOTS: "apps/marketing"}, CONTENT_PAGES)
356
+ assert [f.path for f in verdict.findings] == ["apps/platform/src/content/docs/docs/b.md"], (
357
+ verdict.render()
358
+ )
359
+
360
+
361
+ def test_a_declared_markdown_root_tolerates_whitespace_empties_and_a_trailing_slash(
362
+ tmp_path,
363
+ ) -> None:
364
+ verdict = graded_declaring(
365
+ tmp_path,
366
+ {DECLARED_ROOTS: " apps/marketing/ , ,apps/platform/src/content// "},
367
+ CONTENT_PAGES,
368
+ )
369
+ assert verdict.outcome is Outcome.CLEAN, verdict.render()
370
+
371
+
372
+ def test_a_declared_markdown_root_is_matched_literally_not_as_a_glob(tmp_path) -> None:
373
+ """`_matches_any` is `fnmatchcase`, so a declared root carrying glob
374
+ characters must be escaped or it would admit more than it names."""
375
+ verdict = graded_declaring(
376
+ tmp_path, {DECLARED_ROOTS: "apps/[x]"}, ("apps/[x]/a.md", "apps/x/b.md"),
377
+ )
378
+ assert [f.path for f in verdict.findings] == ["apps/x/b.md"], verdict.render()
379
+ verdict = graded_declaring(tmp_path, {DECLARED_ROOTS: "apps/*"}, ("apps/anything/a.md",))
380
+ assert [f.path for f in verdict.findings] == ["apps/anything/a.md"], verdict.render()
381
+
382
+
383
+ def test_the_declared_roots_are_named_in_the_remedy(tmp_path) -> None:
384
+ """The remedy quotes the SAME set the rule graded against, so a reader is
385
+ never sent somewhere the gate does not accept -- and a declared root is one
386
+ of the places it does."""
387
+ verdict = graded_declaring(tmp_path, {DECLARED_ROOTS: "apps/marketing"}, ("stray/x.md",))
388
+ assert "apps/marketing/*.md" in verdict.findings[0].fix, verdict.render()
389
+
390
+
391
+ @pytest.mark.parametrize(
392
+ "bad",
393
+ ["/etc", "/", "..", "../outside", "apps/../secrets", ".", "./apps", "apps/./x", "apps//x"],
394
+ )
395
+ def test_a_malformed_declared_markdown_root_is_could_not_run_never_a_wider_allowlist(
396
+ tmp_path, bad: str
397
+ ) -> None:
398
+ """An absolute path, a `..` or `.` segment, or an empty segment names no
399
+ directory this repository owns. Treating it as a root could only WIDEN the
400
+ allowlist, so the gate refuses to run rather than grade against it."""
401
+ ctx = Context(root=tmp_path, row=ROW, declarations={DECLARED_ROOTS: bad})
402
+ with pytest.raises(CouldNotRun, match=DECLARED_ROOTS):
403
+ tuple(MarkdownTrees().graded(ctx, FileSet(paths=("stray/x.md",))))
404
+
405
+
406
+ def test_a_malformed_declared_markdown_root_is_could_not_run_through_the_runner(tmp_path) -> None:
407
+ (tmp_path / "stray").mkdir()
408
+ (tmp_path / "stray" / "x.md").write_text("x\n", encoding="utf-8")
409
+ verdict = runner.run(
410
+ ROW, tmp_path, ("stray/x.md",), exhaustive=True,
411
+ declarations={DECLARED_ROOTS: "../outside"},
412
+ )
413
+ assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
414
+ assert DECLARED_ROOTS in verdict.message
@@ -1,12 +1,17 @@
1
- """LAY-016 — no open plans and no open gaps: the limit is zero.
1
+ """LAY-016 — at most ten open plans and at most ten open gaps, by default.
2
2
 
3
3
  Plans and linked gap items use the exact board-state vocabulary shared with
4
4
  LAY-009. An unraised gap and a gap linked to active work both remain open. A
5
5
  gap linked to terminal work is stale register content and must be deleted.
6
6
 
7
- OPEN_LIMIT was 10 until 2026-09-27 (Tarun: "limit is zero" — see
8
- `docs/operations/repo-controls-copy-notes.md` for the quote and decision).
9
- Any open plan or open gap row is now a finding; there is no headroom.
7
+ The two caps are independent and each is `OPEN_LIMIT` (10) unless the
8
+ repository declares its own, as `--declare open-gaps=N` / `--declare
9
+ open-plans=N` (`[controls.compliance.limits].open_gaps` / `.open_plans` in its
10
+ adl-config.toml). `open_gaps_limit` / `open_plans_limit` resolve them: absent
11
+ is the default, and a value that is not a base-10 positive integer is
12
+ could-not-run, never a quiet fall back to the default. The engine default is
13
+ not raised; a repository states the count it carries today and ratchets it
14
+ down.
10
15
  """
11
16
 
12
17
  from __future__ import annotations
@@ -28,7 +33,11 @@ class OpenWorkCap(Check):
28
33
  inputs: ClassVar[dict[str, str]] = {"plans_root": "<plans>"}
29
34
  whole_scope: ClassVar[bool] = True
30
35
 
36
+ # The engine default for each cap. A repository states its own with the
37
+ # `open-gaps` / `open-plans` declarations; see `open_gaps_limit`.
31
38
  OPEN_LIMIT: ClassVar[int] = 10
39
+ OPEN_GAPS_DECLARATION: ClassVar[str] = "open-gaps"
40
+ OPEN_PLANS_DECLARATION: ClassVar[str] = "open-plans"
32
41
  FEATURES: ClassVar[str] = "features"
33
42
  SPEC: ClassVar[str] = "spec.md"
34
43
  REGISTER: ClassVar[str] = "GAPS-AND-FINDINGS.md"
@@ -48,12 +57,46 @@ class OpenWorkCap(Check):
48
57
  UNRAISED: ClassVar[frozenset[str]] = frozenset(("", "-", "—", "TBD"))
49
58
  EXCERPT: ClassVar[int] = 60
50
59
 
60
+ def open_gaps_limit(self, ctx: Context) -> int:
61
+ """The open-gap cap in force: the declared one, else `OPEN_LIMIT`."""
62
+ return self._declared_limit(ctx, self.OPEN_GAPS_DECLARATION, "open_gaps")
63
+
64
+ def open_plans_limit(self, ctx: Context) -> int:
65
+ """The open-plan cap in force: the declared one, else `OPEN_LIMIT`."""
66
+ return self._declared_limit(ctx, self.OPEN_PLANS_DECLARATION, "open_plans")
67
+
68
+ def _declared_limit(self, ctx: Context, declaration: str, key: str) -> int:
69
+ """One cap's declared value, else `OPEN_LIMIT`.
70
+
71
+ ASCII digits only, so `+20`, `1_0`, ` 20` and full-width digits are
72
+ refused, which `int()` alone would accept. A declaration that cannot be
73
+ read as a positive integer is could-not-run: falling back to the default
74
+ would grade the repository against a cap it explicitly replaced.
75
+ """
76
+ declared = ctx.declarations.get(declaration)
77
+ if declared is None:
78
+ return self.OPEN_LIMIT
79
+ if re.fullmatch(r"[0-9]+", declared) is None or int(declared) < 1:
80
+ raise CouldNotRun(
81
+ f"declaration {declaration}={declared!r} is not a base-10 positive "
82
+ f"integer; set [controls.compliance.limits].{key} to a whole number "
83
+ "of at least 1"
84
+ )
85
+ return int(declared)
86
+
51
87
  def graded(self, ctx: Context, files: FileSet) -> Iterable[Finding]:
88
+ # Both caps are resolved before either is graded, so an unreadable
89
+ # declaration is could-not-run even when the cap it names is not the
90
+ # one being exceeded.
91
+ plans_limit = self.open_plans_limit(ctx)
92
+ gaps_limit = self.open_gaps_limit(ctx)
93
+
52
94
  yield from self._cap(
53
95
  "plans",
54
96
  f"{self._plans(ctx)}/{self.FEATURES}",
55
97
  self._open_plans(ctx, set(files)),
56
- "finish or remove every open Feature; none may stay open",
98
+ plans_limit,
99
+ f"finish or remove open Features until no more than {plans_limit} remain",
57
100
  )
58
101
 
59
102
  open_gaps, stale_gaps = self._gaps(ctx, set(files))
@@ -62,16 +105,19 @@ class OpenWorkCap(Check):
62
105
  "gaps",
63
106
  f"{self._plans(ctx)}/{self.REGISTER}",
64
107
  open_gaps,
65
- "finish linked work and delete its gap row, or resolve unraised gaps; "
66
- "none may stay open",
108
+ gaps_limit,
109
+ "finish linked work and delete its gap row, or resolve unraised gaps, "
110
+ f"until no more than {gaps_limit} remain",
67
111
  )
68
112
 
69
- def _cap(self, subject: str, path: str, open_items: list[str], fix: str) -> Iterator[Finding]:
70
- if len(open_items) <= self.OPEN_LIMIT:
113
+ def _cap(
114
+ self, subject: str, path: str, open_items: list[str], limit: int, fix: str
115
+ ) -> Iterator[Finding]:
116
+ if len(open_items) <= limit:
71
117
  return
72
118
  yield self.report(
73
119
  path,
74
- f"{len(open_items)} open {subject}, cap is {self.OPEN_LIMIT}: "
120
+ f"{len(open_items)} open {subject}, cap is {limit}: "
75
121
  + "; ".join(open_items),
76
122
  fix,
77
123
  )
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import pathlib
6
+ import re
6
7
 
7
8
  import pytest
8
9
 
@@ -328,3 +329,258 @@ def test_exactly_ten_open_gaps_pass_and_eleven_fail_with_every_row(
328
329
  finding = next(f for f in eleven.findings if "open gaps" in f.message)
329
330
  for n in range(1, 12):
330
331
  assert f"Unraised gap {n}" in finding.message
332
+
333
+
334
+ # ------------------------------------------------- the two caps are declarable
335
+ # `[controls.compliance.limits].open_gaps` / `.open_plans` reach the engine as
336
+ # `--declare open-gaps=N` / `--declare open-plans=N`. The engine default stays
337
+ # ten for each; a repository that carries more open work today states its own
338
+ # cap in its adl-config.toml, and a value the engine cannot read is a
339
+ # could-not-run, never a quiet fall back to ten. The two caps are independent.
340
+ GAPS_DECLARATION = "open-gaps"
341
+ PLANS_DECLARATION = "open-plans"
342
+
343
+
344
+ def grade_declared(
345
+ root: pathlib.Path,
346
+ declarations: dict[str, str],
347
+ paths: tuple[str, ...] | None = None,
348
+ ):
349
+ """`grade`, with declarations. Does not catch CouldNotRun: some tests expect it raised."""
350
+ if paths is None:
351
+ paths = tuple(str(path.relative_to(root)) for path in root.rglob("*") if path.is_file())
352
+ ctx = Context(
353
+ row=ROW, root=root, inputs={"plans_root": root / "plans"}, declarations=declarations
354
+ )
355
+ return decide(ROW, tuple(OpenWorkCap().run(ctx, FileSet(paths=paths))), advisories=())
356
+
357
+
358
+ def gaps(count: int) -> dict[str, str]:
359
+ return register(*(unraised(n) for n in range(1, count + 1)))
360
+
361
+
362
+ def test_no_declaration_keeps_the_engine_default_of_ten() -> None:
363
+ ctx = Context(row=ROW, root=CLEAN)
364
+ assert OpenWorkCap().open_gaps_limit(ctx) == 10
365
+ assert OpenWorkCap().open_plans_limit(ctx) == 10
366
+ assert OpenWorkCap.OPEN_LIMIT == 10
367
+
368
+
369
+ def test_a_declared_gap_cap_is_the_effective_gap_cap_and_leaves_the_plan_cap() -> None:
370
+ ctx = Context(row=ROW, root=CLEAN, declarations={GAPS_DECLARATION: "37"})
371
+ assert OpenWorkCap().open_gaps_limit(ctx) == 37
372
+ assert OpenWorkCap().open_plans_limit(ctx) == 10
373
+
374
+
375
+ def test_a_declared_plan_cap_is_the_effective_plan_cap_and_leaves_the_gap_cap() -> None:
376
+ ctx = Context(row=ROW, root=CLEAN, declarations={PLANS_DECLARATION: "11"})
377
+ assert OpenWorkCap().open_plans_limit(ctx) == 11
378
+ assert OpenWorkCap().open_gaps_limit(ctx) == 10
379
+
380
+
381
+ def test_a_declared_gap_cap_accepts_gaps_the_default_would_refuse(
382
+ tmp_path: pathlib.Path,
383
+ ) -> None:
384
+ root = build(tmp_path, gaps(14))
385
+ assert grade(root).outcome is Outcome.FINDINGS
386
+ result = grade_declared(root, {GAPS_DECLARATION: "14"})
387
+ assert result.outcome is Outcome.CLEAN, result.render()
388
+ assert result.findings == ()
389
+
390
+
391
+ def test_a_declared_plan_cap_accepts_plans_the_default_would_refuse(
392
+ tmp_path: pathlib.Path,
393
+ ) -> None:
394
+ root = build(tmp_path, features(12))
395
+ assert grade(root).outcome is Outcome.FINDINGS
396
+ result = grade_declared(root, {PLANS_DECLARATION: "12"})
397
+ assert result.outcome is Outcome.CLEAN, result.render()
398
+ assert result.findings == ()
399
+
400
+
401
+ def test_over_a_declared_gap_cap_the_finding_names_the_declared_cap_not_ten(
402
+ tmp_path: pathlib.Path,
403
+ ) -> None:
404
+ result = grade_declared(build(tmp_path, gaps(16)), {GAPS_DECLARATION: "15"})
405
+ assert result.outcome is Outcome.FINDINGS, result.render()
406
+ assert len(result.findings) == 1
407
+ text = result.render()
408
+ assert "16 open gaps, cap is 15" in text, text
409
+ assert "cap is 10" not in text, text
410
+ for n in range(1, 17):
411
+ assert f"Unraised gap {n}" in text
412
+
413
+
414
+ def test_over_a_declared_plan_cap_the_finding_names_the_declared_cap_not_ten(
415
+ tmp_path: pathlib.Path,
416
+ ) -> None:
417
+ result = grade_declared(build(tmp_path, features(13)), {PLANS_DECLARATION: "12"})
418
+ assert result.outcome is Outcome.FINDINGS, result.render()
419
+ assert len(result.findings) == 1
420
+ text = result.render()
421
+ assert "13 open plans, cap is 12" in text, text
422
+ assert "cap is 10" not in text, text
423
+
424
+
425
+ @pytest.mark.parametrize("cap", (1, 3, 10, 25))
426
+ def test_exactly_the_declared_gap_cap_passes_and_one_more_fails(
427
+ tmp_path: pathlib.Path, cap: int
428
+ ) -> None:
429
+ declared = {GAPS_DECLARATION: str(cap)}
430
+ at_cap = grade_declared(build(tmp_path / "at", gaps(cap)), declared)
431
+ over = grade_declared(build(tmp_path / "over", gaps(cap + 1)), declared)
432
+ assert at_cap.outcome is Outcome.CLEAN, at_cap.render()
433
+ assert over.outcome is Outcome.FINDINGS, over.render()
434
+ assert f"{cap + 1} open gaps, cap is {cap}" in over.render()
435
+
436
+
437
+ @pytest.mark.parametrize("cap", (1, 3, 10, 25))
438
+ def test_exactly_the_declared_plan_cap_passes_and_one_more_fails(
439
+ tmp_path: pathlib.Path, cap: int
440
+ ) -> None:
441
+ declared = {PLANS_DECLARATION: str(cap)}
442
+ at_cap = grade_declared(build(tmp_path / "at", features(cap)), declared)
443
+ over = grade_declared(build(tmp_path / "over", features(cap + 1)), declared)
444
+ assert at_cap.outcome is Outcome.CLEAN, at_cap.render()
445
+ assert over.outcome is Outcome.FINDINGS, over.render()
446
+ assert f"{cap + 1} open plans, cap is {cap}" in over.render()
447
+
448
+
449
+ def test_a_declared_cap_below_ten_refuses_what_the_default_would_accept(
450
+ tmp_path: pathlib.Path,
451
+ ) -> None:
452
+ root = build(tmp_path, {**gaps(5), **features(5)})
453
+ assert grade(root).outcome is Outcome.CLEAN
454
+ result = grade_declared(root, {GAPS_DECLARATION: "4", PLANS_DECLARATION: "4"})
455
+ assert result.outcome is Outcome.FINDINGS, result.render()
456
+ text = result.render()
457
+ assert "5 open gaps, cap is 4" in text, text
458
+ assert "5 open plans, cap is 4" in text, text
459
+
460
+
461
+ def test_declaring_ten_grades_exactly_like_no_declaration(tmp_path: pathlib.Path) -> None:
462
+ root = build(tmp_path, {**gaps(11), **features(11)})
463
+ declared = grade_declared(root, {GAPS_DECLARATION: "10", PLANS_DECLARATION: "10"})
464
+ default = grade(root)
465
+ assert declared.outcome is default.outcome is Outcome.FINDINGS
466
+ assert [f.render() for f in declared.findings] == [f.render() for f in default.findings]
467
+ assert "11 open gaps, cap is 10" in declared.render()
468
+ assert "11 open plans, cap is 10" in declared.render()
469
+
470
+
471
+ def test_the_gap_cap_does_not_move_the_plan_cap(tmp_path: pathlib.Path) -> None:
472
+ root = build(tmp_path, {**gaps(15), **features(11)})
473
+ result = grade_declared(root, {GAPS_DECLARATION: "15"})
474
+ assert result.outcome is Outcome.FINDINGS, result.render()
475
+ assert len(result.findings) == 1
476
+ text = result.render()
477
+ assert "11 open plans, cap is 10" in text, text
478
+ assert "open gaps" not in text, text
479
+
480
+
481
+ def test_the_plan_cap_does_not_move_the_gap_cap(tmp_path: pathlib.Path) -> None:
482
+ root = build(tmp_path, {**gaps(11), **features(15)})
483
+ result = grade_declared(root, {PLANS_DECLARATION: "15"})
484
+ assert result.outcome is Outcome.FINDINGS, result.render()
485
+ assert len(result.findings) == 1
486
+ text = result.render()
487
+ assert "11 open gaps, cap is 10" in text, text
488
+ assert "open plans" not in text, text
489
+
490
+
491
+ def test_both_caps_declared_together_each_apply_to_their_own_count(
492
+ tmp_path: pathlib.Path,
493
+ ) -> None:
494
+ root = build(tmp_path, {**gaps(37), **features(11)})
495
+ result = grade_declared(root, {GAPS_DECLARATION: "37", PLANS_DECLARATION: "11"})
496
+ assert result.outcome is Outcome.CLEAN, result.render()
497
+ one_below = grade_declared(root, {GAPS_DECLARATION: "36", PLANS_DECLARATION: "10"})
498
+ assert one_below.outcome is Outcome.FINDINGS, one_below.render()
499
+ text = one_below.render()
500
+ assert "37 open gaps, cap is 36" in text, text
501
+ assert "11 open plans, cap is 10" in text, text
502
+
503
+
504
+ def test_an_unrelated_declaration_does_not_change_either_cap(tmp_path: pathlib.Path) -> None:
505
+ root = build(tmp_path, {**gaps(11), **features(11)})
506
+ result = grade_declared(
507
+ root,
508
+ {"license": "Proprietary", "root-dirs": "apps", "scripts-direct-files": "20"},
509
+ )
510
+ text = result.render()
511
+ assert "11 open gaps, cap is 10" in text, text
512
+ assert "11 open plans, cap is 10" in text, text
513
+
514
+
515
+ def test_the_remedy_states_the_effective_cap_and_never_hard_codes_ten(
516
+ tmp_path: pathlib.Path,
517
+ ) -> None:
518
+ root = build(tmp_path, {**gaps(16), **features(13)})
519
+ result = grade_declared(root, {GAPS_DECLARATION: "15", PLANS_DECLARATION: "12"})
520
+ by_subject = {
521
+ ("gaps" if "open gaps" in f.message else "plans"): f.fix for f in result.findings
522
+ }
523
+ assert "no more than 15 remain" in by_subject["gaps"], by_subject
524
+ assert "no more than 12 remain" in by_subject["plans"], by_subject
525
+ for fix in by_subject.values():
526
+ assert re.search(r"\bten\b", fix, re.IGNORECASE) is None, fix
527
+
528
+
529
+ def test_the_default_remedy_states_the_cap_as_a_number(tmp_path: pathlib.Path) -> None:
530
+ result = grade(build(tmp_path, {**gaps(11), **features(11)}))
531
+ assert len(result.findings) == 2
532
+ for finding in result.findings:
533
+ assert "no more than 10 remain" in finding.fix, finding.fix
534
+ assert re.search(r"\bten\b", finding.fix, re.IGNORECASE) is None, finding.fix
535
+
536
+
537
+ UNREADABLE = ["abc", "0", "-3", "2.5", "", "+20", "1_0", "20"]
538
+
539
+
540
+ @pytest.mark.parametrize("value", UNREADABLE)
541
+ @pytest.mark.parametrize("name", (GAPS_DECLARATION, PLANS_DECLARATION))
542
+ def test_an_unreadable_declared_cap_could_not_run(
543
+ tmp_path: pathlib.Path, name: str, value: str
544
+ ) -> None:
545
+ """Never a silent fall back to ten: not a base-10 positive integer is loud."""
546
+ root = build(tmp_path, {**features(1), **gaps(1)})
547
+ with pytest.raises(CouldNotRun) as raised:
548
+ grade_declared(root, {name: value})
549
+ message = str(raised.value)
550
+ assert name in message, message
551
+ assert repr(value) in message, message
552
+ assert could_not_run(ROW, message).outcome is Outcome.COULD_NOT_RUN
553
+
554
+
555
+ def test_a_bad_declaration_for_the_cap_that_is_not_exceeded_is_still_could_not_run(
556
+ tmp_path: pathlib.Path,
557
+ ) -> None:
558
+ """Eleven gaps exceed the gap cap; the unreadable plan cap must still not be skipped."""
559
+ root = build(tmp_path, gaps(11))
560
+ with pytest.raises(CouldNotRun, match=PLANS_DECLARATION):
561
+ grade_declared(root, {PLANS_DECLARATION: "abc"})
562
+
563
+
564
+ def test_a_declared_cap_reaches_the_check_through_the_runner(tmp_path: pathlib.Path) -> None:
565
+ root = build(tmp_path, {**features(11), **gaps(12)})
566
+ universe = tuple(
567
+ str(path.relative_to(root)) for path in root.rglob("*") if path.is_file()
568
+ )
569
+
570
+ def run(declarations: dict[str, str] | None):
571
+ return runner.run(
572
+ ROW,
573
+ root,
574
+ universe,
575
+ exhaustive=True,
576
+ declarations=declarations,
577
+ trees={"plans": "plans"},
578
+ )
579
+
580
+ default = run(None)
581
+ assert default.outcome is Outcome.FINDINGS, default.render()
582
+ declared = run({GAPS_DECLARATION: "12", PLANS_DECLARATION: "11"})
583
+ assert declared.outcome is Outcome.CLEAN, declared.render()
584
+ refused = run({GAPS_DECLARATION: "abc"})
585
+ assert refused.outcome is Outcome.COULD_NOT_RUN, refused.render()
586
+ assert GAPS_DECLARATION in refused.message