@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.29 → 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/cli/args.ts +2 -2
- package/packages/agent-hooks/src/cli/doctor.ts +2 -1
- package/packages/agent-hooks/src/cli/rules.ts +2 -1
- package/packages/agent-hooks/src/deploy/opencode-template.ts +5 -3
- package/packages/agent-hooks/src/gating/config.ts +11 -0
- package/packages/agent-hooks/src/gating/running-adl.ts +24 -0
- 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/hooks/user-prompt-submit/handler.ts +2 -2
- package/packages/agent-hooks/src/log/writer.ts +170 -24
- package/packages/agent-hooks/src/main.ts +11 -4
- package/packages/agent-hooks/src/pre-tool-use/handler.ts +5 -4
- 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/azdo/src/azdo-wit.ts +8 -2
- package/packages/azdo/src/mcp-server.ts +1 -1
- 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 +38 -20
- 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,112 @@
|
|
|
1
|
+
// Post-install grading (Bug 570); see README, "Compliance seeding".
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { loadComplianceConfig } from './compliance-config.ts'
|
|
6
|
+
import { runTool } from './hook.ts'
|
|
7
|
+
import { which, type Env } from './runtime.ts'
|
|
8
|
+
|
|
9
|
+
interface ComplianceFinding {
|
|
10
|
+
message: string
|
|
11
|
+
/** What the engine says to do; '' when the finding carries no fix clause. */
|
|
12
|
+
fix: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ComplianceGate {
|
|
16
|
+
id: string
|
|
17
|
+
address: string
|
|
18
|
+
/** FAIL: graded and found wrong. ERROR: could not run. */
|
|
19
|
+
status: 'FAIL' | 'ERROR'
|
|
20
|
+
summary: string
|
|
21
|
+
findings: ComplianceFinding[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface InstallCompliance {
|
|
25
|
+
/** True only when every enabled gate PASSED. */
|
|
26
|
+
ok: boolean
|
|
27
|
+
enabled: string[]
|
|
28
|
+
failing: ComplianceGate[]
|
|
29
|
+
/** Printable lines for an agent. */
|
|
30
|
+
guidance: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ROW = /^(\S+)\s+(\S+)\s+\S\s+(FAIL|ERROR)\s*(.*)$/
|
|
34
|
+
const FINDING = /^\s{4}(\S.*)$/
|
|
35
|
+
|
|
36
|
+
/** Parse the engine's failures.log into its non-passing gates. */
|
|
37
|
+
function parseFailuresLog(text: string): ComplianceGate[] {
|
|
38
|
+
const gates: ComplianceGate[] = []
|
|
39
|
+
for (const line of text.split('\n')) {
|
|
40
|
+
if (line.startsWith('#') || line.trim() === '') continue
|
|
41
|
+
const finding = FINDING.exec(line)
|
|
42
|
+
if (finding !== null) {
|
|
43
|
+
const current = gates.at(-1)
|
|
44
|
+
if (current === undefined) throw new Error(`compliance failures.log has a finding before any gate row: ${line}`)
|
|
45
|
+
const at = finding[1]!.lastIndexOf('; fix: ')
|
|
46
|
+
current.findings.push(
|
|
47
|
+
at === -1 ? { message: finding[1]!, fix: '' } : { message: finding[1]!.slice(0, at), fix: finding[1]!.slice(at + '; fix: '.length) },
|
|
48
|
+
)
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
const row = ROW.exec(line)
|
|
52
|
+
if (row === null) throw new Error(`compliance failures.log has a line this installer cannot read: ${line}`)
|
|
53
|
+
gates.push({ id: row[1]!, address: row[2]!, status: row[3] as 'FAIL' | 'ERROR', summary: row[4]!.trim(), findings: [] })
|
|
54
|
+
}
|
|
55
|
+
return gates
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const SWITCH_OFF_GUIDANCE =
|
|
59
|
+
"To switch a gate off, remove it from [controls.compliance] enabled in adl-config.toml \u2014 that is this repository's decision, and adl-config.toml is a protected file landed by the owner."
|
|
60
|
+
|
|
61
|
+
function guidanceFor(enabled: string[], failing: ComplianceGate[]): string[] {
|
|
62
|
+
if (enabled.length === 0) {
|
|
63
|
+
return ['adl-config.toml [controls.compliance].enabled is empty: nothing is enforced. Name the gates to arm.']
|
|
64
|
+
}
|
|
65
|
+
if (failing.length === 0) return [`This repository now enforces ${enabled.length} compliance gates; all pass.`, SWITCH_OFF_GUIDANCE]
|
|
66
|
+
const lines = [
|
|
67
|
+
`This repository now enforces ${enabled.length} compliance gates; ${failing.length} failing. Fix the findings below before committing.`,
|
|
68
|
+
]
|
|
69
|
+
for (const gate of failing) {
|
|
70
|
+
if (gate.status === 'ERROR') {
|
|
71
|
+
lines.push(`COULD-NOT-RUN ${gate.address} (${gate.id}): ${gate.summary}`)
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
lines.push(`FAIL ${gate.address} (${gate.id}): ${gate.summary}`)
|
|
75
|
+
for (const finding of gate.findings) {
|
|
76
|
+
lines.push(` - ${finding.message}`)
|
|
77
|
+
if (finding.fix !== '') lines.push(` fix: ${finding.fix}`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
lines.push(SWITCH_OFF_GUIDANCE)
|
|
81
|
+
return lines
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class UvMissingError extends Error {}
|
|
85
|
+
|
|
86
|
+
/** Grade `repo` with the gates its `enabled` names; throws when no verdict is known, and an empty `enabled` runs nothing. */
|
|
87
|
+
export function gradeInstalledRepo(repo: string, env: Env): InstallCompliance {
|
|
88
|
+
const enabled = loadComplianceConfig(repo).enabled
|
|
89
|
+
if (enabled.length === 0) return { ok: true, enabled, failing: [], guidance: guidanceFor(enabled, []) }
|
|
90
|
+
if (which('uv', env) === null) {
|
|
91
|
+
throw new UvMissingError(
|
|
92
|
+
'uv not found on PATH — install wired the hooks and armed the compliance gates, but cannot grade them. Install uv (https://docs.astral.sh/uv/), then run `adl repo controls compliance run --repo ' + repo + '`',
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
const logDir = mkdtempSync(join(tmpdir(), 'adl-install-compliance-'))
|
|
96
|
+
try {
|
|
97
|
+
const code = runTool('compliance', repo, ['--summary', '--log-dir', logDir], { env, cwd: repo, err: () => {}, quietEngine: true })
|
|
98
|
+
let log: string | null = null
|
|
99
|
+
try {
|
|
100
|
+
log = readFileSync(join(logDir, 'failures.log'), 'utf8')
|
|
101
|
+
} catch {
|
|
102
|
+
log = null
|
|
103
|
+
}
|
|
104
|
+
if (code === 0) return { ok: true, enabled, failing: [], guidance: guidanceFor(enabled, []) }
|
|
105
|
+
if (log === null) throw new Error(`the compliance engine exited ${code} without a failures.log, so no gate verdict is known`)
|
|
106
|
+
const failing = parseFailuresLog(log)
|
|
107
|
+
if (failing.length === 0) throw new Error(`the compliance engine exited ${code} but its failures.log names no gate`)
|
|
108
|
+
return { ok: false, enabled, failing, guidance: guidanceFor(enabled, failing) }
|
|
109
|
+
} finally {
|
|
110
|
+
rmSync(logDir, { recursive: true, force: true })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
// every install. The only preserved bytes are pre-hard-cut hook-log evidence;
|
|
4
4
|
// there is no adoption, merge, or displaced-original path for managed content.
|
|
5
5
|
import { createHash } from 'node:crypto'
|
|
6
|
-
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
7
7
|
import { dirname, join } from 'node:path'
|
|
8
|
-
import {
|
|
8
|
+
import type { SeededAttribute } from './config.ts'
|
|
9
|
+
import { DEFAULT_COMPLIANCE_CONFIG_TEXT, SEEDED_COMPLIANCE_ATTRIBUTES } from './compliance-config.ts'
|
|
10
|
+
import { gradeInstalledRepo, type InstallCompliance } from './install-compliance.ts'
|
|
11
|
+
import type { Env } from './runtime.ts'
|
|
9
12
|
import {
|
|
10
13
|
appendToTableSpan,
|
|
11
|
-
DEFAULT_CONFIG_TEXT,
|
|
12
14
|
extractTableSpan,
|
|
15
|
+
integritySeed,
|
|
13
16
|
isTable,
|
|
14
17
|
removeTableSpan,
|
|
15
18
|
removeFromTableSpan,
|
|
@@ -21,10 +24,15 @@ import {
|
|
|
21
24
|
ADL_NPM_INSTALL,
|
|
22
25
|
CONFIG_REL,
|
|
23
26
|
GITIGNORE_REL,
|
|
27
|
+
GLOBAL_INVARIANTS_SOURCE,
|
|
28
|
+
GLOBAL_INVARIANTS_TARGET,
|
|
24
29
|
HOOK_NAMES,
|
|
25
30
|
HOOKS_PATH,
|
|
26
31
|
LOCK_REL,
|
|
27
32
|
MANAGED_ASSETS,
|
|
33
|
+
SCAFFOLD_ROOT,
|
|
34
|
+
SCAFFOLD_ROOT_FILES,
|
|
35
|
+
SCAFFOLD_TREES,
|
|
28
36
|
TOOL,
|
|
29
37
|
VERSION,
|
|
30
38
|
WORKFLOWS_ROOT,
|
|
@@ -183,6 +191,24 @@ class Ops {
|
|
|
183
191
|
if (mode !== undefined) chmodSync(path, mode)
|
|
184
192
|
}
|
|
185
193
|
|
|
194
|
+
/** Create `rel` only if nothing is there; see README, "Scaffolding". */
|
|
195
|
+
create(rel: string, content: string | Buffer, reason: string): void {
|
|
196
|
+
if (this.writtenSoFar.has(rel) || lstatSync(this.abs(rel), { throwIfNoEntry: false }) !== undefined) return
|
|
197
|
+
this.write(rel, content, reason)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Whether anything (file, folder or link, even dangling) is at `rel`. A non-directory in the way of a parent fails loudly. */
|
|
201
|
+
occupied(rel: string): boolean {
|
|
202
|
+
const segments = rel.split('/')
|
|
203
|
+
for (let end = 1; end <= segments.length; end += 1) {
|
|
204
|
+
const current = lstatSync(this.abs(segments.slice(0, end).join('/')), { throwIfNoEntry: false })
|
|
205
|
+
if (current === undefined) return false
|
|
206
|
+
if (current.isSymbolicLink() && statSync(this.abs(segments.slice(0, end).join('/')), { throwIfNoEntry: false })?.isDirectory() === true) continue
|
|
207
|
+
if (!current.isDirectory()) throw new Error(`cannot scaffold ${rel}: ${segments.slice(0, end).join('/')} exists and is not a directory`)
|
|
208
|
+
}
|
|
209
|
+
return true
|
|
210
|
+
}
|
|
211
|
+
|
|
186
212
|
remove(rel: string, reason: string): void {
|
|
187
213
|
const path = this.abs(rel)
|
|
188
214
|
// `existsSync` follows links and reports false for a dangling link. Owned
|
|
@@ -204,12 +230,18 @@ class Ops {
|
|
|
204
230
|
|
|
205
231
|
export interface InstallOptions {
|
|
206
232
|
dryRun?: boolean
|
|
233
|
+
/** Grade the repository once the wiring is complete (never under dryRun); see README, "Compliance seeding". */
|
|
234
|
+
grade?: { env: Env }
|
|
235
|
+
/** Re-wire only: no config seeding, no scaffolding, no grading (fleet reconcile; see README, "Re-wiring"). */
|
|
236
|
+
rewire?: boolean
|
|
207
237
|
}
|
|
208
238
|
|
|
209
239
|
export interface InstallResult {
|
|
210
240
|
repo: string
|
|
211
241
|
actions: Action[]
|
|
212
242
|
dryRun: boolean
|
|
243
|
+
/** Present only when `options.grade` was given and this was a real run. */
|
|
244
|
+
compliance?: InstallCompliance
|
|
213
245
|
}
|
|
214
246
|
|
|
215
247
|
/**
|
|
@@ -238,6 +270,7 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
|
|
|
238
270
|
// Validate only repo-controls' declared tables before deleting its root.
|
|
239
271
|
// Other owners' tables remain deliberately opaque.
|
|
240
272
|
validateOwnedConfig(initialConfigText)
|
|
273
|
+
declaredScaffoldTrees(initialConfigText)
|
|
241
274
|
// The lock is outside neither configuration nor runtime boundaries, so read
|
|
242
275
|
// its bookkeeping before replacing its containing root. It is never used
|
|
243
276
|
// to preserve root content.
|
|
@@ -271,32 +304,39 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
|
|
|
271
304
|
files[rel] = { sha256: sha256(text) }
|
|
272
305
|
}
|
|
273
306
|
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
SEEDED_ATTRIBUTES,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
307
|
+
const rewire = options.rewire === true
|
|
308
|
+
let config: TableConfigRecord = prev?.config ?? null
|
|
309
|
+
let complianceConfig: TableConfigRecord = prev?.complianceConfig ?? null
|
|
310
|
+
if (!rewire) {
|
|
311
|
+
const controlsAttributes = [...SEEDED_ATTRIBUTES, ...integrityWatchSeed(initialConfigText)]
|
|
312
|
+
const controlsResult = installConfigTable(
|
|
313
|
+
initialConfigText,
|
|
314
|
+
'controls',
|
|
315
|
+
`[controls]${controlsAttributes.map((a) => a.text).join('')}`,
|
|
316
|
+
controlsAttributes,
|
|
317
|
+
prev?.config ?? null,
|
|
318
|
+
ops,
|
|
319
|
+
)
|
|
320
|
+
const complianceResult = installConfigTable(
|
|
321
|
+
controlsResult.text,
|
|
322
|
+
'controls.compliance',
|
|
323
|
+
DEFAULT_COMPLIANCE_CONFIG_TEXT,
|
|
324
|
+
SEEDED_COMPLIANCE_ATTRIBUTES,
|
|
325
|
+
prev?.complianceConfig ?? null,
|
|
326
|
+
ops,
|
|
327
|
+
)
|
|
328
|
+
// Both entries' hashes are computed from the SAME final merged text (not each call's
|
|
329
|
+
// intermediate view of it): appending [compliance] after [controls] can extend the byte
|
|
330
|
+
// span findTableSpan reports for [controls] (e.g. a trailing blank separator line before
|
|
331
|
+
// the next header), so hashing at each call's own intermediate text would immediately
|
|
332
|
+
// go stale the moment a later call appends more content to the same file.
|
|
333
|
+
const finalConfigText = complianceResult.text
|
|
334
|
+
config = { created: controlsResult.created, sha256: sha256(extractTableSpan(finalConfigText, 'controls') as string) }
|
|
335
|
+
complianceConfig = {
|
|
336
|
+
created: complianceResult.created,
|
|
337
|
+
sha256: sha256(extractTableSpan(finalConfigText, 'controls.compliance') as string),
|
|
338
|
+
}
|
|
339
|
+
scaffoldStandardTrees(finalConfigText, ops)
|
|
300
340
|
}
|
|
301
341
|
|
|
302
342
|
const gitignore = installGitignore(repo, prev?.gitignore ?? null, ops)
|
|
@@ -314,7 +354,19 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
|
|
|
314
354
|
gitignore,
|
|
315
355
|
}
|
|
316
356
|
ops.write(LOCK_REL, `${JSON.stringify(lock, null, 2)}\n`, 'install lock')
|
|
317
|
-
|
|
357
|
+
// Graded last, so a missing `uv` fails loudly only after every file is wired.
|
|
358
|
+
const compliance = options.grade !== undefined && !dryRun && !rewire ? gradeInstalledRepo(repo, options.grade.env) : undefined
|
|
359
|
+
return { repo, actions: ops.actions, dryRun, ...(compliance === undefined ? {} : { compliance }) }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The `[controls] integrity` seed: watch the declared compliance register folder (see README, "Compliance seeding"). Empty when no folder is declared. */
|
|
363
|
+
function integrityWatchSeed(configText: string): SeededAttribute[] {
|
|
364
|
+
const trees = ownedTable(configText, 'controls.compliance')?.trees
|
|
365
|
+
// declaredScaffoldTrees ran first in install() and already rejected a non-string value.
|
|
366
|
+
const declared = (isTable(trees) ? (trees.compliance ?? 'compliance') : 'compliance') as string
|
|
367
|
+
const folder = declared.replace(/^(\.\/)+/, '').replace(/\/+$/, '')
|
|
368
|
+
if (folder === '') return []
|
|
369
|
+
return [integritySeed(folder)]
|
|
318
370
|
}
|
|
319
371
|
|
|
320
372
|
/** The value at a dotted table path (`controls.compliance`), or undefined if absent. */
|
|
@@ -425,6 +477,57 @@ function installConfigTable(
|
|
|
425
477
|
return { text: appended, created: true }
|
|
426
478
|
}
|
|
427
479
|
|
|
480
|
+
/** The scaffold trees declared in config, validated; throws on a malformed path so install can refuse before any write. */
|
|
481
|
+
function declaredScaffoldTrees(configText: string): { name: string; base: string }[] {
|
|
482
|
+
const trees: { name: string; base: string }[] = []
|
|
483
|
+
const declared = ownedTable(configText, 'controls.compliance')?.trees
|
|
484
|
+
if (!isTable(declared)) return trees
|
|
485
|
+
assertComplianceTree(declared.compliance)
|
|
486
|
+
for (const name of SCAFFOLD_TREES) {
|
|
487
|
+
const value = declared[name]
|
|
488
|
+
if (value === undefined || value === '') continue
|
|
489
|
+
if (typeof value !== 'string') throw new Error(`${CONFIG_REL} [controls.compliance.trees] ${name} must be a string`)
|
|
490
|
+
const base = value.replace(/^(\.\/)+/, '').replace(/\/+$/, '')
|
|
491
|
+
if (!isRelative(base)) throw new Error(`${CONFIG_REL} [controls.compliance.trees] ${name} must be a repository-relative path: ${value}`)
|
|
492
|
+
trees.push({ name, base })
|
|
493
|
+
}
|
|
494
|
+
return trees
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** The compliance register folder is not scaffolded but is seeded into `[controls] integrity`, so it takes the tree path rules plus a safe character set. */
|
|
498
|
+
function assertComplianceTree(value: unknown): void {
|
|
499
|
+
if (value === undefined || value === '') return
|
|
500
|
+
const where = `${CONFIG_REL} [controls.compliance.trees] compliance`
|
|
501
|
+
if (typeof value !== 'string') throw new Error(`${where} must be a string`)
|
|
502
|
+
const base = value.replace(/^(\.\/)+/, '').replace(/\/+$/, '')
|
|
503
|
+
if (!isRelative(base)) throw new Error(`${where} must be a repository-relative path: ${value}`)
|
|
504
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(base)) throw new Error(`${where} may only contain letters, digits, '.', '_', '-' and '/': ${value}`)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Bootstrap the standard trees at their declared paths, only where the folder is missing; see README, "Scaffolding". */
|
|
508
|
+
function scaffoldStandardTrees(configText: string, ops: Ops): void {
|
|
509
|
+
const missing = declaredScaffoldTrees(configText).filter(({ base }) => !ops.occupied(base))
|
|
510
|
+
for (const rel of SCAFFOLD_ROOT_FILES) {
|
|
511
|
+
ops.create(rel, readFileSync(join(SCAFFOLD_ROOT, 'root', rel)), 'scaffold the repository entry files (user-owned after install)')
|
|
512
|
+
}
|
|
513
|
+
ops.create(GLOBAL_INVARIANTS_TARGET, readFileSync(GLOBAL_INVARIANTS_SOURCE), 'deploy the global invariants (user-owned after install)')
|
|
514
|
+
for (const { name, base } of missing) {
|
|
515
|
+
for (const rel of scaffoldFiles(join(SCAFFOLD_ROOT, name), '')) {
|
|
516
|
+
ops.create(`${base}/${rel}`, readFileSync(join(SCAFFOLD_ROOT, name, rel)), `scaffold the ${name} tree (user-owned after install)`)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Every file under `dir`, relative to it, sorted, depth-first. */
|
|
522
|
+
function scaffoldFiles(dir: string, prefix: string): string[] {
|
|
523
|
+
return readdirSync(join(dir, prefix), { withFileTypes: true })
|
|
524
|
+
.sort((a, b) => (a.name < b.name ? -1 : 1))
|
|
525
|
+
.flatMap((entry) => {
|
|
526
|
+
const rel = prefix === '' ? entry.name : `${prefix}/${entry.name}`
|
|
527
|
+
return entry.isDirectory() ? scaffoldFiles(dir, rel) : [rel]
|
|
528
|
+
})
|
|
529
|
+
}
|
|
530
|
+
|
|
428
531
|
function installGitignore(repo: string, prev: GitignoreRecord | null, ops: Ops): GitignoreRecord {
|
|
429
532
|
const path = join(repo, GITIGNORE_REL)
|
|
430
533
|
const exists = existsSync(path)
|