@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""REP-004 — ported from `compliance_new/checks/repository/no-banned-commands/test.sh`.
|
|
1
|
+
"""REP-004 and REP-006 — ported from `compliance_new/checks/repository/no-banned-commands/test.sh`.
|
|
2
2
|
|
|
3
3
|
One corpus, nine files, one property apiece — see `fixtures/no-banned-commands/`.
|
|
4
4
|
Two sets over it: the WHOLE corpus, handed straight to `graded()` (bypassing the
|
|
@@ -13,16 +13,19 @@ from __future__ import annotations
|
|
|
13
13
|
import pathlib
|
|
14
14
|
|
|
15
15
|
from adl_compliance import runner
|
|
16
|
-
from adl_compliance.checks.repository.
|
|
16
|
+
from adl_compliance.checks.repository.legacy_search import LegacySearch
|
|
17
|
+
from adl_compliance.checks.repository.vcs_in_scripts import VcsInScripts
|
|
17
18
|
from adl_compliance.core.context import Context, FileSet
|
|
19
|
+
from adl_compliance.core.scripts import is_shell_script
|
|
18
20
|
from adl_compliance.core.verdict import Outcome, decide
|
|
19
21
|
from fixture_trees import fixture_trees
|
|
20
22
|
from adl_compliance.policy.registry import load
|
|
21
23
|
|
|
22
24
|
CORPUS = pathlib.Path(__file__).parent / "fixtures" / "no-banned-commands"
|
|
23
|
-
|
|
25
|
+
SEARCH_ROW = load().by_id("REP-004")
|
|
26
|
+
VCS_ROW = load().by_id("REP-006")
|
|
24
27
|
|
|
25
|
-
# The row's
|
|
28
|
+
# The row's scope is repo-wide (PBI 599). The fixture's own subdirectory is
|
|
26
29
|
# named to match whatever that resolves to TODAY (see the sibling directory on
|
|
27
30
|
# disk) — resolved here rather than spelled, so a future remap fails this test
|
|
28
31
|
# loudly (file not found) instead of silently narrowing to nothing.
|
|
@@ -42,76 +45,106 @@ WHOLE = (
|
|
|
42
45
|
NARROWED = tuple(p for p in WHOLE if p.startswith(f"{COMPLIANCE}/"))
|
|
43
46
|
|
|
44
47
|
|
|
45
|
-
def graded_directly(paths: tuple[str, ...]):
|
|
48
|
+
def graded_directly(check, row, paths: tuple[str, ...]):
|
|
46
49
|
"""Bypasses row-scope narrowing entirely — the check's OWN filtering only.
|
|
47
50
|
Mirrors the shell test's direct `check.sh` invocation, which is how it
|
|
48
51
|
proved check.sh performs no location filtering of its own."""
|
|
49
|
-
ctx = Context(root=CORPUS, row=
|
|
50
|
-
findings = tuple(
|
|
51
|
-
return decide(
|
|
52
|
+
ctx = Context(root=CORPUS, row=row)
|
|
53
|
+
findings = tuple(check.graded(ctx, FileSet(paths=paths)))
|
|
54
|
+
return decide(row, findings, 0, ())
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
def test_the_whole_set_is_graded_with_no_location_filtering() -> None:
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
assert
|
|
60
|
-
|
|
61
|
-
assert
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
assert
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
assert "
|
|
75
|
-
assert
|
|
76
|
-
assert
|
|
77
|
-
assert
|
|
58
|
+
"""PBI 605: each gate reports only its own rule, including the pair a path filter in check.py would hide."""
|
|
59
|
+
vcs = graded_directly(VcsInScripts(), VCS_ROW, WHOLE)
|
|
60
|
+
assert vcs.outcome is Outcome.FINDINGS
|
|
61
|
+
assert {f.path for f in vcs.findings} >= {"elsewhere/loose-git.sh"}
|
|
62
|
+
assert "elsewhere/loose-grep.sh" not in {f.path for f in vcs.findings}
|
|
63
|
+
assert {f.rule for f in vcs.findings} == {"GIT-001"}
|
|
64
|
+
assert len(vcs.findings) == 2
|
|
65
|
+
|
|
66
|
+
search = graded_directly(LegacySearch(), SEARCH_ROW, WHOLE)
|
|
67
|
+
assert search.outcome is Outcome.FINDINGS
|
|
68
|
+
assert {f.path for f in search.findings} >= {"elsewhere/loose-grep.sh"}
|
|
69
|
+
assert "elsewhere/loose-git.sh" not in {f.path for f in search.findings}
|
|
70
|
+
assert {f.rule for f in search.findings} == {"GRP-001"}
|
|
71
|
+
assert len(search.findings) == 3
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_the_two_gates_are_distinct_rows_with_their_own_addresses() -> None:
|
|
75
|
+
"""PBI 605: REP-004 is the legacy-search gate (GRP-001); REP-006 is the version-control gate (GIT-001)."""
|
|
76
|
+
registry = load()
|
|
77
|
+
assert registry.resolve("repository.no-legacy-search").id == "REP-004"
|
|
78
|
+
assert registry.resolve("repository.no-vcs-in-scripts").id == "REP-006"
|
|
79
|
+
assert LegacySearch.id == "REP-004" and LegacySearch.rule == "GRP-001"
|
|
80
|
+
assert VcsInScripts.id == "REP-006" and VcsInScripts.rule == "GIT-001"
|
|
81
|
+
assert "repository.no-banned-commands" not in {row.address for row in registry.rows}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_the_two_rows_share_one_repo_wide_scope_but_for_the_managed_projections() -> None:
|
|
85
|
+
"""REP-006 alone leaves the adl-managed projections out (see `test_scope.py`); the rest is shared."""
|
|
86
|
+
exempt = [entry for entry in VCS_ROW.engine.scope if entry not in SEARCH_ROW.engine.scope]
|
|
87
|
+
assert exempt and all(entry.startswith("!.") for entry in exempt)
|
|
88
|
+
assert [e for e in SEARCH_ROW.engine.scope if e not in VCS_ROW.engine.scope] == []
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_the_row_scope_is_repo_wide_so_files_outside_compliance_are_graded() -> None:
|
|
92
|
+
"""PBI 599/601/605: the row is repo-wide, so the `elsewhere/` scripts are reported, one assertion set per gate."""
|
|
93
|
+
search = runner.run(SEARCH_ROW, CORPUS, WHOLE)
|
|
94
|
+
assert search.outcome is Outcome.FINDINGS
|
|
95
|
+
search_paths = {f.path for f in search.findings}
|
|
96
|
+
assert "elsewhere/loose-grep.sh" in search_paths
|
|
97
|
+
assert f"{COMPLIANCE}/checks/grep-bad.sh" in search_paths
|
|
98
|
+
assert f"{COMPLIANCE}/checks/grep-trailing.sh" in search_paths
|
|
99
|
+
|
|
100
|
+
vcs = runner.run(VCS_ROW, CORPUS, WHOLE)
|
|
101
|
+
assert vcs.outcome is Outcome.FINDINGS
|
|
102
|
+
vcs_paths = {f.path for f in vcs.findings}
|
|
103
|
+
assert "elsewhere/loose-git.sh" in vcs_paths
|
|
104
|
+
assert f"{COMPLIANCE}/checks/git-bad.sh" in vcs_paths
|
|
78
105
|
|
|
79
106
|
|
|
80
107
|
def test_a_whole_line_comment_naming_the_word_is_not_a_violation() -> None:
|
|
81
|
-
|
|
82
|
-
paths = {f.path for f in verdict.findings}
|
|
108
|
+
paths = {f.path for f in runner.run(VCS_ROW, CORPUS, NARROWED).findings}
|
|
83
109
|
assert f"{COMPLIANCE}/checks/git-note.sh" not in paths
|
|
110
|
+
paths = {f.path for f in runner.run(SEARCH_ROW, CORPUS, NARROWED).findings}
|
|
84
111
|
assert f"{COMPLIANCE}/checks/grep-note.sh" not in paths
|
|
85
112
|
|
|
86
113
|
|
|
87
114
|
def test_prose_and_a_clean_script_are_not_graded() -> None:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
115
|
+
for row in (SEARCH_ROW, VCS_ROW):
|
|
116
|
+
paths = {f.path for f in runner.run(row, CORPUS, NARROWED).findings}
|
|
117
|
+
assert f"{COMPLIANCE}/checks/notes.md" not in paths
|
|
118
|
+
assert f"{COMPLIANCE}/checks/good.sh" not in paths
|
|
92
119
|
|
|
93
120
|
|
|
94
121
|
def test_no_set_is_could_not_run() -> None:
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
for row in (SEARCH_ROW, VCS_ROW):
|
|
123
|
+
verdict = runner.run(row, CORPUS, (), exhaustive=True)
|
|
124
|
+
assert verdict.outcome is Outcome.COULD_NOT_RUN
|
|
97
125
|
|
|
98
126
|
|
|
99
127
|
def test_this_gate_does_not_report_itself() -> None:
|
|
100
128
|
"""A check that forbids a string necessarily contains it. Both words are
|
|
101
129
|
assembled from fragments, so neither appears in the source as one token."""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
130
|
+
here = pathlib.Path(__file__).parent
|
|
131
|
+
sources = (
|
|
132
|
+
here / "base.py",
|
|
133
|
+
here.parent / "legacy_search" / "check.py",
|
|
134
|
+
here.parent / "vcs_in_scripts" / "check.py",
|
|
135
|
+
)
|
|
136
|
+
for source in sources:
|
|
137
|
+
body = source.read_text(encoding="utf-8")
|
|
138
|
+
for check in (LegacySearch(), VcsInScripts()):
|
|
139
|
+
assert check.WORD not in body, f"{check.WORD!r} appears literally in {source.name}"
|
|
106
140
|
|
|
107
141
|
|
|
108
142
|
def test_the_assembled_words_are_still_the_right_ones() -> None:
|
|
109
|
-
|
|
110
|
-
assert
|
|
143
|
+
assert VcsInScripts.WORD == "g" + "it"
|
|
144
|
+
assert LegacySearch.WORD == "g" + "rep"
|
|
111
145
|
|
|
112
146
|
|
|
113
147
|
def test_a_justfile_is_graded_by_name_not_suffix() -> None:
|
|
114
|
-
|
|
115
|
-
assert
|
|
116
|
-
assert
|
|
117
|
-
assert not check._is_shell("compliance/justfile.md")
|
|
148
|
+
assert is_shell_script("compliance/justfile")
|
|
149
|
+
assert is_shell_script("nested/dir/justfile")
|
|
150
|
+
assert not is_shell_script("compliance/justfile.md")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""REP-004 and REP-006 grade the shell scripts REP-007 selects; REP-006 leaves the managed `.workflows/` projection alone."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from adl_compliance import runner
|
|
10
|
+
from adl_compliance.core.verdict import Outcome, Verdict
|
|
11
|
+
from adl_compliance.policy.registry import load
|
|
12
|
+
|
|
13
|
+
SEARCH = load().by_id("REP-004")
|
|
14
|
+
VCS = load().by_id("REP-006")
|
|
15
|
+
GIT_CALL = "gi" + "t status\n"
|
|
16
|
+
GREP_CALL = "gr" + "ep -r x .\n"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def grade(row, tmp_path: Path, files: dict[str, str]) -> Verdict:
|
|
20
|
+
for rel, body in files.items():
|
|
21
|
+
target = tmp_path / rel
|
|
22
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
target.write_text(body, encoding="utf-8")
|
|
24
|
+
return runner.run(row, tmp_path, tuple(sorted(files)), exhaustive=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize(
|
|
28
|
+
"name",
|
|
29
|
+
["bin/tool", ".husky/pre-commit", "x.just", ".justfile", "Justfile", "bin/zsh-tool"],
|
|
30
|
+
)
|
|
31
|
+
def test_an_unsuffixed_or_alternately_named_script_is_graded(tmp_path: Path, name: str) -> None:
|
|
32
|
+
shebang = "#!/bin/zsh\n" if name.endswith("zsh-tool") else "#!/bin/sh\n"
|
|
33
|
+
body = "" if "just" in name.lower() else shebang
|
|
34
|
+
verdict = grade(VCS, tmp_path, {name: body + GIT_CALL})
|
|
35
|
+
assert verdict.outcome is Outcome.FINDINGS, verdict.render()
|
|
36
|
+
assert [f.path for f in verdict.findings] == [name]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_a_shebang_script_is_graded_by_rep_004_too(tmp_path: Path) -> None:
|
|
40
|
+
verdict = grade(SEARCH, tmp_path, {"bin/tool": "#!/usr/bin/env bash\n" + GREP_CALL})
|
|
41
|
+
assert [f.rule for f in verdict.findings] == ["GRP-001"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_a_non_shell_extensionless_file_is_not_graded(tmp_path: Path) -> None:
|
|
45
|
+
files = {"bin/py": "#!/usr/bin/env python3\n" + GIT_CALL, "bin/plain": GIT_CALL, "scripts/ok.sh": "echo hi\n"}
|
|
46
|
+
verdict = grade(VCS, tmp_path, files)
|
|
47
|
+
assert verdict.outcome is Outcome.CLEAN, verdict.render()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
PROJECTIONS = (".workflows", ".agents", ".claude", ".codex", ".cursor", ".kimi-code", ".opencode")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("root", PROJECTIONS)
|
|
54
|
+
def test_the_vcs_gate_exempts_each_managed_projection(tmp_path: Path, root: str) -> None:
|
|
55
|
+
clean = grade(VCS, tmp_path / "a", {f"{root}/hooks/x.sh": GIT_CALL, "scripts/ok.sh": "echo hi\n"})
|
|
56
|
+
assert clean.outcome is Outcome.CLEAN, clean.render()
|
|
57
|
+
flagged = grade(VCS, tmp_path / "b", {"scripts/x.sh": GIT_CALL, f"{root}/hooks/x.sh": GIT_CALL})
|
|
58
|
+
assert [f.path for f in flagged.findings] == ["scripts/x.sh"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize("root", PROJECTIONS)
|
|
62
|
+
def test_the_legacy_search_gate_still_grades_each_managed_projection(tmp_path: Path, root: str) -> None:
|
|
63
|
+
verdict = grade(SEARCH, tmp_path, {f"{root}/hooks/x.sh": GREP_CALL})
|
|
64
|
+
assert [f.path for f in verdict.findings] == [f"{root}/hooks/x.sh"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.parametrize("root", PROJECTIONS)
|
|
68
|
+
def test_the_multiline_comment_gate_still_grades_each_managed_projection(tmp_path: Path, root: str) -> None:
|
|
69
|
+
row = load().by_id("REP-007")
|
|
70
|
+
verdict = grade(row, tmp_path, {f"{root}/hooks/x.sh": "# one\n# two\n"})
|
|
71
|
+
assert [f.path for f in verdict.findings] == [f"{root}/hooks/x.sh"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""REP-004 (GRP-001) on the shared `banned_commands/base.py` scanner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from ..banned_commands.base import BannedCommand
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LegacySearch(BannedCommand):
|
|
11
|
+
id: ClassVar[str] = "REP-004"
|
|
12
|
+
rule: ClassVar[str] = "GRP-001"
|
|
13
|
+
WORD: ClassVar[str] = "gr" + "ep"
|
|
14
|
+
MESSAGE: ClassVar[str] = "standalone legacy search command is forbidden; use rg"
|
|
15
|
+
FIX: ClassVar[str] = "replace this invocation with an equivalent rg call"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""REP-004 (GRP-001) reports its own rule and no other."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pathlib
|
|
6
|
+
|
|
7
|
+
from adl_compliance import runner
|
|
8
|
+
from adl_compliance.core.verdict import Outcome
|
|
9
|
+
from adl_compliance.policy.registry import load
|
|
10
|
+
|
|
11
|
+
CORPUS = (
|
|
12
|
+
pathlib.Path(__file__).parent.parent / "banned_commands" / "fixtures" / "no-banned-commands"
|
|
13
|
+
)
|
|
14
|
+
ROW = load().by_id("REP-004")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _whole() -> tuple[str, ...]:
|
|
18
|
+
return tuple(
|
|
19
|
+
sorted(p.relative_to(CORPUS).as_posix() for p in CORPUS.rglob("*") if p.is_file())
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_reports_only_its_own_rule() -> None:
|
|
24
|
+
verdict = runner.run(ROW, CORPUS, _whole())
|
|
25
|
+
assert verdict.outcome is Outcome.FINDINGS
|
|
26
|
+
assert {f.rule for f in verdict.findings} == {"GRP-001"}
|
|
27
|
+
assert len(verdict.findings) == 3
|
|
28
|
+
assert "loose-grep.sh" in {pathlib.PurePosixPath(f.path).name for f in verdict.findings}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_is_addressable_by_its_registry_name() -> None:
|
|
32
|
+
assert load().resolve("repository.no-legacy-search").id == "REP-004"
|
|
@@ -67,7 +67,7 @@ specification that is neither a string nor a table, `workspace = true` with no
|
|
|
67
67
|
* A path dependency that is NOT a workspace member (any path dependency of a manifest
|
|
68
68
|
with no `[workspace]`, or one outside the workspace root) is judged by its own
|
|
69
69
|
presence in the lock only; its own dependencies are not read, so a dependency added
|
|
70
|
-
to such a crate (
|
|
70
|
+
to such a crate (a repository's `plugins/keychain` crate is one) is not detected.
|
|
71
71
|
Reading them would need that crate's own workspace for its `workspace = true`
|
|
72
72
|
entries and cargo's feature rules for its optional ones (an optional dependency of a
|
|
73
73
|
non-member is locked only when a feature activates it), and a wrong guess is a false
|
|
@@ -1,37 +1,23 @@
|
|
|
1
|
-
"""REP-002 —
|
|
2
|
-
backend lock without updates.
|
|
1
|
+
"""REP-002 — lockfiles must exist and Cargo must accept its lock without updates.
|
|
3
2
|
|
|
4
3
|
Ported from `compliance_new/checks/repository/lockfile-integrity/check.sh`.
|
|
5
|
-
Entirely `structural`:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
because ONE tree's scope matched, while a check that then hardcodes the
|
|
23
|
-
OTHER tree's default path grades a repository against a path it never
|
|
24
|
-
declared. `<rust>` resolves to `backend` and `<typescript>` resolves to
|
|
25
|
-
`frontend` in the packaged `policy/config.toml` — those are still the
|
|
26
|
-
defaults, just reached through `ctx.inputs` now, the same as every other
|
|
27
|
-
check that names a consumer path.
|
|
28
|
-
|
|
29
|
-
A tree declared absent (`rust = ""` or `typescript = ""` in
|
|
30
|
-
`[controls.compliance.trees]`) drops the inputs naming it, and each half of this
|
|
31
|
-
row is then graded on its own: a repository declaring no Rust tree is graded on
|
|
32
|
-
its TypeScript lockfile alone and is never asked for a `Cargo.lock`. With
|
|
33
|
-
neither tree left the row is could-not-run (see `structural`) -- it used to raise
|
|
34
|
-
`KeyError`.
|
|
4
|
+
Entirely `structural`: it opens NAMED paths and never consults the given file
|
|
5
|
+
set — the row's `engine.scope` decides whether this gate runs at all, not which
|
|
6
|
+
paths it reads.
|
|
7
|
+
|
|
8
|
+
**What it grades (Tarun, 2026-10-08, PBI 598).** The `rust` and `typescript`
|
|
9
|
+
trees are deleted; this check no longer resolves any tree. It grades:
|
|
10
|
+
|
|
11
|
+
* the REPOSITORY-ROOT pairs, automatically: `Cargo.toml` -> `Cargo.lock` and
|
|
12
|
+
`package.json` -> `bun.lock`. A root manifest that is present requires its
|
|
13
|
+
lockfile; a root manifest that is absent requires nothing;
|
|
14
|
+
* every OTHER lockfile only when the repository declares it under
|
|
15
|
+
`[controls.compliance.lockfiles]` (see below). A nested manifest nobody
|
|
16
|
+
declared is not graded: the engine does not guess where a repository keeps
|
|
17
|
+
its workspaces.
|
|
18
|
+
|
|
19
|
+
With neither a root manifest nor a declaration there is nothing to grade, and the
|
|
20
|
+
row is clean (`empty_scope_is_clean`; Tarun, 2026-10-08).
|
|
35
21
|
|
|
36
22
|
Retired 2026-10-01: `coverage.backend-ratchet` and `coverage.backend-crate`,
|
|
37
23
|
named here as precedents, were deleted with the `coverage` domain
|
|
@@ -104,16 +90,11 @@ declared by its manifests list and its lockfiles list together:
|
|
|
104
90
|
bun-manifests / bun-lockfiles `package.json` paths or globs / `bun.lock` paths
|
|
105
91
|
cargo-manifests / cargo-lockfiles `Cargo.toml` paths or globs / `Cargo.lock` paths
|
|
106
92
|
|
|
107
|
-
Why: the registry scope is
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
manifests sit at the repository ROOT and under `apps/*/` and `packages/*/`, so on
|
|
111
|
-
a whole-tree run `engine.scope` matched no file and the row was could-not-run
|
|
112
|
-
(measured live: a narrowed run passed, the exhaustive run could not). The
|
|
113
|
-
per-gate `bun_lock` input moved the grade and never the scope.
|
|
93
|
+
Why: the registry scope is the four root file names, so a workspace that keeps
|
|
94
|
+
its manifests under `apps/*/` or `packages/*/` is invisible to it. The declaration
|
|
95
|
+
states those paths.
|
|
114
96
|
|
|
115
|
-
A declared half
|
|
116
|
-
used:
|
|
97
|
+
A declared half ADDS to the root pair, in both places the paths are used:
|
|
117
98
|
|
|
118
99
|
* scope (`declared_scope`): which changed files select the gate, and, because a
|
|
119
100
|
restated scope is graded whole, which files `graded` is handed;
|
|
@@ -127,8 +108,7 @@ at least one file of the complete in-scope set, or the row is could-not-run
|
|
|
127
108
|
naming the declaration and the entry. A declared glob that matches nothing would
|
|
128
109
|
otherwise shrink what the gate scans to nothing and say OK.
|
|
129
110
|
|
|
130
|
-
An undeclared half is exactly
|
|
131
|
-
early-return shape. A malformed declaration (empty, absolute, backslash, `.`/`..`
|
|
111
|
+
An undeclared half is exactly the root pair, with the same early-return shape. A malformed declaration (empty, absolute, backslash, `.`/`..`
|
|
132
112
|
segments, placeholder or brace or negation syntax, duplicate, the wrong file
|
|
133
113
|
name, a glob in a lockfile entry, or a half with only one of its two lists) is
|
|
134
114
|
could-not-run naming the declaration; the engine never grades against a guess at
|
|
@@ -160,8 +140,8 @@ class Half(NamedTuple):
|
|
|
160
140
|
"""The `--declare` name carrying the literal lockfile paths."""
|
|
161
141
|
manifest_name: str
|
|
162
142
|
"""The file name every manifest entry must end in."""
|
|
163
|
-
|
|
164
|
-
"""
|
|
143
|
+
lockfile_name: str
|
|
144
|
+
"""The file name of this half's lockfile, beside the manifest at the root."""
|
|
165
145
|
|
|
166
146
|
|
|
167
147
|
class Declared(NamedTuple):
|
|
@@ -172,22 +152,12 @@ class Declared(NamedTuple):
|
|
|
172
152
|
class LockfileIntegrity(Check):
|
|
173
153
|
id: ClassVar[str] = "REP-002"
|
|
174
154
|
|
|
175
|
-
CARGO: ClassVar[Half] = Half("cargo-manifests", "cargo-lockfiles", "Cargo.toml", "
|
|
176
|
-
BUN: ClassVar[Half] = Half("bun-manifests", "bun-lockfiles", "package.json", "
|
|
155
|
+
CARGO: ClassVar[Half] = Half("cargo-manifests", "cargo-lockfiles", "Cargo.toml", "Cargo.lock")
|
|
156
|
+
BUN: ClassVar[Half] = Half("bun-manifests", "bun-lockfiles", "package.json", "bun.lock")
|
|
177
157
|
# Cargo first, as the registry lists it and as `structural` grades it.
|
|
178
158
|
HALVES: ClassVar[tuple[Half, ...]] = (CARGO, BUN)
|
|
179
159
|
|
|
180
|
-
|
|
181
|
-
"workspace": "<rust>",
|
|
182
|
-
"cargo_lock": "<rust>/Cargo.lock",
|
|
183
|
-
"cargo_manifest": "<rust>/Cargo.toml",
|
|
184
|
-
"bun_lock": "<typescript>/bun.lock",
|
|
185
|
-
}
|
|
186
|
-
"""Named, declared, and resolved through the SAME tree map every scope
|
|
187
|
-
pattern uses (K-42) — the fix for the hardcoded-literal bug this class's
|
|
188
|
-
docstring describes. A repository declaring `rust = "rust-workspace"` (or
|
|
189
|
-
no `rust` tree at all) is graded against that declaration, never the
|
|
190
|
-
packaged `backend`/`frontend` defaults baked into Python."""
|
|
160
|
+
empty_scope_is_clean: ClassVar[bool] = True
|
|
191
161
|
|
|
192
162
|
MISSING: ClassVar[str] = "LCK-001"
|
|
193
163
|
STALE: ClassVar[str] = "LCK-002"
|
|
@@ -281,12 +251,12 @@ class LockfileIntegrity(Check):
|
|
|
281
251
|
def declared_scope(
|
|
282
252
|
cls, scope: tuple[str, ...], declarations: Mapping[str, str]
|
|
283
253
|
) -> tuple[str, ...] | None:
|
|
284
|
-
"""The registry scope
|
|
254
|
+
"""The registry scope (the root pairs) plus every declared manifest and lockfile.
|
|
285
255
|
|
|
286
|
-
A
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
256
|
+
A declaration ADDS to the root pair: the root `Cargo.toml` and
|
|
257
|
+
`package.json` are graded whether or not a repository also declares other
|
|
258
|
+
workspaces. The scope is restated only when something is declared, and a
|
|
259
|
+
restated scope is graded whole (`Check.declared_scope`).
|
|
290
260
|
"""
|
|
291
261
|
restated = scope
|
|
292
262
|
any_declared = False
|
|
@@ -295,77 +265,39 @@ class LockfileIntegrity(Check):
|
|
|
295
265
|
if declared is None:
|
|
296
266
|
continue
|
|
297
267
|
any_declared = True
|
|
298
|
-
restated = (
|
|
299
|
-
*(pattern for pattern in restated if not pattern.startswith(half.tree)),
|
|
300
|
-
*declared.manifests,
|
|
301
|
-
*declared.lockfiles,
|
|
302
|
-
)
|
|
268
|
+
restated = (*restated, *declared.manifests, *declared.lockfiles)
|
|
303
269
|
return restated if any_declared else None
|
|
304
270
|
|
|
305
271
|
# ------------------------------------------------------------- structural
|
|
306
272
|
|
|
307
|
-
# The three inputs that all name the `<rust>` tree. They are resolved or
|
|
308
|
-
# dropped together, so seeing only some of them means a per-gate
|
|
309
|
-
# `--input REP-002:<name>=...` replaced part of the triple.
|
|
310
|
-
CARGO_INPUTS: ClassVar[tuple[str, ...]] = ("workspace", "cargo_lock", "cargo_manifest")
|
|
311
|
-
|
|
312
273
|
def structural(self, ctx: Context) -> Iterable[Report]:
|
|
313
|
-
# Decision for a tree declared absent (`rust = ""` / `typescript = ""`,
|
|
314
|
-
# which drops every input naming the tree). Unlike an exclusion path, a
|
|
315
|
-
# lockfile IS what this row grades, so each half is judged on its own:
|
|
316
|
-
#
|
|
317
|
-
# * a half whose tree is declared absent is not graded -- the owner
|
|
318
|
-
# stated there is no such lockfile to require, which is the narrower,
|
|
319
|
-
# honest verdict `resolve_inputs` documents ("grades a smaller scope");
|
|
320
|
-
# * with NEITHER half left there is nothing to grade, and an empty set of
|
|
321
|
-
# findings would read as a pass -- `CouldNotRun`, naming both inputs and
|
|
322
|
-
# trees;
|
|
323
|
-
# * a half-resolved Cargo triple is `CouldNotRun`, naming what is missing.
|
|
324
|
-
#
|
|
325
|
-
# This read `ctx.inputs["cargo_lock"]` and `["bun_lock"]` unguarded: a
|
|
326
|
-
# `KeyError` traceback (exit 1), not a verdict, whenever either tree was
|
|
327
|
-
# declared absent.
|
|
328
|
-
#
|
|
329
|
-
# A half the repository DECLARES (see the module docstring) is graded from
|
|
330
|
-
# its declaration and never from the tree or the inputs; the rules above
|
|
331
|
-
# apply to the halves it did not declare.
|
|
332
|
-
cargo_declared = self.declared(self.CARGO, ctx.declarations)
|
|
333
|
-
bun_declared = self.declared(self.BUN, ctx.declarations)
|
|
334
|
-
|
|
335
|
-
present_cargo = [name for name in self.CARGO_INPUTS if name in ctx.inputs]
|
|
336
|
-
if cargo_declared is None and present_cargo and len(present_cargo) != len(self.CARGO_INPUTS):
|
|
337
|
-
missing = [name for name in self.CARGO_INPUTS if name not in ctx.inputs]
|
|
338
|
-
raise CouldNotRun(
|
|
339
|
-
f"{self.id}: the Cargo inputs resolve together from <rust>, but only "
|
|
340
|
-
f"{present_cargo} are set; missing {missing} -- declare "
|
|
341
|
-
"[controls.compliance.trees].rust, or override all three"
|
|
342
|
-
)
|
|
343
|
-
|
|
344
274
|
# (lockfile, cargo workspace, manifest) per Cargo lockfile to grade.
|
|
345
275
|
cargo: list[tuple[Path, Path, Path]] = []
|
|
276
|
+
bun: list[Path] = []
|
|
277
|
+
|
|
278
|
+
# The repository-root pairs, graded because the root manifest is there.
|
|
279
|
+
root_manifest = ctx.path(self.CARGO.manifest_name)
|
|
280
|
+
if self.state(ctx, root_manifest) is not FileState.MISSING:
|
|
281
|
+
cargo.append((ctx.path(self.CARGO.lockfile_name), ctx.root, root_manifest))
|
|
282
|
+
if self.state(ctx, ctx.path(self.BUN.manifest_name)) is not FileState.MISSING:
|
|
283
|
+
bun.append(ctx.path(self.BUN.lockfile_name))
|
|
284
|
+
|
|
285
|
+
# Everything else comes only from the repository's own declaration.
|
|
286
|
+
cargo_declared = self.declared(self.CARGO, ctx.declarations)
|
|
346
287
|
if cargo_declared is not None:
|
|
347
288
|
for relative in cargo_declared.lockfiles:
|
|
348
289
|
lock = ctx.path(relative)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
(ctx.inputs["cargo_lock"], ctx.inputs["workspace"], ctx.inputs["cargo_manifest"])
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
bun: list[Path] = []
|
|
290
|
+
if all(lock != known for known, _, _ in cargo):
|
|
291
|
+
cargo.append((lock, lock.parent, lock.parent / "Cargo.toml"))
|
|
292
|
+
bun_declared = self.declared(self.BUN, ctx.declarations)
|
|
356
293
|
if bun_declared is not None:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
294
|
+
for relative in bun_declared.lockfiles:
|
|
295
|
+
lock = ctx.path(relative)
|
|
296
|
+
if lock not in bun:
|
|
297
|
+
bun.append(lock)
|
|
360
298
|
|
|
361
299
|
if not cargo and not bun:
|
|
362
|
-
|
|
363
|
-
f"{self.id}: no lockfile to grade -- `cargo_lock` (<rust>/Cargo.lock) and "
|
|
364
|
-
"`bun_lock` (<typescript>/bun.lock) are both absent because the trees "
|
|
365
|
-
"<rust> and <typescript> are both declared absent in "
|
|
366
|
-
"[controls.compliance.trees]; a row that grades nothing is could-not-run, "
|
|
367
|
-
"never a clean pass"
|
|
368
|
-
)
|
|
300
|
+
return
|
|
369
301
|
|
|
370
302
|
for lock, workspace, _manifest in cargo:
|
|
371
303
|
if self.state(ctx, lock) is not FileState.PRESENT:
|
|
@@ -382,18 +314,18 @@ class LockfileIntegrity(Check):
|
|
|
382
314
|
for lock in bun:
|
|
383
315
|
if self.state(ctx, lock) is not FileState.PRESENT:
|
|
384
316
|
shown = ctx.relative(lock)
|
|
385
|
-
|
|
317
|
+
bun_dir = shown.rsplit("/", 1)[0] if "/" in shown else "."
|
|
386
318
|
yield self.report(
|
|
387
319
|
shown,
|
|
388
320
|
f"missing {shown}",
|
|
389
|
-
f"run: (cd {
|
|
321
|
+
f"run: (cd {bun_dir} && bun install) to generate the "
|
|
390
322
|
"lockfile and commit the result",
|
|
391
323
|
rule=self.MISSING,
|
|
392
324
|
)
|
|
393
325
|
return
|
|
394
326
|
|
|
395
327
|
if not cargo:
|
|
396
|
-
#
|
|
328
|
+
# No Cargo lockfile to grade: the Bun lockfiles above were the whole row.
|
|
397
329
|
return
|
|
398
330
|
|
|
399
331
|
if shutil.which("cargo") is None:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# fixture lock
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fn main() {}
|