@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/adl +2 -2
- package/bin/health-check +2 -2
- package/bin/reconcile +41 -21
- package/bin/setup +2 -1
- package/package.json +1 -1
- package/packages/agent-hooks/README.md +77 -8
- package/packages/agent-hooks/assets/opencode-plugin.template.ts +2 -6
- package/packages/agent-hooks/src/deploy/opencode-template.ts +5 -3
- package/packages/agent-hooks/src/hooks/crg/background.ts +7 -4
- package/packages/agent-hooks/src/hooks/log.ts +12 -12
- package/packages/agent-hooks/src/log/writer.ts +170 -24
- package/packages/agent-hooks/src/main.ts +1 -1
- package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +1 -1
- package/packages/agent-hooks/src/rules/shell/invocation.ts +2 -1
- package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +24 -2
- package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +19 -45
- package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +9 -0
- package/packages/agent-skills/assets/diagnosing-bugs/scripts/hitl-loop.template.sh +1 -12
- package/packages/fleet/manifest.json +2 -1
- package/packages/fleet/src/repo-maintain.ts +5 -2
- package/packages/repo-controls/AGENTS.md +7 -4
- package/packages/repo-controls/README.md +346 -56
- package/packages/repo-controls/assets/GLOBAL-INVARIANTS.md +21 -0
- package/packages/repo-controls/assets/promote.ts +7 -85
- package/packages/repo-controls/assets/reconcile-guard.sh +1 -14
- package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +25 -0
- package/packages/repo-controls/assets/scaffold/docs/CLAUDE.md +1 -0
- package/packages/repo-controls/assets/scaffold/docs/decisions/index.md +17 -0
- package/packages/repo-controls/assets/scaffold/docs/notes.md +20 -0
- package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +28 -0
- package/packages/repo-controls/assets/scaffold/plans/CLAUDE.md +1 -0
- package/packages/repo-controls/assets/scaffold/plans/GAPS-AND-FINDINGS.md +12 -0
- package/packages/repo-controls/assets/scaffold/plans/LEDGER.md +8 -0
- package/packages/repo-controls/assets/scaffold/plans/features/.gitkeep +0 -0
- package/packages/repo-controls/assets/scaffold/root/AGENTS.md +22 -0
- package/packages/repo-controls/assets/scaffold/root/CLAUDE.md +1 -0
- package/packages/repo-controls/assets/scaffold/workflows/AGENTS.md +16 -0
- package/packages/repo-controls/assets/scaffold/workflows/CLAUDE.md +1 -0
- package/packages/repo-controls/assets/scaffold/workflows/pipelines/.gitkeep +0 -0
- package/packages/repo-controls/assets/scaffold/workflows/scripts/.gitkeep +0 -0
- package/packages/repo-controls/assets/version-bump.sh +7 -48
- package/packages/repo-controls/package/adl-compliance/AGENTS.md +25 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +6 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +7 -42
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/excluded/web/i18n/locales/zh-CN/strings.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +10 -44
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/architecture/ui-contract.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/fixtures/docs/research/UI-CONTRACT.md +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/check.py +1 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/test_check.py +12 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +15 -23
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +0 -136
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +18 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +2 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +0 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/check.py +2 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/stable-delivery.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/stable-recommendation.md +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/guides/target-narration.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +6 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/test_check.py +3 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +10 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/web/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/web/src/a.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/web/src/b.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/fresh/fresh.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub1/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub10/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub11/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub2/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub3/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub4/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub5/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub6/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub7/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub8/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/many/sub9/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/services/i18n/locales/de/common.json +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/web/src/vendored-crate/Cargo.toml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/grants.yaml +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/web/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/grants.yaml +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/web/src/a.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/web/src/b.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/web/src/c.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/grants.yaml +2 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/web/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/fresh/fresh.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub1/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub10/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub11/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub2/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub3/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub4/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub5/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub6/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub7/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub8/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/many/sub9/only.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/web/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/web/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +46 -54
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +16 -76
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +35 -32
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +46 -213
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/README.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +76 -194
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +18 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +49 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/guides/links.md +20 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/guides/route-target.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/guides/links.md +20 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/guides/route-target.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/test_check.py +4 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +3 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.notes/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +8 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +15 -135
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +98 -50
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +14 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/__init__.py +3 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/base.py +42 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_check.py +82 -49
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_scope.py +71 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/check.py +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/legacy_search/test_check.py +32 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/cargo_lock.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +56 -124
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/Cargo.lock +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/Cargo.toml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/bun.lock +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/package.json +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/rust-toolchain.toml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/src/main.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/Cargo.lock +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/Cargo.toml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/bun.lock +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/package.json +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/rust-toolchain.toml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/src/main.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +8 -37
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_declared_paths.py +95 -130
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_root_lockfiles.py +101 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_stale_lock.py +25 -13
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/check.py +124 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/fixtures/valid-register.yaml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/scanner.py +217 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/no_multiline_comments/test_check.py +391 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/check.py +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/vcs_in_scripts/test_check.py +32 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +12 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/ast_grep.py +2 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/declarations.py +24 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/receipt.py +2 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/scripts.py +120 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +1 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.toml +4 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +93 -99
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +93 -99
- 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 +6 -47
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +54 -8
- package/packages/repo-controls/scripts/hooks/pre-commit +12 -2
- package/packages/repo-controls/scripts/hooks/pre-push +27 -19
- package/packages/repo-controls/src/cli.ts +22 -3
- package/packages/repo-controls/src/compliance-config.ts +132 -65
- package/packages/repo-controls/src/config.ts +55 -46
- package/packages/repo-controls/src/constants.ts +9 -0
- package/packages/repo-controls/src/coverage-ratchet.ts +19 -4
- package/packages/repo-controls/src/doctor.ts +20 -30
- package/packages/repo-controls/src/findings-ratchet.ts +14 -0
- package/packages/repo-controls/src/gitignore-block.ts +3 -13
- package/packages/repo-controls/src/hook-log.ts +7 -10
- package/packages/repo-controls/src/hook.ts +127 -83
- package/packages/repo-controls/src/install-compliance.ts +112 -0
- package/packages/repo-controls/src/install.ts +133 -30
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fn main() {}
|
|
@@ -5,9 +5,9 @@ test.sh builds its fixture inline (`mktemp`, `printf`, a `cp` of the real
|
|
|
5
5
|
`backend/rust-toolchain.toml`, a real `cargo generate-lockfile --offline`) and
|
|
6
6
|
asserts exactly two things:
|
|
7
7
|
|
|
8
|
-
1. the built fixture — a valid `
|
|
9
|
-
non-empty `
|
|
10
|
-
2. emptying `
|
|
8
|
+
1. the built fixture — a valid root `Cargo.toml`/`Cargo.lock` pair plus a
|
|
9
|
+
non-empty root `bun.lock` beside a `package.json` — passes (`... | rg -F 'OK'`).
|
|
10
|
+
2. emptying `bun.lock` afterwards makes the gate fail.
|
|
11
11
|
|
|
12
12
|
Ported here as two COMMITTED fixture directories built the identical way (see
|
|
13
13
|
`fixtures/clean/` and `fixtures/empty-bun-lock/`, and the git history of this
|
|
@@ -26,7 +26,6 @@ scenario test.sh does not have.
|
|
|
26
26
|
from __future__ import annotations
|
|
27
27
|
|
|
28
28
|
import shutil
|
|
29
|
-
from pathlib import Path
|
|
30
29
|
|
|
31
30
|
import pytest
|
|
32
31
|
from helpers import corpus, corpus_files
|
|
@@ -34,7 +33,7 @@ from helpers import corpus, corpus_files
|
|
|
34
33
|
from adl_compliance import runner
|
|
35
34
|
from adl_compliance.checks.repository.lockfile_integrity import LockfileIntegrity
|
|
36
35
|
from adl_compliance.core.verdict import Outcome
|
|
37
|
-
from adl_compliance.policy.registry import
|
|
36
|
+
from adl_compliance.policy.registry import load
|
|
38
37
|
|
|
39
38
|
FIXTURES = corpus("repository", "lockfile-integrity")
|
|
40
39
|
ROW = load().by_id("REP-002")
|
|
@@ -66,46 +65,18 @@ def test_the_row_identifies_this_check() -> None:
|
|
|
66
65
|
|
|
67
66
|
|
|
68
67
|
def test_clean_corpus_passes() -> None:
|
|
69
|
-
# test.sh: `bash "$gate" --root "$fixture" | rg -F 'OK'`
|
|
70
|
-
# backend/Cargo.toml + Cargo.lock pair cargo itself generated, plus a
|
|
71
|
-
# non-empty frontend/bun.lock.
|
|
68
|
+
# test.sh: `bash "$gate" --root "$fixture" | rg -F 'OK'` on a cargo-generated root pair plus a root bun.lock.
|
|
72
69
|
verdict = run("clean")
|
|
73
70
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
74
71
|
assert verdict.findings == ()
|
|
75
72
|
|
|
76
73
|
|
|
77
|
-
def
|
|
74
|
+
def test_empty_bun_lockfile_is_reported() -> None:
|
|
78
75
|
# test.sh: `: > "$fixture/frontend/bun.lock"` then asserts the gate fails.
|
|
79
76
|
verdict = run("empty-bun-lock")
|
|
80
77
|
assert verdict.outcome is Outcome.FINDINGS, verdict.render()
|
|
81
78
|
assert len(verdict.findings) == 1
|
|
82
79
|
finding = verdict.findings[0]
|
|
83
80
|
assert finding.rule == "LCK-001"
|
|
84
|
-
assert finding.path == "
|
|
85
|
-
assert finding.message == "missing
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def test_a_redeclared_rust_tree_is_honoured(tmp_path: Path) -> None:
|
|
89
|
-
"""The bug this pins: `WORKSPACE`/`CARGO_LOCK`/`CARGO_MANIFEST`/`BUN_LOCK`
|
|
90
|
-
used to be hardcoded `ClassVar`s (`"backend"`, `"backend/Cargo.lock"`,
|
|
91
|
-
`"backend/Cargo.toml"`, `"frontend/bun.lock"`) that bypassed tree
|
|
92
|
-
resolution entirely, unlike every other check's `inputs` mapping (K-42).
|
|
93
|
-
|
|
94
|
-
Measured for real on enact-vps-m5 (no `rust` tree at all — no Cargo.toml
|
|
95
|
-
anywhere in that checkout): once `<typescript>` scope matched a declared
|
|
96
|
-
workspace's `package.json`, this check still unconditionally reported
|
|
97
|
-
`LCK-001: missing backend/Cargo.lock` against a literal path that
|
|
98
|
-
repository never named and has no Rust code at. A repository that
|
|
99
|
-
declares `rust = "rust-workspace"` (and has no `backend/` at all) must be
|
|
100
|
-
graded against `rust-workspace/Cargo.lock`, never the packaged default.
|
|
101
|
-
"""
|
|
102
|
-
trees = effective_trees({"rust": "rust-workspace", "typescript": "ts-workspace"})
|
|
103
|
-
# The subject here is an EMPTY tree: the lockfile is reported missing because
|
|
104
|
-
# nothing exists, so the empty corpus is opted into explicitly.
|
|
105
|
-
verdict = runner.run(ROW, tmp_path, corpus_files(tmp_path, allow_empty=True), trees=trees)
|
|
106
|
-
assert verdict.outcome is Outcome.FINDINGS, verdict.render()
|
|
107
|
-
assert len(verdict.findings) == 1
|
|
108
|
-
finding = verdict.findings[0]
|
|
109
|
-
assert finding.rule == "LCK-001"
|
|
110
|
-
assert finding.path == "rust-workspace/Cargo.lock"
|
|
111
|
-
assert finding.message == "missing rust-workspace/Cargo.lock"
|
|
81
|
+
assert finding.path == "bun.lock"
|
|
82
|
+
assert finding.message == "missing bun.lock"
|
|
@@ -7,13 +7,12 @@ and a lockfiles list together:
|
|
|
7
7
|
bun-manifests / bun-lockfiles `package.json` paths or globs / `bun.lock` paths
|
|
8
8
|
cargo-manifests / cargo-lockfiles `Cargo.toml` paths or globs / `Cargo.lock` paths
|
|
9
9
|
|
|
10
|
-
Why this exists
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
lockfiles must exist) from the same lists.
|
|
10
|
+
Why this exists: a workspace keeps its manifests at the repository ROOT and under
|
|
11
|
+
`apps/*/` and `packages/*/`. The registry scope is the four root file names, so a
|
|
12
|
+
nested manifest is invisible to it unless the repository declares it. Since
|
|
13
|
+
PBI 598 (Tarun, 2026-10-08) the declaration ADDS to the root pair: it extends the
|
|
14
|
+
scope (which files select the gate) and the grade (which lockfiles must exist)
|
|
15
|
+
from the same lists.
|
|
17
16
|
|
|
18
17
|
This file is separate from `test_check.py` on purpose: that module carries a
|
|
19
18
|
module-level cargo skip and an autouse patch of `runner.lookup`, and nothing in it
|
|
@@ -27,7 +26,6 @@ from collections.abc import Mapping
|
|
|
27
26
|
from pathlib import Path
|
|
28
27
|
|
|
29
28
|
import pytest
|
|
30
|
-
from fixture_trees import fixture_trees
|
|
31
29
|
from helpers import corpus, corpus_files
|
|
32
30
|
|
|
33
31
|
from adl_compliance import runner
|
|
@@ -42,13 +40,9 @@ CLEAN_CORPUS = corpus("repository", "lockfile-integrity") / "clean"
|
|
|
42
40
|
|
|
43
41
|
requires_cargo = pytest.mark.skipif(shutil.which("cargo") is None, reason="cargo is not installed")
|
|
44
42
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"frontend": "",
|
|
49
|
-
"typescript": "{apps,packages,workflows,infra,tests,dev,live-probes}",
|
|
50
|
-
}
|
|
51
|
-
# The declaration enact-combined needs (see .scratchpad/rep002-scope/enact-combined-patch.toml).
|
|
43
|
+
# No trees: REP-002 resolves none (PBI 598).
|
|
44
|
+
NO_TREES: dict[str, str] = {}
|
|
45
|
+
# The declaration a repository with nested workspaces writes.
|
|
52
46
|
BUN = {
|
|
53
47
|
"bun-manifests": "package.json,apps/*/package.json,packages/*/package.json",
|
|
54
48
|
"bun-lockfiles": "bun.lock",
|
|
@@ -58,7 +52,7 @@ CARGO = {
|
|
|
58
52
|
"cargo-lockfiles": "backend/Cargo.lock",
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
|
|
55
|
+
WORKSPACE_FILES = {
|
|
62
56
|
"README.md": "# repo\n",
|
|
63
57
|
"package.json": "{}\n",
|
|
64
58
|
"bun.lock": "lock\n",
|
|
@@ -102,40 +96,38 @@ def grade(
|
|
|
102
96
|
)
|
|
103
97
|
|
|
104
98
|
|
|
105
|
-
# ------------------------------------------------------------ the
|
|
99
|
+
# ------------------------------------------------------------ the nested-workspace shape
|
|
106
100
|
|
|
107
101
|
|
|
108
|
-
def
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
verdict
|
|
114
|
-
assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
|
|
115
|
-
assert "engine.scope matches no file on disk" in verdict.message
|
|
102
|
+
def test_undeclared_nested_manifests_are_not_graded_on_a_whole_tree(tmp_path: Path) -> None:
|
|
103
|
+
"""Declaring nothing grades the root pair alone; nested package.json files are not asked for a lockfile."""
|
|
104
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
105
|
+
verdict = grade(tmp_path, files, trees=NO_TREES, declarations={})
|
|
106
|
+
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
107
|
+
assert verdict.units == 2 # package.json and bun.lock at the root
|
|
116
108
|
|
|
117
109
|
|
|
118
|
-
def
|
|
119
|
-
files = write(tmp_path,
|
|
120
|
-
verdict = grade(tmp_path, files, trees=
|
|
110
|
+
def test_declared_the_nested_workspace_shape_is_clean_on_a_whole_tree(tmp_path: Path) -> None:
|
|
111
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
112
|
+
verdict = grade(tmp_path, files, trees=NO_TREES, declarations=BUN)
|
|
121
113
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
122
114
|
assert verdict.units == IN_SCOPE
|
|
123
115
|
|
|
124
116
|
|
|
125
|
-
def
|
|
117
|
+
def test_declared_the_nested_workspace_shape_is_clean_on_a_narrowed_run(tmp_path: Path) -> None:
|
|
126
118
|
"""One changed manifest selects the gate, and the gate is graded over every
|
|
127
119
|
declared path in the complete universe."""
|
|
128
|
-
files = write(tmp_path,
|
|
120
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
129
121
|
verdict = grade(
|
|
130
|
-
tmp_path, files, trees=
|
|
122
|
+
tmp_path, files, trees=NO_TREES, declarations=BUN, given=("apps/web/package.json",)
|
|
131
123
|
)
|
|
132
124
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
133
125
|
assert verdict.units == IN_SCOPE
|
|
134
126
|
|
|
135
127
|
|
|
136
128
|
def test_declared_a_changed_lockfile_alone_selects_the_gate(tmp_path: Path) -> None:
|
|
137
|
-
files = write(tmp_path,
|
|
138
|
-
verdict = grade(tmp_path, files, trees=
|
|
129
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
130
|
+
verdict = grade(tmp_path, files, trees=NO_TREES, declarations=BUN, given=("bun.lock",))
|
|
139
131
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
140
132
|
assert verdict.units == IN_SCOPE
|
|
141
133
|
|
|
@@ -145,26 +137,12 @@ def test_declared_a_change_outside_every_declared_path_does_not_select_the_gate(
|
|
|
145
137
|
) -> None:
|
|
146
138
|
"""Selection follows the declaration. With nothing declared changed, the gate
|
|
147
139
|
does not apply: the missing lockfile below is not probed on this run."""
|
|
148
|
-
files = write(tmp_path, {**
|
|
149
|
-
verdict = grade(tmp_path, files, trees=
|
|
140
|
+
files = write(tmp_path, {**WORKSPACE_FILES, "bun.lock": ""})
|
|
141
|
+
verdict = grade(tmp_path, files, trees=NO_TREES, declarations=BUN, given=("README.md",))
|
|
150
142
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
151
143
|
assert verdict.findings == ()
|
|
152
144
|
|
|
153
145
|
|
|
154
|
-
def test_declared_the_lockfile_input_is_not_consulted(tmp_path: Path) -> None:
|
|
155
|
-
"""The declaration drives the grade: a per-gate `bun_lock` input pointing at
|
|
156
|
-
nothing cannot fail a repository whose declared lockfile is present."""
|
|
157
|
-
files = write(tmp_path, ENACT_FILES)
|
|
158
|
-
verdict = grade(
|
|
159
|
-
tmp_path,
|
|
160
|
-
files,
|
|
161
|
-
trees=ENACT_TREES,
|
|
162
|
-
declarations=BUN,
|
|
163
|
-
inputs={"REP-002": {"bun_lock": tmp_path / "nowhere" / "bun.lock"}},
|
|
164
|
-
)
|
|
165
|
-
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
166
|
-
|
|
167
|
-
|
|
168
146
|
# ----------------------------------------------------------------- the lockfile grade
|
|
169
147
|
|
|
170
148
|
|
|
@@ -172,12 +150,12 @@ def test_declared_the_lockfile_input_is_not_consulted(tmp_path: Path) -> None:
|
|
|
172
150
|
def test_declared_a_missing_or_empty_lockfile_is_the_same_finding(
|
|
173
151
|
tmp_path: Path, body: str | None
|
|
174
152
|
) -> None:
|
|
175
|
-
files = dict(
|
|
153
|
+
files = dict(WORKSPACE_FILES)
|
|
176
154
|
if body is None:
|
|
177
155
|
del files["bun.lock"]
|
|
178
156
|
else:
|
|
179
157
|
files["bun.lock"] = body
|
|
180
|
-
verdict = grade(tmp_path, write(tmp_path, files), trees=
|
|
158
|
+
verdict = grade(tmp_path, write(tmp_path, files), trees=NO_TREES, declarations=BUN)
|
|
181
159
|
assert verdict.outcome is Outcome.FINDINGS, verdict.render()
|
|
182
160
|
[finding] = verdict.findings
|
|
183
161
|
assert finding.rule == "LCK-001"
|
|
@@ -187,18 +165,12 @@ def test_declared_a_missing_or_empty_lockfile_is_the_same_finding(
|
|
|
187
165
|
|
|
188
166
|
|
|
189
167
|
def test_declared_and_undeclared_report_a_missing_lockfile_identically(tmp_path: Path) -> None:
|
|
190
|
-
"""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
files = write(tmp_path, {"frontend/package.json": "{}\n", "frontend/bun.lock": ""})
|
|
194
|
-
undeclared = grade(
|
|
195
|
-
tmp_path, files, trees={"rust": "", "typescript": "frontend"}, declarations={}
|
|
196
|
-
)
|
|
168
|
+
"""The automatic root pair and an explicit declaration of the same files give equal findings."""
|
|
169
|
+
files = write(tmp_path, {"package.json": "{}\n", "bun.lock": ""})
|
|
170
|
+
undeclared = grade(tmp_path, files, trees=NO_TREES, declarations={})
|
|
197
171
|
declared = grade(
|
|
198
|
-
tmp_path,
|
|
199
|
-
|
|
200
|
-
trees={"rust": "", "typescript": "elsewhere"},
|
|
201
|
-
declarations={"bun-manifests": "frontend/package.json", "bun-lockfiles": "frontend/bun.lock"},
|
|
172
|
+
tmp_path, files, trees=NO_TREES,
|
|
173
|
+
declarations={"bun-manifests": "package.json", "bun-lockfiles": "bun.lock"},
|
|
202
174
|
)
|
|
203
175
|
assert undeclared.outcome is Outcome.FINDINGS, undeclared.render()
|
|
204
176
|
assert declared.findings == undeclared.findings
|
|
@@ -206,40 +178,40 @@ def test_declared_and_undeclared_report_a_missing_lockfile_identically(tmp_path:
|
|
|
206
178
|
|
|
207
179
|
def test_declared_two_present_lockfiles_are_clean(tmp_path: Path) -> None:
|
|
208
180
|
declarations = {
|
|
209
|
-
"bun-manifests": "package.json,
|
|
210
|
-
"bun-lockfiles": "bun.lock,
|
|
181
|
+
"bun-manifests": "package.json,services/web/package.json",
|
|
182
|
+
"bun-lockfiles": "bun.lock,services/web/bun.lock",
|
|
211
183
|
}
|
|
212
184
|
both = {
|
|
213
|
-
**
|
|
214
|
-
"
|
|
215
|
-
"
|
|
185
|
+
**WORKSPACE_FILES,
|
|
186
|
+
"services/web/package.json": "{}\n",
|
|
187
|
+
"services/web/bun.lock": "lock\n",
|
|
216
188
|
}
|
|
217
|
-
verdict = grade(tmp_path, write(tmp_path, both), trees=
|
|
189
|
+
verdict = grade(tmp_path, write(tmp_path, both), trees=NO_TREES, declarations=declarations)
|
|
218
190
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
219
191
|
|
|
220
192
|
|
|
221
193
|
def test_declared_the_second_lockfile_missing_is_reported_by_its_own_path(tmp_path: Path) -> None:
|
|
222
194
|
declarations = {
|
|
223
|
-
"bun-manifests": "package.json,
|
|
224
|
-
"bun-lockfiles": "bun.lock,
|
|
195
|
+
"bun-manifests": "package.json,services/web/package.json",
|
|
196
|
+
"bun-lockfiles": "bun.lock,services/web/bun.lock",
|
|
225
197
|
}
|
|
226
|
-
files = {**
|
|
227
|
-
verdict = grade(tmp_path, write(tmp_path, files), trees=
|
|
198
|
+
files = {**WORKSPACE_FILES, "services/web/package.json": "{}\n"}
|
|
199
|
+
verdict = grade(tmp_path, write(tmp_path, files), trees=NO_TREES, declarations=declarations)
|
|
228
200
|
[finding] = verdict.findings
|
|
229
|
-
assert finding.path == "
|
|
230
|
-
assert finding.message == "missing
|
|
231
|
-
assert "(cd
|
|
201
|
+
assert finding.path == "services/web/bun.lock"
|
|
202
|
+
assert finding.message == "missing services/web/bun.lock"
|
|
203
|
+
assert "(cd services/web && bun install)" in finding.fix
|
|
232
204
|
|
|
233
205
|
|
|
234
206
|
def test_declared_both_lockfiles_missing_reports_only_the_first(tmp_path: Path) -> None:
|
|
235
207
|
"""check.sh's shape: the first problem ends the run."""
|
|
236
208
|
declarations = {
|
|
237
|
-
"bun-manifests": "package.json,
|
|
238
|
-
"bun-lockfiles": "bun.lock,
|
|
209
|
+
"bun-manifests": "package.json,services/web/package.json",
|
|
210
|
+
"bun-lockfiles": "bun.lock,services/web/bun.lock",
|
|
239
211
|
}
|
|
240
|
-
files = {k: v for k, v in
|
|
241
|
-
files["
|
|
242
|
-
verdict = grade(tmp_path, write(tmp_path, files), trees=
|
|
212
|
+
files = {k: v for k, v in WORKSPACE_FILES.items() if k != "bun.lock"}
|
|
213
|
+
files["services/web/package.json"] = "{}\n"
|
|
214
|
+
verdict = grade(tmp_path, write(tmp_path, files), trees=NO_TREES, declarations=declarations)
|
|
243
215
|
assert [f.path for f in verdict.findings] == ["bun.lock"]
|
|
244
216
|
|
|
245
217
|
|
|
@@ -251,12 +223,12 @@ def test_declared_both_lockfiles_missing_reports_only_the_first(tmp_path: Path)
|
|
|
251
223
|
def test_a_declared_manifest_matching_no_file_is_could_not_run_naming_it(
|
|
252
224
|
tmp_path: Path, dead: str, narrowed: bool
|
|
253
225
|
) -> None:
|
|
254
|
-
files = write(tmp_path,
|
|
226
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
255
227
|
declarations = {**BUN, "bun-manifests": f"package.json,{dead}"}
|
|
256
228
|
verdict = grade(
|
|
257
229
|
tmp_path,
|
|
258
230
|
files,
|
|
259
|
-
trees=
|
|
231
|
+
trees=NO_TREES,
|
|
260
232
|
declarations=declarations,
|
|
261
233
|
given=("package.json",) if narrowed else None,
|
|
262
234
|
)
|
|
@@ -270,11 +242,11 @@ def test_a_dead_declaration_is_loud_even_though_other_declared_paths_match(
|
|
|
270
242
|
) -> None:
|
|
271
243
|
"""The scope is non-empty (the live entries match), so the runner's own
|
|
272
244
|
empty-scope guard is silent; only the per-declaration check can see it."""
|
|
273
|
-
files = write(tmp_path,
|
|
245
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
274
246
|
verdict = grade(
|
|
275
247
|
tmp_path,
|
|
276
248
|
files,
|
|
277
|
-
trees=
|
|
249
|
+
trees=NO_TREES,
|
|
278
250
|
declarations={**BUN, "bun-manifests": "package.json,services/*/package.json"},
|
|
279
251
|
)
|
|
280
252
|
assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
|
|
@@ -286,9 +258,9 @@ def test_the_check_itself_refuses_a_dead_declaration_given_a_complete_set(tmp_pa
|
|
|
286
258
|
"""Not only through the runner: a caller building a Context by hand gets the
|
|
287
259
|
same refusal, because the check is what knows what it was told to scan."""
|
|
288
260
|
ctx = Context(root=tmp_path, row=ROW, declarations=BUN | {"bun-manifests": "app/*/package.json"})
|
|
289
|
-
write(tmp_path,
|
|
261
|
+
write(tmp_path, WORKSPACE_FILES)
|
|
290
262
|
with pytest.raises(CouldNotRun) as raised:
|
|
291
|
-
tuple(LockfileIntegrity().run(ctx, FileSet(paths=tuple(sorted(
|
|
263
|
+
tuple(LockfileIntegrity().run(ctx, FileSet(paths=tuple(sorted(WORKSPACE_FILES)))))
|
|
292
264
|
assert "`bun-manifests`" in str(raised.value)
|
|
293
265
|
assert "app/*/package.json" in str(raised.value)
|
|
294
266
|
|
|
@@ -317,7 +289,7 @@ MALFORMED = [
|
|
|
317
289
|
pytest.param(bun("package.json, ,apps/*/package.json"), "bun-manifests", id="manifest blank entry"),
|
|
318
290
|
pytest.param(bun("package.json,package.json"), "bun-manifests", id="manifest duplicate"),
|
|
319
291
|
pytest.param(bun("!apps/web/package.json"), "bun-manifests", id="manifest negation"),
|
|
320
|
-
pytest.param(bun("<
|
|
292
|
+
pytest.param(bun("<tree>/package.json"), "bun-manifests", id="manifest placeholder"),
|
|
321
293
|
pytest.param(bun("apps/{web,x}/package.json"), "bun-manifests", id="manifest brace union"),
|
|
322
294
|
pytest.param(bun("apps/web/README.md"), "bun-manifests", id="manifest wrong file name"),
|
|
323
295
|
pytest.param(bun("apps/web/"), "bun-manifests", id="manifest directory"),
|
|
@@ -343,8 +315,8 @@ MALFORMED = [
|
|
|
343
315
|
def test_a_malformed_declaration_is_could_not_run_naming_it(
|
|
344
316
|
tmp_path: Path, declarations: dict[str, str], named: str
|
|
345
317
|
) -> None:
|
|
346
|
-
files = write(tmp_path,
|
|
347
|
-
verdict = grade(tmp_path, files, trees=
|
|
318
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
319
|
+
verdict = grade(tmp_path, files, trees=NO_TREES, declarations=declarations)
|
|
348
320
|
assert verdict.outcome is Outcome.COULD_NOT_RUN, verdict.render()
|
|
349
321
|
assert f"`{named}`" in verdict.message, verdict.message
|
|
350
322
|
|
|
@@ -353,7 +325,7 @@ def test_a_malformed_declaration_is_could_not_run_naming_it(
|
|
|
353
325
|
def test_the_check_refuses_a_malformed_declaration_without_the_runner(
|
|
354
326
|
tmp_path: Path, declarations: dict[str, str], named: str
|
|
355
327
|
) -> None:
|
|
356
|
-
files = write(tmp_path,
|
|
328
|
+
files = write(tmp_path, WORKSPACE_FILES)
|
|
357
329
|
ctx = Context(root=tmp_path, row=ROW, declarations=declarations)
|
|
358
330
|
with pytest.raises(CouldNotRun) as raised:
|
|
359
331
|
tuple(LockfileIntegrity().run(ctx, FileSet(paths=files)))
|
|
@@ -368,10 +340,12 @@ def test_an_absent_declaration_restates_nothing() -> None:
|
|
|
368
340
|
assert LockfileIntegrity.declared_scope(ROW.engine.scope, {"license": "Proprietary"}) is None
|
|
369
341
|
|
|
370
342
|
|
|
371
|
-
|
|
343
|
+
ROOT_SCOPE = ("Cargo.toml", "Cargo.lock", "package.json", "bun.lock")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def test_a_declared_bun_half_adds_to_the_root_pairs() -> None:
|
|
372
347
|
assert LockfileIntegrity.declared_scope(ROW.engine.scope, BUN) == (
|
|
373
|
-
|
|
374
|
-
"<rust>/Cargo.lock",
|
|
348
|
+
*ROOT_SCOPE,
|
|
375
349
|
"package.json",
|
|
376
350
|
"apps/*/package.json",
|
|
377
351
|
"packages/*/package.json",
|
|
@@ -379,17 +353,17 @@ def test_a_declared_bun_half_replaces_only_the_typescript_entries() -> None:
|
|
|
379
353
|
)
|
|
380
354
|
|
|
381
355
|
|
|
382
|
-
def
|
|
356
|
+
def test_a_declared_cargo_half_adds_to_the_root_pairs() -> None:
|
|
383
357
|
assert LockfileIntegrity.declared_scope(ROW.engine.scope, CARGO) == (
|
|
384
|
-
|
|
385
|
-
"<typescript>/bun.lock",
|
|
358
|
+
*ROOT_SCOPE,
|
|
386
359
|
"backend/Cargo.toml",
|
|
387
360
|
"backend/Cargo.lock",
|
|
388
361
|
)
|
|
389
362
|
|
|
390
363
|
|
|
391
|
-
def
|
|
364
|
+
def test_both_halves_declared_still_keep_the_root_pairs() -> None:
|
|
392
365
|
assert LockfileIntegrity.declared_scope(ROW.engine.scope, {**BUN, **CARGO}) == (
|
|
366
|
+
*ROOT_SCOPE,
|
|
393
367
|
"backend/Cargo.toml",
|
|
394
368
|
"backend/Cargo.lock",
|
|
395
369
|
"package.json",
|
|
@@ -399,14 +373,9 @@ def test_both_halves_declared_leave_no_tree_default() -> None:
|
|
|
399
373
|
)
|
|
400
374
|
|
|
401
375
|
|
|
402
|
-
def
|
|
403
|
-
"""
|
|
404
|
-
|
|
405
|
-
anyone deciding it. This pins the shape the replacement relies on."""
|
|
406
|
-
scope = ROW.engine.scope
|
|
407
|
-
assert sorted(scope) == sorted(
|
|
408
|
-
["<rust>/Cargo.toml", "<rust>/Cargo.lock", "<typescript>/package.json", "<typescript>/bun.lock"]
|
|
409
|
-
)
|
|
376
|
+
def test_the_registry_scope_is_the_four_root_files() -> None:
|
|
377
|
+
"""Root-literal names with no tree placeholder: the pairs graded automatically."""
|
|
378
|
+
assert tuple(ROW.engine.scope) == ROOT_SCOPE
|
|
410
379
|
|
|
411
380
|
|
|
412
381
|
def test_both_registry_files_carry_the_same_text() -> None:
|
|
@@ -418,26 +387,25 @@ def test_both_registry_files_carry_the_same_text() -> None:
|
|
|
418
387
|
|
|
419
388
|
|
|
420
389
|
@requires_cargo
|
|
421
|
-
def
|
|
422
|
-
shutil.copytree(CLEAN_CORPUS, tmp_path
|
|
390
|
+
def test_declared_cargo_paths_are_graded_and_undeclared_nested_ones_are_not(tmp_path: Path) -> None:
|
|
391
|
+
shutil.copytree(CLEAN_CORPUS, tmp_path / "backend")
|
|
423
392
|
files = corpus_files(tmp_path)
|
|
424
|
-
trees = {**fixture_trees(), "rust": "somewhere-else"}
|
|
425
393
|
|
|
426
|
-
undeclared = grade(tmp_path, files, trees=
|
|
427
|
-
assert
|
|
394
|
+
undeclared = grade(tmp_path, files, trees=NO_TREES, declarations={})
|
|
395
|
+
assert undeclared.outcome is Outcome.CLEAN, undeclared.render()
|
|
428
396
|
|
|
429
|
-
declared = grade(tmp_path, files, trees=
|
|
397
|
+
declared = grade(tmp_path, files, trees=NO_TREES, declarations=CARGO)
|
|
430
398
|
assert declared.outcome is Outcome.CLEAN, declared.render()
|
|
431
399
|
|
|
432
400
|
|
|
433
401
|
@requires_cargo
|
|
434
402
|
def test_declared_cargo_missing_lockfile_is_the_missing_finding(tmp_path: Path) -> None:
|
|
435
|
-
shutil.copytree(CLEAN_CORPUS, tmp_path
|
|
403
|
+
shutil.copytree(CLEAN_CORPUS, tmp_path / "backend")
|
|
436
404
|
(tmp_path / "backend" / "Cargo.lock").unlink()
|
|
437
405
|
verdict = grade(
|
|
438
406
|
tmp_path,
|
|
439
407
|
corpus_files(tmp_path),
|
|
440
|
-
trees=
|
|
408
|
+
trees=NO_TREES,
|
|
441
409
|
declarations=CARGO,
|
|
442
410
|
)
|
|
443
411
|
[finding] = verdict.findings
|
|
@@ -461,7 +429,7 @@ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory
|
|
|
461
429
|
"""
|
|
462
430
|
from adl_compliance.checks.repository.lockfile_integrity import check as module
|
|
463
431
|
|
|
464
|
-
write(tmp_path, {"nested/ws/Cargo.toml": "[package]\n", "nested/ws/Cargo.lock": "lock\n"
|
|
432
|
+
write(tmp_path, {"nested/ws/Cargo.toml": "[package]\n", "nested/ws/Cargo.lock": "lock\n"})
|
|
465
433
|
calls: list[tuple[list[str], Path]] = []
|
|
466
434
|
|
|
467
435
|
def fake_run(argv: list[str], *, cwd: Path, **_: object) -> object:
|
|
@@ -473,7 +441,7 @@ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory
|
|
|
473
441
|
verdict = grade(
|
|
474
442
|
tmp_path,
|
|
475
443
|
corpus_files(tmp_path),
|
|
476
|
-
trees=
|
|
444
|
+
trees=NO_TREES,
|
|
477
445
|
declarations=cargo("nested/*/Cargo.toml", "nested/ws/Cargo.lock"),
|
|
478
446
|
)
|
|
479
447
|
[finding] = verdict.findings
|
|
@@ -489,35 +457,32 @@ def test_declared_cargo_rejection_is_lck_002_against_the_lockfiles_own_directory
|
|
|
489
457
|
|
|
490
458
|
@requires_cargo
|
|
491
459
|
def test_declared_cargo_workspace_is_the_lockfiles_directory(tmp_path: Path) -> None:
|
|
492
|
-
"""A
|
|
493
|
-
|
|
494
|
-
shutil.copytree(CLEAN_CORPUS / "backend", tmp_path / "crates" / "ws")
|
|
495
|
-
write(tmp_path, {"frontend/bun.lock": "lock\n"})
|
|
460
|
+
"""A nested workspace: cargo runs from the lock's directory, so its rust-toolchain override applies."""
|
|
461
|
+
shutil.copytree(CLEAN_CORPUS, tmp_path / "crates" / "ws")
|
|
496
462
|
verdict = grade(
|
|
497
463
|
tmp_path,
|
|
498
464
|
corpus_files(tmp_path),
|
|
499
|
-
trees=
|
|
465
|
+
trees=NO_TREES,
|
|
500
466
|
declarations=cargo("crates/*/Cargo.toml", "crates/ws/Cargo.lock"),
|
|
501
467
|
)
|
|
502
468
|
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
503
469
|
|
|
504
470
|
|
|
505
471
|
@requires_cargo
|
|
506
|
-
def
|
|
507
|
-
"""Bun declared, Cargo not: the
|
|
472
|
+
def test_the_root_cargo_pair_is_graded_beside_a_declared_bun_half(tmp_path: Path) -> None:
|
|
473
|
+
"""Bun declared, Cargo not: the root Cargo pair is still graded automatically."""
|
|
508
474
|
shutil.copytree(CLEAN_CORPUS, tmp_path, dirs_exist_ok=True)
|
|
509
|
-
write(tmp_path,
|
|
475
|
+
write(tmp_path, WORKSPACE_FILES)
|
|
510
476
|
files = corpus_files(tmp_path)
|
|
511
|
-
trees = {"rust": "backend", "typescript": ENACT_TREES["typescript"]}
|
|
512
477
|
|
|
513
|
-
clean = grade(tmp_path, files, trees=
|
|
478
|
+
clean = grade(tmp_path, files, trees=NO_TREES, declarations=BUN)
|
|
514
479
|
assert clean.outcome is Outcome.CLEAN, clean.render()
|
|
515
|
-
assert clean.units == IN_SCOPE + 2 # +
|
|
480
|
+
assert clean.units == IN_SCOPE + 2 # + Cargo.toml and Cargo.lock at the root
|
|
516
481
|
|
|
517
482
|
(tmp_path / "bun.lock").unlink()
|
|
518
|
-
broken = grade(tmp_path, corpus_files(tmp_path), trees=
|
|
483
|
+
broken = grade(tmp_path, corpus_files(tmp_path), trees=NO_TREES, declarations=BUN)
|
|
519
484
|
assert [f.path for f in broken.findings] == ["bun.lock"]
|
|
520
485
|
|
|
521
|
-
(tmp_path / "
|
|
522
|
-
both = grade(tmp_path, corpus_files(tmp_path), trees=
|
|
523
|
-
assert [f.path for f in both.findings] == ["
|
|
486
|
+
(tmp_path / "Cargo.lock").unlink()
|
|
487
|
+
both = grade(tmp_path, corpus_files(tmp_path), trees=NO_TREES, declarations=BUN)
|
|
488
|
+
assert [f.path for f in both.findings] == ["Cargo.lock"], "the Cargo half is reported first, as before"
|