@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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `.adl/logs/` logger (docs/architecture/cli-design.md §5a, item 9). `agent-hooks`
|
|
3
|
-
* "holds logs" (§12): this module lives here and writes
|
|
4
|
-
* `hooks-deny.log` (`../hooks/log.ts`);
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* "holds logs" (§12): this module lives here and writes
|
|
4
|
+
* `agent-hooks/hooks.log` / `agent-hooks/deny.log` (`../hooks/log.ts`);
|
|
5
|
+
* `apps/cli/src/log.ts` re-exports it so `adl run`'s `agents/tool-calls.log`
|
|
6
|
+
* writer and repo-controls' `git-hooks/` writer share it. One folder per
|
|
7
|
+
* owner under `.adl/logs/` (Bug 600); every stream rotates through here.
|
|
8
8
|
*
|
|
9
9
|
* - JSON Lines, one object per line. Fields are written in a fixed order;
|
|
10
10
|
* an absent field is OMITTED, never null-filled.
|
|
@@ -14,24 +14,35 @@
|
|
|
14
14
|
* must never fail the caller.
|
|
15
15
|
* - `command` is redacted with `redact()` before it is written.
|
|
16
16
|
* - Rotation: a file that would exceed `maxBytes` (10 MiB by default) is
|
|
17
|
-
* rotated to `.1` … `.5`, dropping `.5`, before the new line is written
|
|
17
|
+
* rotated to `.1` … `.5`, dropping `.5`, before the new line is written,
|
|
18
|
+
* under a per-file lock (README, "Hook logs").
|
|
18
19
|
*/
|
|
20
|
+
import { randomBytes } from "node:crypto";
|
|
19
21
|
import { spawnSync } from "node:child_process";
|
|
20
22
|
import {
|
|
21
23
|
appendFileSync,
|
|
22
24
|
chmodSync,
|
|
23
25
|
existsSync,
|
|
26
|
+
linkSync,
|
|
24
27
|
mkdirSync,
|
|
28
|
+
readdirSync,
|
|
29
|
+
readFileSync,
|
|
25
30
|
renameSync,
|
|
31
|
+
rmSync,
|
|
26
32
|
statSync,
|
|
27
|
-
unlinkSync,
|
|
28
33
|
writeFileSync,
|
|
29
34
|
} from "node:fs";
|
|
30
35
|
import { homedir } from "node:os";
|
|
31
36
|
import { basename, dirname, join } from "node:path";
|
|
32
37
|
import { ensureAdlGitignore } from "./adl-dir.ts";
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
/** Every stream as `<owner folder>/<file>` under `.adl/logs/` (README, "Hook logs": one folder per owner, Bug 600). */
|
|
40
|
+
export type LogFileName =
|
|
41
|
+
| "agent-hooks/hooks"
|
|
42
|
+
| "agent-hooks/deny"
|
|
43
|
+
| "git-hooks/activity"
|
|
44
|
+
| "git-hooks/deny"
|
|
45
|
+
| "agents/tool-calls";
|
|
35
46
|
|
|
36
47
|
export interface LogEntryInput {
|
|
37
48
|
/** Second column of every row: the host alone (`claude`, `codex`, `cursor`, `kimi`, `opencode`; `git` for git-hook rows). */
|
|
@@ -78,6 +89,8 @@ export interface WriteLogOptions {
|
|
|
78
89
|
dir?: string;
|
|
79
90
|
/** cwd used to resolve the git root when `dir` is not given. */
|
|
80
91
|
cwd?: string;
|
|
92
|
+
/** How long to wait for the rotation lock before appending without rotating; defaults to 10 s. Tests inject a small value. */
|
|
93
|
+
lockTimeoutMs?: number;
|
|
81
94
|
/** Rotation threshold in bytes; defaults to 10 MiB. Tests inject a small value. */
|
|
82
95
|
maxBytes?: number;
|
|
83
96
|
/** Clock override for deterministic `ts` values in tests. */
|
|
@@ -147,22 +160,154 @@ function ensureDirSecure(dir: string): void {
|
|
|
147
160
|
ensureAdlGitignore(dir);
|
|
148
161
|
}
|
|
149
162
|
|
|
163
|
+
function isMissing(error: unknown): boolean {
|
|
164
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
165
|
+
}
|
|
166
|
+
|
|
150
167
|
function ensureFileSecure(filePath: string): void {
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
writeFileSync(filePath, "", { mode: 0o600, flag: "a" });
|
|
169
|
+
try {
|
|
170
|
+
tightenMode(filePath, 0o600);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (!isMissing(error)) throw error;
|
|
153
173
|
}
|
|
154
|
-
tightenMode(filePath, 0o600);
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
function renameIfPresent(src: string, dst: string): boolean {
|
|
177
|
+
try {
|
|
178
|
+
renameSync(src, dst);
|
|
179
|
+
return true;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (isMissing(error)) return false;
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const INSERT_ATTEMPTS = 5;
|
|
187
|
+
|
|
188
|
+
function linkIfFree(src: string, dst: string): boolean {
|
|
189
|
+
try {
|
|
190
|
+
linkSync(src, dst);
|
|
191
|
+
return true;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
194
|
+
if (code === "EEXIST" || code === "ENOENT") return false;
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function shiftBackups(filePath: string): void {
|
|
200
|
+
rmSync(`${filePath}.${MAX_BACKUPS}`, { force: true });
|
|
160
201
|
for (let n = MAX_BACKUPS - 1; n >= 1; n--) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
202
|
+
if (linkIfFree(`${filePath}.${n}`, `${filePath}.${n + 1}`)) rmSync(`${filePath}.${n}`, { force: true });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Moves `src` into slot `.1` without ever overwriting a backup; exported for tests, `afterShift` is their race hook. */
|
|
207
|
+
export function insertBackup(filePath: string, src: string, afterShift?: () => void): void {
|
|
208
|
+
for (let attempt = 0; attempt < INSERT_ATTEMPTS; attempt++) {
|
|
209
|
+
shiftBackups(filePath);
|
|
210
|
+
afterShift?.();
|
|
211
|
+
if (linkIfFree(src, `${filePath}.1`)) {
|
|
212
|
+
rmSync(src, { force: true });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!existsSync(src)) return;
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`log rotation could not find a free backup slot for ${src}; file kept`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function ownerAlive(pid: number): boolean {
|
|
221
|
+
try {
|
|
222
|
+
process.kill(pid, 0);
|
|
223
|
+
return true;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return (error as NodeJS.ErrnoException).code === "EPERM";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function orphanedAsides(filePath: string): string[] {
|
|
230
|
+
const prefix = `${basename(filePath)}.rotating-`;
|
|
231
|
+
const dir = dirname(filePath);
|
|
232
|
+
return readdirSync(dir)
|
|
233
|
+
.filter((name) => name.startsWith(prefix))
|
|
234
|
+
.map((name) => ({ path: join(dir, name), pid: Number(/^(\d+)-/.exec(name.slice(prefix.length))?.[1]) }))
|
|
235
|
+
.map((entry) => ({ ...entry, mtime: statSync(entry.path, { throwIfNoEntry: false })?.mtimeMs }))
|
|
236
|
+
.filter(({ pid, mtime }) => mtime !== undefined && (!Number.isInteger(pid) || !ownerAlive(pid) || Date.now() - mtime > LOCK_STALE_MS))
|
|
237
|
+
.sort((a, b) => (a.mtime as number) - (b.mtime as number))
|
|
238
|
+
.map(({ path }) => path);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function rotate(filePath: string): void {
|
|
242
|
+
for (const orphan of orphanedAsides(filePath)) insertBackup(filePath, orphan);
|
|
243
|
+
const aside = `${filePath}.rotating-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
244
|
+
if (!renameIfPresent(filePath, aside)) return;
|
|
245
|
+
insertBackup(filePath, aside);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** A lock older than this was left by a crashed writer and is broken. */
|
|
249
|
+
const LOCK_STALE_MS = 5_000;
|
|
250
|
+
const LOCK_TIMEOUT_MS = 10_000;
|
|
251
|
+
|
|
252
|
+
function sleepSync(ms: number): void {
|
|
253
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function readLockToken(lockPath: string): string | undefined {
|
|
257
|
+
try {
|
|
258
|
+
return readFileSync(lockPath, "utf8");
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (isMissing(error)) return undefined;
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function breakStaleLock(lockPath: string, observed: string): void {
|
|
266
|
+
if (readLockToken(lockPath) === observed) rmSync(lockPath, { force: true });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Runs `body` holding `<filePath>.lock` (O_EXCL create); size check and rotation are one critical section (README, "Hook logs"). Exported for tests. */
|
|
270
|
+
export function withRotationLock(filePath: string, lockTimeoutMs: number, body: () => void): void {
|
|
271
|
+
const lockPath = `${filePath}.lock`;
|
|
272
|
+
const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
273
|
+
const deadline = Date.now() + lockTimeoutMs;
|
|
274
|
+
for (;;) {
|
|
275
|
+
try {
|
|
276
|
+
writeFileSync(lockPath, token, { flag: "wx", mode: 0o600 });
|
|
277
|
+
break;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
280
|
+
}
|
|
281
|
+
const held = statSync(lockPath, { throwIfNoEntry: false });
|
|
282
|
+
if (held !== undefined && Date.now() - held.mtimeMs > LOCK_STALE_MS) {
|
|
283
|
+
const observed = readLockToken(lockPath);
|
|
284
|
+
if (observed !== undefined) breakStaleLock(lockPath, observed);
|
|
285
|
+
}
|
|
286
|
+
if (Date.now() > deadline) throw new Error(`log rotation lock ${lockPath} is held; refusing to rotate without it`);
|
|
287
|
+
sleepSync(2);
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
body();
|
|
291
|
+
} finally {
|
|
292
|
+
if (readLockToken(lockPath) === token) rmSync(lockPath, { force: true });
|
|
164
293
|
}
|
|
165
|
-
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Rotates `filePath` to `.1` … `.5` when `incomingBytes` more would pass `maxBytes`, size re-read inside the lock; exported for raw-text streams (README, "Hook logs"). */
|
|
297
|
+
export function rotateIfNeeded(
|
|
298
|
+
filePath: string,
|
|
299
|
+
incomingBytes: number,
|
|
300
|
+
maxBytes: number = DEFAULT_MAX_BYTES,
|
|
301
|
+
lockTimeoutMs: number = LOCK_TIMEOUT_MS,
|
|
302
|
+
): void {
|
|
303
|
+
const over = () => {
|
|
304
|
+
const size = statSync(filePath, { throwIfNoEntry: false })?.size ?? 0;
|
|
305
|
+
return size > 0 && size + incomingBytes > maxBytes;
|
|
306
|
+
};
|
|
307
|
+
if (!over()) return;
|
|
308
|
+
withRotationLock(filePath, lockTimeoutMs, () => {
|
|
309
|
+
if (over()) rotate(filePath);
|
|
310
|
+
});
|
|
166
311
|
}
|
|
167
312
|
|
|
168
313
|
const USERINFO_RE = /(https?:\/\/)[^/\s:@]+:[^/\s@]+@/gi;
|
|
@@ -223,22 +368,23 @@ function serialize(entry: LogEntryInput, ts: string): string {
|
|
|
223
368
|
return `${JSON.stringify(record)}\n`;
|
|
224
369
|
}
|
|
225
370
|
|
|
226
|
-
/** Appends one JSON-Lines entry to `<dir>/<file>.log
|
|
371
|
+
/** Appends one JSON-Lines entry to `<dir>/<file>.log` (`file` is `<owner>/<name>`), rotating first if needed. Never throws for a missing repo — callers resolve `dir` with `resolveLogDir`. */
|
|
227
372
|
export function writeLogEntry(file: LogFileName, entry: LogEntryInput, options: WriteLogOptions = {}): void {
|
|
228
373
|
const dir = options.dir ?? resolveLogDir(options.cwd).dir;
|
|
229
374
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
230
375
|
const ts = (options.now?.() ?? new Date()).toISOString();
|
|
231
376
|
const line = serialize(entry, ts);
|
|
232
377
|
|
|
233
|
-
ensureDirSecure(dir);
|
|
234
378
|
const filePath = join(dir, `${file}.log`);
|
|
379
|
+
ensureDirSecure(dirname(filePath));
|
|
235
380
|
ensureFileSecure(filePath);
|
|
236
381
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
382
|
+
try {
|
|
383
|
+
rotateIfNeeded(filePath, Buffer.byteLength(line, "utf8"), maxBytes, options.lockTimeoutMs);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
process.stderr.write(`adl log: rotation deferred, row kept: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
241
386
|
}
|
|
387
|
+
ensureFileSecure(filePath);
|
|
242
388
|
|
|
243
389
|
appendFileSync(filePath, line, { mode: 0o600 });
|
|
244
390
|
}
|
|
@@ -72,7 +72,8 @@ import type { HookHandler, HookInvocation, HookResult } from './hooks/contract.t
|
|
|
72
72
|
import { payloadCwd } from './hooks/contract.ts'
|
|
73
73
|
import { resolveHost, readHostFlag, type HostId } from './hosts/spec.ts'
|
|
74
74
|
import { resolveManagedRepo, realRootIO } from './gating/root.ts'
|
|
75
|
-
import {
|
|
75
|
+
import { setRunningAdlVersion } from './gating/running-adl.ts'
|
|
76
|
+
import { decodeConfig, describeConfigIssue, type AdlRepoConfig } from './gating/config.ts'
|
|
76
77
|
import { FAIL_CLOSED_EXIT_CODE } from './rules/engine.ts'
|
|
77
78
|
import { handlePreToolUse } from './pre-tool-use/handler.ts'
|
|
78
79
|
import { handleUserPromptSubmit } from './hooks/user-prompt-submit/handler.ts'
|
|
@@ -123,7 +124,13 @@ function writeResult(result: HookResult): number {
|
|
|
123
124
|
return result.exitCode
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
/**
|
|
128
|
+
* `adlVersion` is the embedding CLI's version (`adl hook` passes
|
|
129
|
+
* `apps/cli/package.json`'s); the standalone entry passes none, and config
|
|
130
|
+
* errors then say `version unknown` rather than guess.
|
|
131
|
+
*/
|
|
132
|
+
export async function run(argv: readonly string[], adlVersion?: string): Promise<number> {
|
|
133
|
+
setRunningAdlVersion(adlVersion)
|
|
127
134
|
if (argv.includes('--version')) {
|
|
128
135
|
process.stdout.write(`${readPackageVersion()}\n`)
|
|
129
136
|
return 0
|
|
@@ -185,7 +192,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
185
192
|
// `adl-config.toml` where `adl hooks deploy` has run — nowhere else. No
|
|
186
193
|
// git root, or no `adl-config.toml` above `cwd`: a strict no-op (this
|
|
187
194
|
// file's own header) — no rule evaluation, no identity check, no
|
|
188
|
-
// `hooks.log` write. There is no global identity baseline: a repo where
|
|
195
|
+
// `agent-hooks/hooks.log` write. There is no global identity baseline: a repo where
|
|
189
196
|
// `adl-config.toml` does not exist has no business running hooks at all.
|
|
190
197
|
// The same no-op applies when `[hooks] enabled = false` (below).
|
|
191
198
|
const managed = resolveManagedRepo(realRootIO, cwd)
|
|
@@ -199,7 +206,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
199
206
|
const decoded = decodeConfig(text)
|
|
200
207
|
if (!decoded.ok) {
|
|
201
208
|
process.stderr.write(
|
|
202
|
-
`adl hook:
|
|
209
|
+
`adl hook: ${describeConfigIssue(managed.configPath, decoded.issue)}\n`,
|
|
203
210
|
)
|
|
204
211
|
return 1
|
|
205
212
|
}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
import { readFileSync } from 'node:fs'
|
|
20
20
|
import { join } from 'node:path'
|
|
21
21
|
import type { ActivityNote, HookInvocation, HookResult } from '../hooks/contract.ts'
|
|
22
|
-
import { decodeConfig } from '../gating/config.ts'
|
|
22
|
+
import { decodeConfig, describeConfigIssue } from '../gating/config.ts'
|
|
23
|
+
import { runningAdl } from '../gating/running-adl.ts'
|
|
23
24
|
import { MARKER } from '../gating/root.ts'
|
|
24
25
|
import {
|
|
25
26
|
buildEngineContext,
|
|
@@ -221,7 +222,7 @@ export async function handlePreToolUse(inv: HookInvocation): Promise<HookResult>
|
|
|
221
222
|
const text = readFileSync(configPath, 'utf8')
|
|
222
223
|
const decoded = decodeConfig(text)
|
|
223
224
|
if (!decoded.ok) {
|
|
224
|
-
throw new Error(`adl hook:
|
|
225
|
+
throw new Error(`adl hook: ${describeConfigIssue(configPath, decoded.issue)}`)
|
|
225
226
|
}
|
|
226
227
|
const config = decoded.value
|
|
227
228
|
|
|
@@ -243,10 +244,10 @@ export async function handlePreToolUse(inv: HookInvocation): Promise<HookResult>
|
|
|
243
244
|
inv.root,
|
|
244
245
|
)
|
|
245
246
|
if (!ctx.ok) {
|
|
246
|
-
inv.record?.({ reason: 'ERROR rules config', message: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}` })
|
|
247
|
+
inv.record?.({ reason: 'ERROR rules config', message: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}` })
|
|
247
248
|
return {
|
|
248
249
|
stdout: '',
|
|
249
|
-
stderr: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}\n`,
|
|
250
|
+
stderr: `adl hook: ${configPath} ${ctx.issue.pointer}: ${ctx.issue.message}${runningAdl()}\n`,
|
|
250
251
|
exitCode: FAIL_CLOSED_EXIT_CODE,
|
|
251
252
|
}
|
|
252
253
|
}
|
|
@@ -55,7 +55,7 @@ export function toolNameEndingWithKnownSuffix(tool: string, knownSuffixes: Reado
|
|
|
55
55
|
*
|
|
56
56
|
* Prefix-less fallback (added 2026-09-28 with the one-agent-identity
|
|
57
57
|
* standard): OpenCode's and Kimi's own MCP tool-call naming has never actually been
|
|
58
|
-
* observed in production (`.adl/logs/hooks.log` shows only `mcp__`-prefixed
|
|
58
|
+
* observed in production (`.adl/logs/agent-hooks/hooks.log` shows only `mcp__`-prefixed
|
|
59
59
|
* calls, from Claude/Codex/Cursor) — they MAY call an azdo tool with no
|
|
60
60
|
* `mcp__` prefix at all, spelled `<server>_<tool>` / `<server>.<tool>` /
|
|
61
61
|
* `<server>:<tool>`. When `tool` carries no `mcp__` prefix, this falls back
|
|
@@ -501,7 +501,8 @@ function classifyGitArgv(argv: readonly Token[], binary: string): readonly Invoc
|
|
|
501
501
|
|
|
502
502
|
function walkGit(tokens: readonly Token[], index: number, parser: Parser): readonly Invocation[] {
|
|
503
503
|
const binary = tokenValue(tokens[index] as Token)
|
|
504
|
-
|
|
504
|
+
// Redirections are the shell's, not git's: write targets are classified from the raw tokens elsewhere (Bug 626).
|
|
505
|
+
const argv = withoutRedirections(tokens.slice(index + 1))
|
|
505
506
|
const scan = scanGitGlobals(argv, 0)
|
|
506
507
|
if (scan.terminal) return [{ kind: 'read-only', name: 'git' }]
|
|
507
508
|
if (scan.index >= argv.length) return uncertain('git command missing subcommand')
|
|
@@ -91,7 +91,7 @@ ready for the next research-page review.
|
|
|
91
91
|
|
|
92
92
|
| Metric | Source | Use |
|
|
93
93
|
| --- | --- | --- |
|
|
94
|
-
| Total decisions per host | `hooks.log` / `hooks.log.1` / `hooks-deny.log
|
|
94
|
+
| Total decisions per host | `agent-hooks/hooks.log*` / `agent-hooks/deny.log*` (plus the pre-Bug-600 top-level `hooks.log` / `hooks.log.1` / `hooks-deny.log`), deduped by `(ts, command, rule)` | Volume baseline |
|
|
95
95
|
| Denials per host, broken down by `rule` field | same | Where the hook fires |
|
|
96
96
|
| Search-tool denials (`tool.grep` / `tool.native-search` / `tool.find`) | same | The hook category this skill cares about |
|
|
97
97
|
| `ast-grep` invocations (Bash command starting with `ast-grep`) | same | Whether the recommended tool is being used |
|
|
@@ -117,4 +117,26 @@ classifying them by tool). For the retry analysis, follow the
|
|
|
117
117
|
— the hand-curated analysis this script would generate for today
|
|
118
118
|
- `packages/agent-hooks/src/rules/impl/tool-grep.ts`,
|
|
119
119
|
`tool-find.ts`, `tool-native-search.ts` — the deny messages the
|
|
120
|
-
audit is measuring
|
|
120
|
+
audit is measuring
|
|
121
|
+
|
|
122
|
+
## Script notes
|
|
123
|
+
|
|
124
|
+
Rationale moved out of `scripts/audit.sh` (REP-007: no multi-line comments).
|
|
125
|
+
|
|
126
|
+
- Usage: `scripts/audit.sh [--days N] [--out DIR]`; defaults `--days 7`, `--out docs/research`.
|
|
127
|
+
- Output: `<out>/research-YYYY-MM-DD-search-tool-usage-across-surfaces.md`, plus one new row appended
|
|
128
|
+
to `<out>/index.md`. Per-host, per-day tally of agent-hooks decisions, grouped by surface
|
|
129
|
+
(claude / codex / kimi / opencode), so the next weekly page in the series can compare against the
|
|
130
|
+
previous one. Reads only; never modifies the repo beyond those two files.
|
|
131
|
+
- Source data: `.adl/logs/agent-hooks/hooks.log[.1-.5]` and `deny.log[.1-.5]` (one folder per owner,
|
|
132
|
+
Bug 600, Tarun 2026-10-08); the pre-Bug-600 top-level `.adl/logs/hooks.log`, `hooks.log.1` and
|
|
133
|
+
`hooks-deny.log` (read-only history, never migrated, deduped against the new ones);
|
|
134
|
+
`.adl/logs/agents/tool-calls.log` (`adl run` lifecycle).
|
|
135
|
+
- Path discovery: the script lives in the agent-skills asset tree (5 levels deep in dev, 4 once
|
|
136
|
+
deployed to `.agents/skills/`), so it uses `git rev-parse --show-toplevel` to work in both layouts
|
|
137
|
+
and falls back to the dev layout if git is missing.
|
|
138
|
+
- Required tools: `jq`, `python3` and `rg`; each is checked up front and the script fails loud
|
|
139
|
+
(exit 1) naming the missing one. `rg` is a protected bootstrap tool on every managed machine.
|
|
140
|
+
- Cost: under 30 s on a typical 2-3 MB log set; no network.
|
|
141
|
+
- An existing page for today is left untouched (it may be hand-curated); `index.md` is still updated
|
|
142
|
+
so the catalog stays current. Existing index rows are never disturbed; one row is appended.
|
|
@@ -1,35 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# (packages/agent-skills/assets/agent-search-tool-audit/).
|
|
4
|
-
#
|
|
5
|
-
# Per-host, per-day tally of agent-hooks decisions in `.adl/logs/`,
|
|
6
|
-
# grouped by surface (claude / codex / kimi / opencode). Writes one
|
|
7
|
-
# dated markdown under `docs/research/` so the next weekly research
|
|
8
|
-
# page in the "agent search-tool behaviour" series can compare against
|
|
9
|
-
# the previous one. Reads only — never modifies the repo.
|
|
10
|
-
#
|
|
11
|
-
# Usage:
|
|
12
|
-
# scripts/audit.sh [--days N] [--out DIR]
|
|
13
|
-
#
|
|
14
|
-
# Defaults: --days 7, --out docs/research
|
|
15
|
-
#
|
|
16
|
-
# Output:
|
|
17
|
-
# <out>/research-YYYY-MM-DD-search-tool-usage-across-surfaces.md
|
|
18
|
-
# <out>/index.md (one new row appended for the new page)
|
|
19
|
-
#
|
|
20
|
-
# Source data:
|
|
21
|
-
# .adl/logs/hooks.log (current decision log)
|
|
22
|
-
# .adl/logs/hooks.log.1 (rotated decision log)
|
|
23
|
-
# .adl/logs/hooks-deny.log (dedicated deny stream)
|
|
24
|
-
# .adl/logs/agents/tool-calls.log (adl run lifecycle)
|
|
25
|
-
#
|
|
26
|
-
# Path discovery: this script lives in the agent-skills asset tree
|
|
27
|
-
# (5 levels deep in dev, 4 levels deep once deployed to
|
|
28
|
-
# `.agents/skills/`). Use `git rev-parse --show-toplevel` so it
|
|
29
|
-
# works in both layouts.
|
|
30
|
-
#
|
|
31
|
-
# Cost: <30 s on a typical 2-3 MB log set; no network, no new tools
|
|
32
|
-
# beyond jq + python3 + the shell.
|
|
2
|
+
# Usage and rationale: SKILL.md, "Script notes", in this skill's folder.
|
|
33
3
|
|
|
34
4
|
set -euo pipefail
|
|
35
5
|
|
|
@@ -40,7 +10,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
40
10
|
--days) DAYS="$2"; shift 2;;
|
|
41
11
|
--out) OUT="$2"; shift 2;;
|
|
42
12
|
-h|--help)
|
|
43
|
-
|
|
13
|
+
echo "usage: audit.sh [--days N] [--out DIR] (defaults: --days 7, --out docs/research)"
|
|
44
14
|
exit 0
|
|
45
15
|
;;
|
|
46
16
|
*)
|
|
@@ -52,10 +22,6 @@ done
|
|
|
52
22
|
|
|
53
23
|
cd "$(dirname "$0")/../.."
|
|
54
24
|
|
|
55
|
-
# `git rev-parse --show-toplevel` works in both dev (5 levels deep
|
|
56
|
-
# under packages/agent-skills/assets/agent-search-tool-audit/scripts)
|
|
57
|
-
# and deployed (4 levels deep under .agents/skills/). Fall back to
|
|
58
|
-
# the dev layout if git is missing.
|
|
59
25
|
if command -v git >/dev/null 2>&1; then
|
|
60
26
|
if toplevel=$(git rev-parse --show-toplevel 2>/dev/null) && [[ -n "$toplevel" ]]; then
|
|
61
27
|
cd "$toplevel"
|
|
@@ -63,9 +29,22 @@ if command -v git >/dev/null 2>&1; then
|
|
|
63
29
|
fi
|
|
64
30
|
|
|
65
31
|
command -v jq >/dev/null 2>&1 || { echo "jq is required and is not on PATH" >&2; exit 1; }
|
|
32
|
+
command -v rg >/dev/null 2>&1 || { echo "rg is required and is not on PATH" >&2; exit 1; }
|
|
66
33
|
command -v python3 >/dev/null 2>&1 || { echo "python3 is required and is not on PATH" >&2; exit 1; }
|
|
67
34
|
|
|
68
35
|
LOG_FILES=(
|
|
36
|
+
.adl/logs/agent-hooks/hooks.log
|
|
37
|
+
.adl/logs/agent-hooks/hooks.log.1
|
|
38
|
+
.adl/logs/agent-hooks/hooks.log.2
|
|
39
|
+
.adl/logs/agent-hooks/hooks.log.3
|
|
40
|
+
.adl/logs/agent-hooks/hooks.log.4
|
|
41
|
+
.adl/logs/agent-hooks/hooks.log.5
|
|
42
|
+
.adl/logs/agent-hooks/deny.log
|
|
43
|
+
.adl/logs/agent-hooks/deny.log.1
|
|
44
|
+
.adl/logs/agent-hooks/deny.log.2
|
|
45
|
+
.adl/logs/agent-hooks/deny.log.3
|
|
46
|
+
.adl/logs/agent-hooks/deny.log.4
|
|
47
|
+
.adl/logs/agent-hooks/deny.log.5
|
|
69
48
|
.adl/logs/hooks.log
|
|
70
49
|
.adl/logs/hooks.log.1
|
|
71
50
|
.adl/logs/hooks-deny.log
|
|
@@ -80,15 +59,12 @@ TODAY="$(date -u +%Y-%m-%d)"
|
|
|
80
59
|
RUN_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
81
60
|
OUT_FILE="${OUT}/research-${TODAY}-search-tool-usage-across-surfaces.md"
|
|
82
61
|
|
|
83
|
-
# If today's page already exists, leave it alone — it might be a hand-curated
|
|
84
|
-
# analysis from earlier in the day. Append / update `index.md` either way so
|
|
85
|
-
# the catalog stays current.
|
|
86
62
|
if [[ -f "$OUT_FILE" ]]; then
|
|
87
63
|
echo "${OUT_FILE} already exists; leaving it untouched. Re-run after deleting it (or pass --out to a different dir)."
|
|
88
64
|
# Still touch index.md so the catalog row stays current (idempotent).
|
|
89
65
|
INDEX="${OUT}/index.md"
|
|
90
66
|
ROW="| [Search-tool usage across surfaces — ${TODAY}](research-${TODAY}-search-tool-usage-across-surfaces.md) | Auto-generated weekly audit of per-host search-tool denials and recommended-tool invocations |"
|
|
91
|
-
if [[ -f "$INDEX" ]] && !
|
|
67
|
+
if [[ -f "$INDEX" ]] && ! rg -q "research-${TODAY}-search-tool-usage-across-surfaces.md" "$INDEX"; then
|
|
92
68
|
python3 - "$INDEX" "$ROW" <<'PYEOF' >/dev/null
|
|
93
69
|
import sys
|
|
94
70
|
path, row = sys.argv[1], sys.argv[2]
|
|
@@ -177,13 +153,13 @@ lines.append("type: Research")
|
|
|
177
153
|
lines.append("created: {0}".format(today))
|
|
178
154
|
lines.append("status: stable")
|
|
179
155
|
lines.append("last_update: {0}".format(run_ts))
|
|
180
|
-
lines.append("sources: .adl/logs/hooks.log, .adl/logs/hooks.log.1, .adl/logs/hooks-deny.log")
|
|
156
|
+
lines.append("sources: .adl/logs/agent-hooks/hooks.log*, .adl/logs/agent-hooks/deny.log*, .adl/logs/hooks.log, .adl/logs/hooks.log.1, .adl/logs/hooks-deny.log")
|
|
181
157
|
lines.append("related: ./index.md, ./research-2026-10-02-search-tool-usage-across-surfaces.md")
|
|
182
158
|
lines.append("---")
|
|
183
159
|
lines.append("")
|
|
184
160
|
lines.append("# Agent interface search-tool usage across surfaces ({0})".format(today))
|
|
185
161
|
lines.append("")
|
|
186
|
-
lines.append("Auto-generated weekly audit. Window: trailing {0} UTC days ending {1}. Source: `.adl/logs/hooks
|
|
162
|
+
lines.append("Auto-generated weekly audit. Window: trailing {0} UTC days ending {1}. Source: `.adl/logs/agent-hooks/` (plus the pre-Bug-600 top-level `hooks.log*` and `hooks-deny.log`). Re-run with `just research/weekly-search-audit`.".format(days, today))
|
|
187
163
|
lines.append("")
|
|
188
164
|
lines.append("## Headline per host")
|
|
189
165
|
lines.append("")
|
|
@@ -238,8 +214,6 @@ lines.append("")
|
|
|
238
214
|
sys.stdout.write("\n".join(lines))
|
|
239
215
|
PYEOF
|
|
240
216
|
|
|
241
|
-
# Append / update index.md with the new page row. We don't disturb
|
|
242
|
-
# other rows; we add one to the end of the table.
|
|
243
217
|
INDEX="${OUT}/index.md"
|
|
244
218
|
ROW="| [Search-tool usage across surfaces — ${TODAY}](research-${TODAY}-search-tool-usage-across-surfaces.md) | Auto-generated weekly audit of per-host search-tool denials and recommended-tool invocations |"
|
|
245
219
|
|
|
@@ -247,7 +221,7 @@ if [[ ! -f "$INDEX" ]]; then
|
|
|
247
221
|
echo "index.md not present at ${INDEX}; not updating" >&2
|
|
248
222
|
exit 0
|
|
249
223
|
fi
|
|
250
|
-
if
|
|
224
|
+
if rg -q "research-${TODAY}-search-tool-usage-across-surfaces.md" "$INDEX"; then
|
|
251
225
|
echo "index.md already has today's page; not appending"
|
|
252
226
|
else
|
|
253
227
|
# Insert before the closing fence (if any) or just append before EOF.
|
|
@@ -98,3 +98,12 @@ Required before declaring done:
|
|
|
98
98
|
- [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns
|
|
99
99
|
|
|
100
100
|
**Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the `/improve-codebase-architecture` skill with the specifics. Make the recommendation **after** the fix is in, not before — you have more information now than when you started.
|
|
101
|
+
|
|
102
|
+
## HITL loop script
|
|
103
|
+
|
|
104
|
+
`scripts/hitl-loop.template.sh` is the human-in-the-loop reproduction loop (usage notes moved out of the script so the deployed copy has no multi-line comment run).
|
|
105
|
+
|
|
106
|
+
- Copy the file, edit the steps in it, and run it. The agent runs the script; the user follows prompts in their terminal.
|
|
107
|
+
- Usage: `bash hitl-loop.template.sh`
|
|
108
|
+
- Two helpers: `step "<instruction>"` shows the instruction and waits for Enter; `capture VAR "<question>"` shows the question and reads the response into `VAR`.
|
|
109
|
+
- At the end, captured values are printed as `KEY=VALUE` for the agent to parse.
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Human-in-the-loop reproduction loop.
|
|
3
|
-
# Copy this file, edit the steps below, and run it.
|
|
4
|
-
# The agent runs the script; the user follows prompts in their terminal.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# bash hitl-loop.template.sh
|
|
8
|
-
#
|
|
9
|
-
# Two helpers:
|
|
10
|
-
# step "<instruction>" → show instruction, wait for Enter
|
|
11
|
-
# capture VAR "<question>" → show question, read response into VAR
|
|
12
|
-
#
|
|
13
|
-
# At the end, captured values are printed as KEY=VALUE for the agent to parse.
|
|
2
|
+
# Human-in-the-loop reproduction loop; usage in SKILL.md "HITL loop script".
|
|
14
3
|
|
|
15
4
|
set -euo pipefail
|
|
16
5
|
|
|
@@ -42,7 +42,12 @@ const isAllowedType = (type: string | undefined): type is AllowedType => (ALLOWE
|
|
|
42
42
|
function assertAllowedType(type: string | undefined): asserts type is AllowedType {
|
|
43
43
|
if (!isAllowedType(type)) throw new Error(`type must be one of ${ALLOWED_TYPES.join(', ')}; got ${type ?? 'undefined'}`);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
interface WorkItemView {
|
|
46
|
+
id: number; title: string; type: string; state: string; assignedTo: string | undefined; description: string | undefined; url: string | undefined;
|
|
47
|
+
links: { parent: number | null; children: number[]; predecessors: number[]; successors: number[]; related: number[] };
|
|
48
|
+
effort: number | null; failureReason: string | null; fields?: Record<string, unknown>; relations?: unknown[];
|
|
49
|
+
}
|
|
50
|
+
function formatItem(wi: any, raw: boolean): WorkItemView {
|
|
46
51
|
const fields = wi.fields ?? {};
|
|
47
52
|
const relationId = (url: string) => Number(/(\d+)\/?$/.exec(url)?.[1]);
|
|
48
53
|
const links = { parent: null as number | null, children: [] as number[], predecessors: [] as number[], successors: [] as number[], related: [] as number[] };
|
|
@@ -53,7 +58,8 @@ function formatItem(wi: any, raw: boolean): Record<string, unknown> {
|
|
|
53
58
|
if (relation.rel === 'System.LinkTypes.Dependency-Forward') links.successors.push(relationId(relation.url));
|
|
54
59
|
if (relation.rel === 'System.LinkTypes.Related') links.related.push(relationId(relation.url));
|
|
55
60
|
}
|
|
56
|
-
const
|
|
61
|
+
const effort = fields['Microsoft.VSTS.Scheduling.Effort']; const failureReason = fields['Custom.FailureReason'];
|
|
62
|
+
const out: WorkItemView = { id: wi.id, title: fields['System.Title'], type: fields['System.WorkItemType'], state: fields['System.State'], assignedTo: fields['System.AssignedTo']?.displayName, description: fields['System.Description'], url: wi._links?.html?.href, links, effort: typeof effort === 'number' ? effort : null, failureReason: typeof failureReason === 'string' && failureReason ? failureReason : null };
|
|
57
63
|
if (raw) { out.fields = fields; out.relations = wi.relations ?? []; }
|
|
58
64
|
return out;
|
|
59
65
|
}
|
|
@@ -124,7 +124,7 @@ export function createServer(): Server {
|
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
name: 'workitem_list_or_get',
|
|
127
|
-
description: 'Get one or many tracked work items, or query the scoped backlog.',
|
|
127
|
+
description: 'Get one or many tracked work items, or query the scoped backlog. Each item carries effort and failureReason (both null when unset).',
|
|
128
128
|
inputSchema: {
|
|
129
129
|
type: 'object',
|
|
130
130
|
properties: {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"remove_worktrees": true
|
|
34
34
|
},
|
|
35
35
|
"brew_upgrade_exclude": [],
|
|
36
|
-
"protected_tools": ["git", "jq"],
|
|
36
|
+
"protected_tools": ["git", "jq", "ripgrep"],
|
|
37
37
|
"git_global_config": {
|
|
38
38
|
"credential.helper": ["", "{git-credential-manager}"],
|
|
39
39
|
"credential.https://dev.azure.com.useHttpPath": "true",
|
|
@@ -159,6 +159,7 @@
|
|
|
159
159
|
"node": {"manager": "brew", "package": "node", "executable": "node"},
|
|
160
160
|
"ripgrep": {"manager": "brew", "package": "ripgrep", "executable": "rg"},
|
|
161
161
|
"search": {"manager": "shim", "package": "enact-docs-search", "executable": "/usr/local/bin/search"},
|
|
162
|
+
"shfmt": {"manager": "brew", "package": "shfmt", "executable": "shfmt"},
|
|
162
163
|
"tailscale": {"manager": "apt", "package": "tailscale", "executable": "/usr/bin/tailscale"},
|
|
163
164
|
"uv": {"manager": "brew", "package": "uv", "executable": "uv"},
|
|
164
165
|
"yq": {"manager": "brew", "package": "yq", "executable": "yq"}
|
|
@@ -328,8 +328,8 @@ export function maintainRepositories(options: MaintainOptions): MaintainResult {
|
|
|
328
328
|
if (maxAgeDays !== undefined) pruneStale(repo, name, current, maxAgeDays)
|
|
329
329
|
|
|
330
330
|
// 5. the per-repo enrolment.
|
|
331
|
-
const argv = [...options.install.leadingArgs, 'install', '--repo', repo]
|
|
332
|
-
const shown = `adl install --repo ${repo}`
|
|
331
|
+
const argv = [...options.install.leadingArgs, 'install', '--repo', repo, '--rewire']
|
|
332
|
+
const shown = `adl install --repo ${repo} --rewire`
|
|
333
333
|
if (dry) {
|
|
334
334
|
info(`${name}: [dry] would run ${shown}`)
|
|
335
335
|
return
|
|
@@ -341,6 +341,9 @@ export function maintainRepositories(options: MaintainOptions): MaintainResult {
|
|
|
341
341
|
fail(`${name}: ${shown} exited ${result.status}: ${(result.stderr ?? '').trim()}`, repo)
|
|
342
342
|
} else {
|
|
343
343
|
info(`${name}: ${shown}: ok`)
|
|
344
|
+
for (const line of `${result.stdout}${result.stderr}`.split('\n')) {
|
|
345
|
+
if (/WARNING|FAIL|COULD-NOT-RUN/.test(line)) warn(`${name}: ${line.trim()}`)
|
|
346
|
+
}
|
|
344
347
|
}
|
|
345
348
|
}
|
|
346
349
|
|