@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.
- package/bin/adl +2 -2
- package/bin/git +16 -17
- package/bin/git-ado +25 -24
- package/bin/git-defaults +173 -0
- package/bin/health-check +8 -1
- package/bin/kimi-hooks +1 -1
- package/bin/lib/adl-package.sh +257 -0
- package/bin/lib/fleet-host.sh +8 -5
- package/bin/reconcile +482 -232
- package/bin/reconcile-host +30 -20
- package/bin/setup +20 -17
- package/bin/shell-path-policy +15 -5
- package/bin/sync-and-reconcile +62 -64
- package/bin/validate-manifest +33 -9
- package/package.json +2 -10
- package/packages/agent-hooks/README.md +14 -9
- package/packages/agent-hooks/assets/hooks.json +0 -20
- package/packages/agent-hooks/src/cli/args.ts +1 -1
- package/packages/agent-hooks/src/cli/doctor.ts +6 -58
- package/packages/agent-hooks/src/deploy/deploy.ts +12 -41
- package/packages/agent-hooks/src/deploy/doctor.ts +8 -11
- package/packages/agent-hooks/src/deploy/index.ts +14 -14
- package/packages/agent-hooks/src/deploy/json-merge.ts +7 -8
- package/packages/agent-hooks/src/deploy/legacy-lock.ts +2 -2
- package/packages/agent-hooks/src/deploy/list.ts +1 -1
- package/packages/agent-hooks/src/deploy/lock.ts +31 -12
- package/packages/agent-hooks/src/deploy/profile.ts +3 -4
- package/packages/agent-hooks/src/deploy/source.ts +1 -1
- package/packages/agent-hooks/src/gating/config.ts +12 -24
- package/packages/agent-hooks/src/gating/root.ts +3 -3
- package/packages/agent-hooks/src/hooks/briefing/handler.ts +2 -2
- package/packages/agent-hooks/src/hooks/briefing/index.ts +27 -287
- package/packages/agent-hooks/src/hooks/contract.ts +17 -17
- package/packages/agent-hooks/src/hooks/crg/background.ts +27 -0
- package/packages/agent-hooks/src/hooks/crg/index.ts +7 -6
- package/packages/agent-hooks/src/hooks/log.ts +8 -10
- package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +1 -1
- package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +1 -1
- package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +6 -5
- package/packages/agent-hooks/src/hosts/registry.ts +1 -1
- package/packages/agent-hooks/src/log/adl-dir.ts +44 -0
- package/packages/agent-hooks/src/log/writer.ts +16 -2
- package/packages/agent-hooks/src/main.ts +7 -41
- package/packages/agent-hooks/src/pre-tool-use/handler.ts +71 -82
- package/packages/agent-hooks/src/rules/engine.ts +13 -17
- package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +3 -3
- package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +12 -14
- package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +28 -28
- package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +8 -9
- package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +56 -59
- package/packages/agent-hooks/src/rules/impl/adl-identity.ts +16 -23
- package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +19 -16
- package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +2 -2
- package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +9 -12
- package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +5 -7
- package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +411 -0
- package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +4 -5
- package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +6 -7
- package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +77 -70
- package/packages/agent-hooks/src/rules/impl/deny-allowlist.ts +131 -0
- package/packages/agent-hooks/src/rules/impl/git-helpers.ts +21 -23
- package/packages/agent-hooks/src/rules/impl/git-worktree.ts +14 -28
- package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -3
- package/packages/agent-hooks/src/rules/impl/messages.ts +1 -1
- package/packages/agent-hooks/src/rules/impl/pr-completion.ts +1 -1
- package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +35 -46
- package/packages/agent-hooks/src/rules/impl/tool-find.ts +34 -7
- package/packages/agent-hooks/src/rules/impl/tool-grep.ts +33 -12
- package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +52 -9
- package/packages/agent-hooks/src/rules/ports.ts +5 -7
- package/packages/agent-hooks/src/rules/registry.ts +4 -6
- package/packages/agent-hooks/src/rules/shell/git-identity.ts +86 -101
- package/packages/agent-hooks/src/rules/shell/git-internals.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/invocation.ts +24 -118
- package/packages/agent-hooks/src/rules/shell/piped-script.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/prefixes.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +11 -10
- package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +26 -26
- package/packages/agent-hooks/src/rules/shell/substitution.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/xargs.ts +2 -2
- package/packages/agent-hooks/src/rules/spec.ts +2 -2
- package/packages/agent-settings/README.md +287 -0
- package/packages/agent-settings/assets/GLOBAL-AGENTS.md +14 -16
- package/packages/agent-settings/assets/agents/architect.toml +2 -0
- package/packages/agent-settings/assets/agents/code-reviewer.toml +2 -0
- package/packages/agent-settings/assets/agents/executor.toml +2 -0
- package/packages/agent-settings/assets/agents/explore.toml +2 -0
- package/packages/agent-settings/assets/agents/planner.toml +2 -0
- package/packages/agent-settings/assets/mcp.json +0 -4
- package/packages/agent-settings/deploy-profile.json +139 -0
- package/packages/agent-settings/deploy-profile.schema.json +147 -0
- package/packages/agent-settings/managed/README.md +29 -30
- package/packages/agent-settings/managed/codex-requirements.toml +9 -9
- package/packages/agent-settings/package.json +20 -0
- package/packages/agent-settings/src/deploy/agent-render.ts +2 -2
- package/packages/agent-settings/src/deploy/agent-toml.ts +1 -1
- package/packages/agent-settings/src/deploy/agents-deploy.ts +17 -3
- package/packages/agent-settings/src/deploy/doctor.ts +3 -83
- package/packages/agent-settings/src/deploy/index.ts +6 -33
- package/packages/agent-settings/src/deploy/legacy-lock.ts +6 -6
- package/packages/agent-settings/src/deploy/list.ts +5 -19
- package/packages/agent-settings/src/deploy/lock.ts +8 -18
- package/packages/agent-settings/src/deploy/mcp-deploy.ts +10 -5
- package/packages/agent-settings/src/deploy/mcp-merge.ts +22 -3
- package/packages/agent-settings/src/deploy/mcp-source.ts +1 -1
- package/packages/agent-settings/src/deploy/profile.ts +11 -35
- package/packages/agent-settings/src/deploy/retire-lean-ctx.ts +184 -0
- package/packages/agent-settings/src/deploy/settings.ts +17 -22
- package/packages/agent-settings/src/global-surface-sweep.ts +0 -2
- package/packages/agent-settings/src/kimi-hooks.ts +339 -192
- package/packages/agent-settings/src/opencode-hooks.ts +1 -1
- package/packages/agent-settings/tsconfig.json +23 -0
- package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +120 -0
- package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +276 -0
- package/packages/agent-skills/assets/debug-issue/SKILL.md +8 -7
- package/packages/agent-skills/assets/explore-codebase/SKILL.md +13 -13
- package/packages/agent-skills/assets/refactor-safely/SKILL.md +12 -11
- package/packages/agent-skills/assets/review-changes/SKILL.md +7 -7
- package/packages/agent-skills/assets/testing/references/e2e.md +37 -0
- package/packages/agent-skills/assets/testing/references/integration.md +37 -0
- package/packages/agent-skills/assets/testing/references/unit.md +32 -0
- package/packages/agent-skills/dist/deploy.d.ts +2 -0
- package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
- package/packages/agent-skills/dist/deploy.js +27 -1
- package/packages/agent-skills/dist/deploy.js.map +1 -1
- package/packages/agent-skills/dist/doctor.d.ts +0 -2
- package/packages/agent-skills/dist/doctor.d.ts.map +1 -1
- package/packages/agent-skills/dist/doctor.js +1 -1
- package/packages/agent-skills/dist/doctor.js.map +1 -1
- package/packages/agent-skills/dist/fs-utils.d.ts +0 -1
- package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -1
- package/packages/agent-skills/dist/fs-utils.js +1 -1
- package/packages/agent-skills/dist/fs-utils.js.map +1 -1
- package/packages/agent-skills/src/deploy.ts +29 -1
- package/packages/agent-skills/src/doctor.ts +1 -1
- package/packages/agent-skills/src/fs-utils.ts +1 -1
- package/packages/azdo/README.md +10 -9
- package/packages/azdo/package.json +1 -3
- package/packages/azdo/scripts/probe-ado-rules.ts +311 -0
- package/packages/azdo/src/azdo-pl.ts +2 -54
- package/packages/azdo/src/azdo-pr-review.ts +4 -4
- package/packages/azdo/src/azdo-repo-doctor.ts +101 -52
- package/packages/azdo/src/azdo-wit.ts +44 -2
- package/packages/azdo/src/lib/agent-identity.ts +12 -14
- package/packages/azdo/src/lib/agent-token.ts +2 -2
- package/packages/azdo/src/lib/client.ts +0 -5
- package/packages/azdo/src/lib/graph-identity.ts +4 -4
- package/packages/azdo/src/workflow-ladder.csv +241 -0
- package/packages/azdo/src/workflow-rules-matrix.csv +85 -0
- package/packages/azdo/src/workflow-rules.ts +194 -0
- package/packages/fleet/manifest.json +35 -11
- package/packages/fleet/manifest.schema.json +62 -10
- package/packages/fleet/src/default-agent-file.ts +3 -3
- package/packages/fleet/src/install-adl-package.ts +73 -15
- package/packages/fleet/src/install-embedding-model.ts +13 -13
- package/packages/fleet/src/managed-settings.ts +7 -7
- package/packages/fleet/src/repo-maintain.ts +91 -55
- package/packages/fleet/src/scratch-reap.ts +5 -5
- package/packages/fleet/src/vscode-settings.ts +5 -5
- package/packages/repo-controls/README.md +141 -43
- package/packages/repo-controls/assets/promote.ts +4 -4
- package/packages/repo-controls/assets/reconcile-guard.sh +10 -46
- package/packages/repo-controls/assets/version-bump.sh +31 -84
- package/packages/repo-controls/bunfig.toml +6 -7
- package/packages/repo-controls/package/adl-compliance/AGENTS.md +5 -6
- package/packages/repo-controls/package/adl-compliance/conftest.py +3 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +4 -16
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +9 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/check.py +266 -0
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/.hidden.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/data.csv +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/diagram.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs/guide/mock.png +1 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/docs.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p1.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p2.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p3.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p4.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-assets/plans/deep/p5.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/build.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/architecture/overview.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/install.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/docs/guide/start.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-clean/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-decisions/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-default-cap/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-exempt/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-grew/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-no-section/grants.yaml +4 -0
- 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
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-over-cap/grants.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/held/d.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/docs/shrunk/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-stale/grants.yaml +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/a.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/b.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/docs/guide/c.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/fixtures/docs-size-unusable/grants.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/directory_size/test_check.py +345 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +18 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +35 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/check.py +91 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/CLAUDE.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/-leading-hyphen.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/double--hyphen.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/getting started.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/nested/page-here.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/trailing-hyphen-.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/user_profile.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/UI-CONTRACT.png +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/assets/diagram.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/index.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/notes.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/reference.html +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/2026-10-experiment.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +194 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +14 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +5 -5
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +58 -6
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +64 -17
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +60 -9
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +17 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +0 -1
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs-okf-profiles.yaml +5 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/experiment-ok.md +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +11 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-sources.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +14 -0
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research-neighbours/docs/operations/runbook.md +26 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +228 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +11 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +5 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +160 -44
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +0 -2
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/grants.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +0 -2
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +5 -0
- 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
- 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
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +4 -0
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +0 -2
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/grants.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/register.py +127 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +265 -29
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +75 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +194 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +56 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +258 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +40 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +108 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +9 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +1 -1
- 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
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +3 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +2 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +23 -6
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +76 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +58 -17
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +161 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +35 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +108 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +281 -61
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/absolute-path.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/backslash-path.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/baseline-is-a-list.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/boolean-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/dot-segment.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/duplicate-path.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-file.yaml +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/empty-segment.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/float-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/negative-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-over-limit-baseline.yaml +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/no-schema-version.yaml +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/non-string-path.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/not-yaml.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/null-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/parent-segment.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/string-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/top-level-is-a-list.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/trailing-slash.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/unknown-key.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/wrong-schema-version.yaml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/malformed/zero-size.yaml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/valid.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +758 -45
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +4 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +2 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +490 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +368 -55
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +4 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +523 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +1179 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +122 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +33 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +0 -11
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +16 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +12 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +133 -147
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +133 -147
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +52 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +23 -4
- package/packages/repo-controls/package.json +2 -9
- package/packages/repo-controls/scripts/hooks/post-merge +13 -0
- package/packages/repo-controls/scripts/hooks/pre-push +3 -3
- package/packages/repo-controls/src/azdo-push.ts +64 -36
- package/packages/repo-controls/src/azure-devops-governance.ts +7 -9
- package/packages/repo-controls/src/azure-devops-remote.ts +1 -1
- package/packages/repo-controls/src/cli.ts +29 -8
- package/packages/repo-controls/src/compliance-config.ts +410 -2
- package/packages/repo-controls/src/config.ts +133 -60
- package/packages/repo-controls/src/constants.ts +13 -27
- package/packages/repo-controls/src/coverage-ratchet.ts +4 -4
- package/packages/repo-controls/src/doctor.ts +46 -19
- package/packages/repo-controls/src/gitignore-block.ts +5 -29
- package/packages/repo-controls/src/hook-log.ts +1 -2
- package/packages/repo-controls/src/hook.ts +201 -45
- package/packages/repo-controls/src/install.ts +14 -8
- package/packages/repo-controls/src/integrity.ts +3 -3
- package/packages/repo-controls/src/lfs.ts +111 -0
- package/packages/repo-controls/src/promote.ts +8 -8
- package/packages/repo-controls/src/runtime.ts +9 -1
- package/packages/repo-controls/src/workflows-drift.ts +4 -5
- package/packages/repo-controls/src/worktree-guard.ts +3 -3
|
@@ -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 —
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
"
|
|
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(
|
|
70
|
-
|
|
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 {
|
|
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
|
|
|
@@ -80,7 +81,7 @@ def test_the_committed_clean_corpus_is_silent() -> None:
|
|
|
80
81
|
|
|
81
82
|
@pytest.mark.parametrize(
|
|
82
83
|
"status",
|
|
83
|
-
("New", "Approved", "
|
|
84
|
+
("New", "In Planning", "Awaiting Approval", "Approved", "Spec Validation", "In Implementation", "In Testing", "In Review", "Updating Docs"),
|
|
84
85
|
)
|
|
85
86
|
def test_each_exact_active_plan_state_counts_as_open(
|
|
86
87
|
tmp_path: pathlib.Path, status: str
|
|
@@ -178,7 +179,7 @@ def test_a_partial_file_set_counts_the_complete_plan_tree(
|
|
|
178
179
|
|
|
179
180
|
@pytest.mark.parametrize(
|
|
180
181
|
"status",
|
|
181
|
-
("New", "Approved", "
|
|
182
|
+
("New", "In Planning", "Awaiting Approval", "Approved", "Spec Validation", "In Implementation", "In Testing", "In Review", "Updating Docs"),
|
|
182
183
|
)
|
|
183
184
|
def test_unraised_and_each_linked_active_gap_state_both_count_as_open(
|
|
184
185
|
tmp_path: pathlib.Path, status: str
|
|
@@ -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
|