@amsterdamdatalabs/adl-agent-control-darwin-arm64 0.1.4
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 +4 -0
- package/bin/cursor-settings +14 -0
- package/bin/default-agent-file +14 -0
- package/bin/git +670 -0
- package/bin/git-ado +425 -0
- package/bin/global-agents +14 -0
- package/bin/global-surface-sweep +14 -0
- package/bin/health-check +38 -0
- package/bin/kimi-hooks +14 -0
- package/bin/lean-ctx-observe-strip +361 -0
- package/bin/lib/bun-compile-build.sh +34 -0
- package/bin/lib/lean-ctx-observe-hooks.sh +634 -0
- package/bin/managed-settings +14 -0
- package/bin/opencode-hooks +14 -0
- package/bin/reconcile +2648 -0
- package/bin/scratch-reap +14 -0
- package/bin/shell-path-policy +64 -0
- package/bin/surface-policy +14 -0
- package/bin/sync-and-reconcile +215 -0
- package/bin/update-azure-agent-pool +29 -0
- package/bin/validate-manifest +171 -0
- package/bin/vscode-settings +14 -0
- package/package.json +44 -0
- package/packages/agent-hooks/README.md +411 -0
- package/packages/agent-hooks/assets/hooks.json +97 -0
- package/packages/agent-hooks/assets/opencode-plugin.template.ts +224 -0
- package/packages/agent-hooks/bunfig.toml +27 -0
- package/packages/agent-hooks/deploy-profile.json +53 -0
- package/packages/agent-hooks/deploy-profile.schema.json +120 -0
- package/packages/agent-hooks/package.json +23 -0
- package/packages/agent-hooks/src/cli/args.ts +99 -0
- package/packages/agent-hooks/src/cli/doctor.ts +241 -0
- package/packages/agent-hooks/src/cli/rules.ts +133 -0
- package/packages/agent-hooks/src/deploy/deploy.ts +181 -0
- package/packages/agent-hooks/src/deploy/doctor.ts +158 -0
- package/packages/agent-hooks/src/deploy/index.ts +17 -0
- package/packages/agent-hooks/src/deploy/json-merge.ts +115 -0
- package/packages/agent-hooks/src/deploy/legacy-lock.ts +213 -0
- package/packages/agent-hooks/src/deploy/list.ts +117 -0
- package/packages/agent-hooks/src/deploy/lock.ts +65 -0
- package/packages/agent-hooks/src/deploy/opencode-template.ts +74 -0
- package/packages/agent-hooks/src/deploy/profile.ts +117 -0
- package/packages/agent-hooks/src/deploy/render.ts +162 -0
- package/packages/agent-hooks/src/deploy/source.ts +84 -0
- package/packages/agent-hooks/src/gating/config.ts +514 -0
- package/packages/agent-hooks/src/gating/root.ts +117 -0
- package/packages/agent-hooks/src/hooks/briefing/handler.ts +22 -0
- package/packages/agent-hooks/src/hooks/briefing/index.ts +320 -0
- package/packages/agent-hooks/src/hooks/contract.ts +112 -0
- package/packages/agent-hooks/src/hooks/crg/handler.ts +22 -0
- package/packages/agent-hooks/src/hooks/crg/index.ts +209 -0
- package/packages/agent-hooks/src/hooks/lean-ctx/handler.ts +22 -0
- package/packages/agent-hooks/src/hooks/lean-ctx/index.ts +173 -0
- package/packages/agent-hooks/src/hooks/lean-ctx/mapping.ts +121 -0
- package/packages/agent-hooks/src/hooks/lean-ctx/scope-rewrite.ts +207 -0
- package/packages/agent-hooks/src/hooks/log.ts +233 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/config.ts +58 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/constants.ts +149 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/handler.ts +72 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/index.ts +203 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/match.ts +212 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/skill-roots.ts +73 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/skills.ts +98 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/tokenizer.ts +115 -0
- package/packages/agent-hooks/src/hooks/user-prompt-submit/vector.ts +174 -0
- package/packages/agent-hooks/src/hosts/impl/claude.ts +47 -0
- package/packages/agent-hooks/src/hosts/impl/codex.ts +47 -0
- package/packages/agent-hooks/src/hosts/impl/cursor.ts +40 -0
- package/packages/agent-hooks/src/hosts/impl/kimi.ts +47 -0
- package/packages/agent-hooks/src/hosts/impl/opencode.ts +54 -0
- package/packages/agent-hooks/src/hosts/registry.ts +29 -0
- package/packages/agent-hooks/src/hosts/spec.ts +113 -0
- package/packages/agent-hooks/src/lean-ctx/env.ts +106 -0
- package/packages/agent-hooks/src/lean-ctx/mcp.ts +140 -0
- package/packages/agent-hooks/src/lib/spawn-with-timeout.ts +120 -0
- package/packages/agent-hooks/src/log/writer.ts +230 -0
- package/packages/agent-hooks/src/main.ts +269 -0
- package/packages/agent-hooks/src/pre-tool-use/handler.ts +549 -0
- package/packages/agent-hooks/src/rules/engine.ts +293 -0
- package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +51 -0
- package/packages/agent-hooks/src/rules/impl/adl-git-credential-bypass.ts +320 -0
- package/packages/agent-hooks/src/rules/impl/adl-git-identity.ts +249 -0
- package/packages/agent-hooks/src/rules/impl/adl-git-push-policy.ts +258 -0
- package/packages/agent-hooks/src/rules/impl/adl-human-azdo-bypass.ts +731 -0
- package/packages/agent-hooks/src/rules/impl/adl-identity.ts +128 -0
- package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +179 -0
- package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +65 -0
- package/packages/agent-hooks/src/rules/impl/adl-pr-review-role.ts +156 -0
- package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +121 -0
- package/packages/agent-hooks/src/rules/impl/adl-session-required.ts +403 -0
- package/packages/agent-hooks/src/rules/impl/adl-token-minting-role.ts +92 -0
- package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +74 -0
- package/packages/agent-hooks/src/rules/impl/advisory-coverage.ts +43 -0
- package/packages/agent-hooks/src/rules/impl/advisory-finder.ts +62 -0
- package/packages/agent-hooks/src/rules/impl/agent-secrets-stay-unread.ts +173 -0
- package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +508 -0
- package/packages/agent-hooks/src/rules/impl/fs-canonical-checkout.ts +86 -0
- package/packages/agent-hooks/src/rules/impl/fs-git-internals.ts +75 -0
- package/packages/agent-hooks/src/rules/impl/fs-mv.ts +66 -0
- package/packages/agent-hooks/src/rules/impl/fs-rm.ts +83 -0
- package/packages/agent-hooks/src/rules/impl/fs-trash.ts +140 -0
- package/packages/agent-hooks/src/rules/impl/git-core.ts +101 -0
- package/packages/agent-hooks/src/rules/impl/git-helpers.ts +408 -0
- package/packages/agent-hooks/src/rules/impl/git-history.ts +77 -0
- package/packages/agent-hooks/src/rules/impl/git-paths.ts +139 -0
- package/packages/agent-hooks/src/rules/impl/git-plumbing.ts +167 -0
- package/packages/agent-hooks/src/rules/impl/git-protected-tree.ts +132 -0
- package/packages/agent-hooks/src/rules/impl/git-transitions.ts +292 -0
- package/packages/agent-hooks/src/rules/impl/git-verify.ts +95 -0
- package/packages/agent-hooks/src/rules/impl/git-worktree.ts +265 -0
- package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +76 -0
- package/packages/agent-hooks/src/rules/impl/messages.ts +128 -0
- package/packages/agent-hooks/src/rules/impl/parse-uncertain.ts +143 -0
- package/packages/agent-hooks/src/rules/impl/pr-completion.ts +35 -0
- package/packages/agent-hooks/src/rules/impl/shell-remote-script-exec.ts +63 -0
- package/packages/agent-hooks/src/rules/impl/tool-azure-devops.ts +283 -0
- package/packages/agent-hooks/src/rules/impl/tool-bun-test.ts +81 -0
- package/packages/agent-hooks/src/rules/impl/tool-cargo.ts +66 -0
- package/packages/agent-hooks/src/rules/impl/tool-chmod-hook.ts +42 -0
- package/packages/agent-hooks/src/rules/impl/tool-find.ts +82 -0
- package/packages/agent-hooks/src/rules/impl/tool-frontend.ts +83 -0
- package/packages/agent-hooks/src/rules/impl/tool-grep.ts +93 -0
- package/packages/agent-hooks/src/rules/impl/tool-lean-ctx-global.ts +594 -0
- package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +77 -0
- package/packages/agent-hooks/src/rules/ports.ts +178 -0
- package/packages/agent-hooks/src/rules/registry.ts +233 -0
- package/packages/agent-hooks/src/rules/shell/git-identity.ts +1415 -0
- package/packages/agent-hooks/src/rules/shell/git-internals.ts +186 -0
- package/packages/agent-hooks/src/rules/shell/invocation.ts +1083 -0
- package/packages/agent-hooks/src/rules/shell/piped-script.ts +201 -0
- package/packages/agent-hooks/src/rules/shell/prefixes.ts +214 -0
- package/packages/agent-hooks/src/rules/shell/protected-carrier-writes.ts +108 -0
- package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +149 -0
- package/packages/agent-hooks/src/rules/shell/split.ts +179 -0
- package/packages/agent-hooks/src/rules/shell/substitution.ts +394 -0
- package/packages/agent-hooks/src/rules/shell/token.ts +180 -0
- package/packages/agent-hooks/src/rules/shell/xargs.ts +81 -0
- package/packages/agent-hooks/src/rules/spec.ts +152 -0
- package/packages/agent-hooks/src/surface-policy.ts +37 -0
- package/packages/agent-hooks/tsconfig.json +23 -0
- package/packages/agent-settings/assets/GLOBAL-AGENTS.md +107 -0
- package/packages/agent-settings/assets/agents/architect.toml +30 -0
- package/packages/agent-settings/assets/agents/code-reviewer.toml +27 -0
- package/packages/agent-settings/assets/agents/critic.toml +35 -0
- package/packages/agent-settings/assets/agents/executor.toml +28 -0
- package/packages/agent-settings/assets/agents/explore.toml +25 -0
- package/packages/agent-settings/assets/agents/planner.toml +29 -0
- package/packages/agent-settings/assets/lean-ctx/config.toml +39 -0
- package/packages/agent-settings/assets/lean-ctx/gitignore +6 -0
- package/packages/agent-settings/assets/lean-ctx/layout.toml +5 -0
- package/packages/agent-settings/assets/mcp.json +20 -0
- package/packages/agent-settings/managed/README.md +331 -0
- package/packages/agent-settings/managed/claude-managed-settings.json +150 -0
- package/packages/agent-settings/managed/codex-requirements.toml +105 -0
- package/packages/agent-settings/protected.json +470 -0
- package/packages/agent-settings/src/deploy/agent-render.ts +94 -0
- package/packages/agent-settings/src/deploy/agent-toml.ts +76 -0
- package/packages/agent-settings/src/deploy/agents-deploy.ts +129 -0
- package/packages/agent-settings/src/deploy/doctor.ts +201 -0
- package/packages/agent-settings/src/deploy/index.ts +33 -0
- package/packages/agent-settings/src/deploy/json-pretty.ts +66 -0
- package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +138 -0
- package/packages/agent-settings/src/deploy/legacy-lock.ts +286 -0
- package/packages/agent-settings/src/deploy/list.ts +135 -0
- package/packages/agent-settings/src/deploy/lock.ts +112 -0
- package/packages/agent-settings/src/deploy/mcp-deploy.ts +175 -0
- package/packages/agent-settings/src/deploy/mcp-inspect.ts +56 -0
- package/packages/agent-settings/src/deploy/mcp-merge.ts +164 -0
- package/packages/agent-settings/src/deploy/mcp-render.ts +80 -0
- package/packages/agent-settings/src/deploy/mcp-source.ts +43 -0
- package/packages/agent-settings/src/deploy/profile.ts +296 -0
- package/packages/agent-settings/src/deploy/settings.ts +65 -0
- package/packages/agent-settings/src/global-agents.ts +230 -0
- package/packages/agent-settings/src/global-surface-sweep.ts +2179 -0
- package/packages/agent-settings/src/kimi-hooks.ts +455 -0
- package/packages/agent-settings/src/opencode-hooks.ts +76 -0
- package/packages/agent-settings/src/surface-policy.ts +441 -0
- package/packages/agent-settings/surface-policy.json +20 -0
- package/packages/agent-skills/assets/code-review/SKILL.md +100 -0
- package/packages/agent-skills/assets/codebase-design/SKILL.md +98 -0
- package/packages/agent-skills/assets/codebase-design/references/DEEPENING.md +37 -0
- package/packages/agent-skills/assets/codebase-design/references/DESIGN-IT-TWICE.md +44 -0
- package/packages/agent-skills/assets/codebase-design/references/TESTABILITY.md +29 -0
- package/packages/agent-skills/assets/debug-issue/SKILL.md +40 -0
- package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +100 -0
- package/packages/agent-skills/assets/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +34 -0
- package/packages/agent-skills/assets/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/packages/agent-skills/assets/domain-modeling/SKILL.md +86 -0
- package/packages/agent-skills/assets/domain-modeling/references/ADR-FORMAT.md +47 -0
- package/packages/agent-skills/assets/domain-modeling/references/CONTEXT-FORMAT.md +60 -0
- package/packages/agent-skills/assets/explore-codebase/SKILL.md +41 -0
- package/packages/agent-skills/assets/handoff/SKILL.md +28 -0
- package/packages/agent-skills/assets/implement/SKILL.md +28 -0
- package/packages/agent-skills/assets/refactor-safely/SKILL.md +44 -0
- package/packages/agent-skills/assets/review-changes/SKILL.md +45 -0
- package/packages/agent-skills/assets/tdd/SKILL.md +49 -0
- package/packages/agent-skills/assets/tdd/references/mocking.md +59 -0
- package/packages/agent-skills/assets/tdd/references/tests.md +77 -0
- package/packages/agent-skills/assets/testing/SKILL.md +54 -0
- package/packages/agent-skills/assets/to-feature/SKILL.md +91 -0
- package/packages/agent-skills/assets/to-feature/references/FEATURE-SPEC-TEMPLATE.md +88 -0
- package/packages/agent-skills/assets/to-workitems/SKILL.md +90 -0
- package/packages/agent-skills/assets/to-workitems/references/AZURE-DEVOPS-HIERARCHY.md +118 -0
- package/packages/agent-skills/assets/to-workitems/references/TICKET-TEMPLATES.md +70 -0
- package/packages/agent-skills/assets/wayfinder/SKILL.md +100 -0
- package/packages/agent-skills/assets/wayfinder/references/TEMPLATES.md +35 -0
- package/packages/agent-skills/assets/wayfinder/references/TICKET-TYPES.md +8 -0
- package/packages/agent-skills/deploy-profile.json +9 -0
- package/packages/agent-skills/deploy-profile.schema.json +45 -0
- package/packages/agent-skills/dist/deploy.d.ts +27 -0
- package/packages/agent-skills/dist/deploy.d.ts.map +1 -0
- package/packages/agent-skills/dist/deploy.js +186 -0
- package/packages/agent-skills/dist/deploy.js.map +1 -0
- package/packages/agent-skills/dist/doctor.d.ts +24 -0
- package/packages/agent-skills/dist/doctor.d.ts.map +1 -0
- package/packages/agent-skills/dist/doctor.js +141 -0
- package/packages/agent-skills/dist/doctor.js.map +1 -0
- package/packages/agent-skills/dist/fs-utils.d.ts +8 -0
- package/packages/agent-skills/dist/fs-utils.d.ts.map +1 -0
- package/packages/agent-skills/dist/fs-utils.js +40 -0
- package/packages/agent-skills/dist/fs-utils.js.map +1 -0
- package/packages/agent-skills/dist/index.d.ts +8 -0
- package/packages/agent-skills/dist/index.d.ts.map +1 -0
- package/packages/agent-skills/dist/index.js +8 -0
- package/packages/agent-skills/dist/index.js.map +1 -0
- package/packages/agent-skills/dist/legacy-lock.d.ts +44 -0
- package/packages/agent-skills/dist/legacy-lock.d.ts.map +1 -0
- package/packages/agent-skills/dist/legacy-lock.js +121 -0
- package/packages/agent-skills/dist/legacy-lock.js.map +1 -0
- package/packages/agent-skills/dist/list.d.ts +23 -0
- package/packages/agent-skills/dist/list.d.ts.map +1 -0
- package/packages/agent-skills/dist/list.js +75 -0
- package/packages/agent-skills/dist/list.js.map +1 -0
- package/packages/agent-skills/dist/manifest.d.ts +15 -0
- package/packages/agent-skills/dist/manifest.d.ts.map +1 -0
- package/packages/agent-skills/dist/manifest.js +57 -0
- package/packages/agent-skills/dist/manifest.js.map +1 -0
- package/packages/agent-skills/dist/profile.d.ts +41 -0
- package/packages/agent-skills/dist/profile.d.ts.map +1 -0
- package/packages/agent-skills/dist/profile.js +86 -0
- package/packages/agent-skills/dist/profile.js.map +1 -0
- package/packages/agent-skills/dist/skill-config.d.ts +24 -0
- package/packages/agent-skills/dist/skill-config.d.ts.map +1 -0
- package/packages/agent-skills/dist/skill-config.js +68 -0
- package/packages/agent-skills/dist/skill-config.js.map +1 -0
- package/packages/agent-skills/dist/skill-meta.d.ts +17 -0
- package/packages/agent-skills/dist/skill-meta.d.ts.map +1 -0
- package/packages/agent-skills/dist/skill-meta.js +67 -0
- package/packages/agent-skills/dist/skill-meta.js.map +1 -0
- package/packages/agent-skills/package.json +31 -0
- package/packages/agent-skills/src/deploy.ts +222 -0
- package/packages/agent-skills/src/doctor.ts +178 -0
- package/packages/agent-skills/src/fs-utils.ts +40 -0
- package/packages/agent-skills/src/index.ts +29 -0
- package/packages/agent-skills/src/legacy-lock.ts +152 -0
- package/packages/agent-skills/src/list.ts +94 -0
- package/packages/agent-skills/src/manifest.ts +66 -0
- package/packages/agent-skills/src/profile.ts +128 -0
- package/packages/agent-skills/src/skill-config.ts +75 -0
- package/packages/agent-skills/src/skill-meta.ts +78 -0
- package/packages/agent-skills/tsconfig.build.json +13 -0
- package/packages/agent-skills/tsconfig.json +23 -0
- package/packages/azdo/README.md +111 -0
- package/packages/azdo/package.json +21 -0
- package/packages/azdo/src/azdo-pl.ts +275 -0
- package/packages/azdo/src/azdo-pr-review.ts +160 -0
- package/packages/azdo/src/azdo-pr.ts +289 -0
- package/packages/azdo/src/azdo-repo-doctor.ts +839 -0
- package/packages/azdo/src/azdo-wit.ts +145 -0
- package/packages/azdo/src/cli.ts +251 -0
- package/packages/azdo/src/lib/agent-identity.ts +198 -0
- package/packages/azdo/src/lib/agent-token.ts +167 -0
- package/packages/azdo/src/lib/client.ts +259 -0
- package/packages/azdo/src/lib/graph-identity.ts +153 -0
- package/packages/azdo/src/lib/validate-pr-metadata.ts +57 -0
- package/packages/azdo/src/mcp-server.ts +209 -0
- package/packages/azdo/src/tool-dispatch.ts +204 -0
- package/packages/azdo/tsconfig.json +13 -0
- package/packages/fleet/jobs/git-maintain/src/main.ts +588 -0
- package/packages/fleet/manifest.json +207 -0
- package/packages/fleet/manifest.schema.json +283 -0
- package/packages/fleet/src/default-agent-file.ts +144 -0
- package/packages/fleet/src/install-adl-package.ts +178 -0
- package/packages/fleet/src/install-embedding-model.ts +412 -0
- package/packages/fleet/src/launch-agents.ts +649 -0
- package/packages/fleet/src/managed-settings.ts +278 -0
- package/packages/fleet/src/scratch-reap.ts +601 -0
- package/packages/fleet/src/vscode-settings.ts +705 -0
- package/packages/fleet/systemd/adl-azure-agent-update.service +13 -0
- package/packages/fleet/systemd/adl-azure-agent-update.timer +10 -0
- package/packages/repo-controls/AGENTS.md +96 -0
- package/packages/repo-controls/CLAUDE.md +1 -0
- package/packages/repo-controls/LICENSE +20 -0
- package/packages/repo-controls/README.md +493 -0
- package/packages/repo-controls/assets/enable-integration-auto-complete.mjs +79 -0
- package/packages/repo-controls/assets/promote.ts +371 -0
- package/packages/repo-controls/assets/version-bump.sh +189 -0
- package/packages/repo-controls/azure-pipelines.yml +274 -0
- package/packages/repo-controls/bin/adl-repo-controls.ts +10 -0
- package/packages/repo-controls/bunfig.toml +58 -0
- package/packages/repo-controls/package/adl-compliance/AGENTS.md +163 -0
- package/packages/repo-controls/package/adl-compliance/CLAUDE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/conftest.py +78 -0
- package/packages/repo-controls/package/adl-compliance/pyproject.toml +31 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/__init__.py +50 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/__init__.py +98 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/check.py +210 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-bad-status/docs/architecture/overview.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-bad-status/docs/decisions/adr-901-proposed.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-broken-backlink/docs/decisions/adr-902-dangling.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-clean/docs/architecture/overview.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-clean/docs/decisions/adr-900-example.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-frontmatter-mismatch/.plans/b_committed/26-08-08-lifecycle-fixture.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-frontmatter-mismatch/.specs/demo/lifecycle.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-frontmatter-mismatch/docs/decisions/adr-903-frontmatter-mismatch.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-inactive-plan/.plans/c_done/26-08-08-completed-fixture.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-inactive-plan/.specs/demo/lifecycle.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/fixtures/adr-lifecycle-inactive-plan/docs/decisions/adr-904-completed-plan.md +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/adr_lifecycle/test_check.py +127 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/check.py +91 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/CLAUDE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/architecture/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/compliance/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/decisions/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/index.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/operations/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/research/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/stray-root-file.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/totally-made-up/nonsense.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +106 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/check.py +291 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/clean/backend/clean.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/excluded/frontend/mobile/src/i18n/locales/zh-CN/strings.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/violation/backend/escaped.rs +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/violation/backend/raw.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/violation/backend//346/226/207/344/273/266.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/test_check.py +193 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/check.py +140 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-clean/docs/architecture/foo.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-clean/docs/architecture/index.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-violation/docs/architecture/foo.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/fixtures/index-updated-violation/docs/architecture/index.md +9 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/index_updated/test_check.py +98 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/check.py +378 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/docs-knowledge-indexes.yaml +192 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs/index.md +14 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs/one.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs/two.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-clean/docs-knowledge-indexes.yaml +20 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs/index.md +15 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs/one.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs/two.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-duplicate/docs-knowledge-indexes.yaml +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs/index.md +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs/one.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs/two.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/fixtures/knowledge-indexes-missing-entry/docs-knowledge-indexes.yaml +16 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/indexes/test_check.py +102 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/check.py +387 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-bad-date/plans/LEDGER.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-clean/plans/LEDGER.md +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-empty-table/plans/LEDGER.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-long-event/plans/LEDGER.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/fixtures/ledger-violation/plans/LEDGER.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +427 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/check.py +84 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-fence/docs/bad-fence.md +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-fence/docs/valid.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-frontmatter/docs/bad-front-matter.md +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-bad-frontmatter/docs/valid.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/fixtures/markdown-structure-clean/docs/valid.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/markdown_ast/test_check.py +126 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/check.py +210 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +119 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs/architecture/demo.md +17 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-clean/docs-okf-profiles.yaml +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs/architecture/bad-status.md +17 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-violation/docs-okf-profiles.yaml +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +49 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/register.py +159 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/check.py +578 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/architecture/backend/crate-overview.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/architecture/team/team-overview.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/compliance/controls/control-matrix.md +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/compliance/controls/control-matrix.yaml +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-clean/docs/research/benchmarks/note.md +24 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/backend/crate-overview.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-delivery.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-recommendation.md +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/target-narration.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/team/team-overview.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/compliance/controls/control-matrix.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/compliance/controls/control-matrix.yaml +17 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/research/benchmarks/bad-research.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/test_check.py +461 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/check.py +124 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/.workflows/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/.workflows/CLAUDE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/CLAUDE.md +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/INVARIANTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/bad-pointer/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/bad-pointer/CLAUDE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/docs/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/docs/CLAUDE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/missing-pair/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/owned/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/fixtures/owned/CLAUDE.md +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/agents_layout/test_check.py +116 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/__init__.py +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/check.py +380 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/fixtures/redeclared-workflows-tree/automation/pipelines/pr-validation.yaml +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/ci_paths/test_check.py +274 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/check.py +227 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/grants.yaml +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/vendored-crate/Cargo.toml +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/grants.yaml +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/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/frontend/packages/desktop/src/renderer/solo/item.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/grants.yaml +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/test_check.py +138 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/check.py +240 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-clean/.plans/a_new/plan.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-clean/backend/AGENTS.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-clean/docs/page.md +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-stray/backend/notes.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/fixtures/markdown-trees-stray/docs/page.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/markdown_trees/test_check.py +220 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/__init__.py +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +225 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/GAPS-AND-FINDINGS.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/spec.md +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +330 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/check.py +240 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/agent/crates/enact-providers/src/snapshots/README.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-app/assets/builtin-assistants/avatars/assistant.jpg +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-assets/assets/logos/tools/coding/tool.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-session/src/backend/codex_init_prompt.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/mobile/assets/images/icon.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/logos/brand/app.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/themes/default-theme.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/public/pwa/icon-192.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/resources/product.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/apps/legacy/file.ts +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/backend/crates/enact-session/src/backend/README.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/frontend/unapproved/screenshot.png +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/test_check.py +230 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +53 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/__init__.py +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +438 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +9 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/spec.md +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/templates/feature.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +9 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/spec.md +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +461 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/check.py +62 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/NotAMigration.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/0001_long_prefix.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/001_init.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/002_add_index.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/003-dashed-slug.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/004_MixedCase.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/005_trailing space.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/01_short_prefix.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/not-a-migration.sql +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/test_check.py +116 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/check.py +188 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/.plans/c_done/26-07-27-history.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/README.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/links.md +20 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/route-target.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/.plans/c_done/26-07-27-history.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/README.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/links.md +20 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/route-target.md +8 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/test_check.py +166 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/check.py +135 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.agents/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.changeset/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.claude/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.code-review-graph/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.code-review-graph_old/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.codex/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.cursor/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.cursorrules +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.dockerignore +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.docs/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.donors/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.gitattributes +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.kimi-code/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.lfsconfig +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.mcp.json +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.opencode/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.workflows/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/CHANGELOG.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/CLAUDE-INVARIANTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/CLAUDE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/INDEPENDENT-VERIFICATION.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/INVARIANTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/LICENSE +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/NOTICE +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/README.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/RELEASE.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/VERSION +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/backend/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/compliance/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/dev/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/docker/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/docs/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/frontend/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/infra/declared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/justfile +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/opencode.jsonc +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/rule-tests/undeclared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/rules/undeclared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/unapproved-root/undeclared.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/undeclared-root-file.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/test_check.py +122 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/check.py +290 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.changeset/some-change.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.plans/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.specs/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/.workflows/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/CHANGELOG.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/CLAUDE.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/INDEPENDENT-VERIFICATION.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/INVARIANTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/README.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/RELEASE.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/STRAY.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/docs/AGENTS.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/docs/architecture/note.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/frontend/README.md +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/.workflows/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/README.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/docs/overview.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-missing-guide/plans/AGENTS.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/test_check.py +329 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/check.py +127 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/a/b/c/d/at-limit.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/a/b/c/d/e/too-deep.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-01.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-02.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-03.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-04.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-05.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-06.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-07.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-08.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-09.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-10.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-11.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-12.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-13.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-14.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-15.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/fixtures/scripts-layout/.workflows/scripts/direct-16.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/scripts_size_depth/test_check.py +163 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/check.py +169 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/.gitattributes +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/large file.bin +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/.gitattributes +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/large file.bin +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/small.txt +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/missing-filter/large file.bin +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/.gitattributes +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/payload.dat +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/test_check.py +133 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/check.py +129 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/disjoint/gate-registry.yaml +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/empty/gate-registry.yaml +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/overlapping/gate-registry.yaml +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/test_check.py +109 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/check.py +149 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/clean/docs/readme.md +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/relevant/VERSION +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/test_check.py +151 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/check.py +202 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/changesets/package.json +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/package.json +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/CHANGELOG.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/VERSION +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/changesets/package.json +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/package.json +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/CHANGELOG.md +5 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/VERSION +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/test_check.py +162 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/check.py +103 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/git-bad.sh +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/git-note.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/good.sh +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/grep-bad.sh +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/grep-note.sh +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/grep-trailing.sh +2 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/compliance_py/checks/notes.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/elsewhere/loose-git.sh +6 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/fixtures/no-banned-commands/elsewhere/loose-grep.sh +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/test_check.py +117 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/__init__.py +13 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/check.py +265 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/fixtures/ignored-paths/keep.log +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/fixtures/ignored-paths/src/main.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/ignored_paths/test_check.py +180 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/__init__.py +12 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/check.py +84 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/fixtures/license-declared-clean/LICENSE +10 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/fixtures/license-declared-mismatch/LICENSE +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/fixtures/license-declared-missing/README.md +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/license_declared/test_check.py +66 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/check.py +167 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.lock +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.toml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/rust-toolchain.toml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/src/main.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/frontend/bun.lock +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.lock +7 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.toml +4 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/rust-toolchain.toml +3 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/src/main.rs +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/frontend/bun.lock +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/test_check.py +109 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +483 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/ast_grep.py +199 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/batch.py +52 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/check.py +303 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/constants.py +106 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/context.py +99 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/errors.py +32 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/finding.py +65 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/models.py +40 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/receipt.py +63 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/scope.py +82 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/core/verdict.py +161 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/__init__.py +1 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.py +131 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/config.toml +30 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +65 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +791 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.schema.yaml +176 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +791 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/grants-registry.yaml +112 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/grants.py +129 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/registry.py +281 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/root_shape.py +250 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/severity-policy.yaml +114 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/providers/__init__.py +11 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/runner.py +371 -0
- package/packages/repo-controls/package/adl-compliance/uv.lock +199 -0
- package/packages/repo-controls/package.json +33 -0
- package/packages/repo-controls/scripts/hooks/commit-msg +63 -0
- package/packages/repo-controls/scripts/hooks/lib/hook-log.sh +62 -0
- package/packages/repo-controls/scripts/hooks/post-checkout +15 -0
- package/packages/repo-controls/scripts/hooks/post-commit +18 -0
- package/packages/repo-controls/scripts/hooks/pre-commit +82 -0
- package/packages/repo-controls/scripts/hooks/pre-push +144 -0
- package/packages/repo-controls/src/azdo-push.ts +463 -0
- package/packages/repo-controls/src/azure-devops-governance.ts +248 -0
- package/packages/repo-controls/src/azure-devops-remote.ts +61 -0
- package/packages/repo-controls/src/cli.ts +188 -0
- package/packages/repo-controls/src/compliance-config.ts +200 -0
- package/packages/repo-controls/src/config.ts +690 -0
- package/packages/repo-controls/src/constants.ts +97 -0
- package/packages/repo-controls/src/coverage-ratchet.ts +153 -0
- package/packages/repo-controls/src/doc-freshness.ts +172 -0
- package/packages/repo-controls/src/doctor.ts +304 -0
- package/packages/repo-controls/src/findings-ratchet.ts +163 -0
- package/packages/repo-controls/src/gitignore-block.ts +253 -0
- package/packages/repo-controls/src/hook-log.ts +72 -0
- package/packages/repo-controls/src/hook.ts +694 -0
- package/packages/repo-controls/src/install.ts +506 -0
- package/packages/repo-controls/src/integrity.ts +104 -0
- package/packages/repo-controls/src/promote.ts +25 -0
- package/packages/repo-controls/src/runtime.ts +110 -0
- package/packages/repo-controls/src/workflows-drift.ts +106 -0
- package/packages/repo-controls/src/worktree-guard.ts +272 -0
- package/packages/repo-controls/src/worktree-limit.ts +11 -0
- package/packages/repo-controls/tsconfig.json +14 -0
- package/packages/repo-controls/workflows/scripts/release/verify-npm-artifact.ts +119 -0
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classifying a token stream into an `Invocation`.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `.agents/hooks/pre-tool-use/src/walk.mjs`. The old tree spread
|
|
5
|
+
* this classification across `walk.mjs`, three Sets in `constants.mjs`, and a
|
|
6
|
+
* prose `reason` string in `blocked-reason.mjs` that `deny.mjs` then
|
|
7
|
+
* regex-reparsed into a message kind. Here `walk` only classifies — it
|
|
8
|
+
* returns a structured `Invocation`, never a reason — and every rule's own
|
|
9
|
+
* `match()` (src/rules/impl/*.ts) decides whether that invocation is denied.
|
|
10
|
+
*
|
|
11
|
+
* `NON_CANONICAL_TEST_RECIPES` (constants.mjs:45) is deliberately not
|
|
12
|
+
* ported: the Set was empty, so `walkJustCommand`'s recipe branch
|
|
13
|
+
* (walk.mjs:144-151) was unreachable dead code exercised by no corpus string.
|
|
14
|
+
* Its config equivalent is `[rules."tool.noncanonical-test"] enabled = false`
|
|
15
|
+
* — there is no rule registered under that id at all, since one guarded by
|
|
16
|
+
* an always-empty Set is not a rule, it is a config toggle with nothing
|
|
17
|
+
* behind it.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { basename, dynamicToken, isTrustedRepoScript, type Token, tokenValue } from './token.ts'
|
|
21
|
+
import { scanGitGlobals, skipPrefixes } from './prefixes.ts'
|
|
22
|
+
import {
|
|
23
|
+
gitInternalsWriteTargets,
|
|
24
|
+
unresolvableOperandWriteTargets,
|
|
25
|
+
unresolvableRedirectionWriteTargets,
|
|
26
|
+
withoutRedirections,
|
|
27
|
+
} from './git-internals.ts'
|
|
28
|
+
import { protectedCarrierWriteTargets } from './protected-carrier-writes.ts'
|
|
29
|
+
import { type HereDocument, hereDocumentPlaceholder, hereDocumentPlaceholderIndex, nestedSources, scanHereDocuments } from './substitution.ts'
|
|
30
|
+
import { type PieceFeed, splitShell } from './split.ts'
|
|
31
|
+
import { tokenize } from './token.ts'
|
|
32
|
+
import { uncertain, xargsInvocation } from './xargs.ts'
|
|
33
|
+
import { EXPRESSION_RUNNERS, stageFetcher, staticProducerOutput, wordFetcher } from './piped-script.ts'
|
|
34
|
+
|
|
35
|
+
export type Invocation =
|
|
36
|
+
| {
|
|
37
|
+
readonly kind: 'git'
|
|
38
|
+
readonly subcommand: string
|
|
39
|
+
readonly globals: readonly Token[]
|
|
40
|
+
readonly rest: readonly Token[]
|
|
41
|
+
/**
|
|
42
|
+
* The raw command word that invoked git — `git`/`git.exe` when run by
|
|
43
|
+
* bare name (the common case, and the only one `git-worktree`'s
|
|
44
|
+
* synthetic shortcut below ever produces), or the exact path when run
|
|
45
|
+
* by one (`/usr/local/bin/git`, `/usr/bin/git`, …). H2
|
|
46
|
+
* (`adl-git-credential-bypass.ts`) denies an ABSOLUTE path other than
|
|
47
|
+
* the managed front controller `/usr/local/bin/git` — an agent
|
|
48
|
+
* invoking git by any other absolute path bypasses `bin/git-ado`
|
|
49
|
+
* entirely, since that routing lives at exactly that one path. Absent
|
|
50
|
+
* on the `git-worktree` binary shortcut (no real "binary word" to
|
|
51
|
+
* report there).
|
|
52
|
+
*/
|
|
53
|
+
readonly binary?: string
|
|
54
|
+
}
|
|
55
|
+
| { readonly kind: 'rm'; readonly args: readonly Token[] }
|
|
56
|
+
| { readonly kind: 'trash'; readonly args: readonly Token[] }
|
|
57
|
+
| { readonly kind: 'mv'; readonly args: readonly Token[] }
|
|
58
|
+
| { readonly kind: 'hook-disarm' }
|
|
59
|
+
/**
|
|
60
|
+
* A command that writes into a `.git` directory or gitdir file — delete,
|
|
61
|
+
* trash, move, copy/link/sync over, truncate, redirect into, tee, in-place
|
|
62
|
+
* edit, chmod/chown, or find -delete. `targets` are the offending paths.
|
|
63
|
+
* See git-internals.ts; denied by fs.git-internals.
|
|
64
|
+
*/
|
|
65
|
+
| { readonly kind: 'git-internals-write'; readonly name: string; readonly targets: readonly string[] }
|
|
66
|
+
/**
|
|
67
|
+
* A command that writes into one of the seven tamper-protected carrier
|
|
68
|
+
* files (`.claude/settings.json`, `.codex/hooks.json`, `adl-config.toml`,
|
|
69
|
+
* …) via redirection, tee, mv/cp/install/ln, sed -i/perl -pi, truncate,
|
|
70
|
+
* trash/rm/unlink/shred, or dd. See shell/protected-carrier-writes.ts;
|
|
71
|
+
* denied by agent.tamper-protection. `git rm`/`checkout --`/`restore --`/
|
|
72
|
+
* `reset --` on one of these files is a `'git'` Invocation instead — see
|
|
73
|
+
* that rule's own match().
|
|
74
|
+
*/
|
|
75
|
+
| { readonly kind: 'protected-carrier-write'; readonly name: string; readonly targets: readonly string[] }
|
|
76
|
+
| { readonly kind: 'cargo' }
|
|
77
|
+
/**
|
|
78
|
+
* `az devops|repos|pipelines|boards|artifacts ...`. `args` is everything
|
|
79
|
+
* after `az` itself (the subcommand group onward, e.g. `['repos', 'pr',
|
|
80
|
+
* 'create', '--title', 'x']`) — carried so a rule can tell a mutating
|
|
81
|
+
* subcommand from a read one (`adl.session-required-for-writes`); denied
|
|
82
|
+
* outright regardless of that distinction by tool.azure-devops, which
|
|
83
|
+
* ignores this field. See impl/tool-azure-devops.ts.
|
|
84
|
+
*/
|
|
85
|
+
| { readonly kind: 'azure-devops'; readonly args: readonly Token[] }
|
|
86
|
+
| { readonly kind: 'find' }
|
|
87
|
+
| { readonly kind: 'grep' }
|
|
88
|
+
| { readonly kind: 'frontend-tool'; readonly tool: string }
|
|
89
|
+
/**
|
|
90
|
+
* `bun test` (any arguments, including a file/pattern operand) — the
|
|
91
|
+
* `test` subcommand specifically, after skipping bun's own `-C <dir>` /
|
|
92
|
+
* `--cwd <dir>` / `--cwd=<dir>` global. Tarun's decision (2026-09-27,
|
|
93
|
+
* feature az372): ban `bun test` only — `bun run test`, `bun run
|
|
94
|
+
* <script>`, `bunx`, `bun install` and every other bun subcommand stay
|
|
95
|
+
* unclassified here (`null` from `walkToolCommand`) and fall through to
|
|
96
|
+
* the ordinary `command` kind, which no rule denies. See
|
|
97
|
+
* impl/tool-bun-test.ts.
|
|
98
|
+
*/
|
|
99
|
+
| { readonly kind: 'bun-test' }
|
|
100
|
+
| { readonly kind: 'uncertain'; readonly reason: string }
|
|
101
|
+
/**
|
|
102
|
+
* A shell (or `.`/`source`/`eval`/PowerShell `iex`) executing content a
|
|
103
|
+
* network fetcher produced — piped into its stdin, or read through a
|
|
104
|
+
* `<(...)`/`$(...)`/backtick. See piped-script.ts; denied by
|
|
105
|
+
* shell.remote-script-exec.
|
|
106
|
+
*/
|
|
107
|
+
| { readonly kind: 'remote-script-exec'; readonly fetcher: string }
|
|
108
|
+
/**
|
|
109
|
+
* A command the hook never denies, but that advisory.finder wants to see:
|
|
110
|
+
* `grep`/`egrep`/`fgrep`/`rgrep`/`ack`/`ag` (constants.mjs's
|
|
111
|
+
* `SUBSTITUTABLE_FINDERS`), plus every other read-only consumer and
|
|
112
|
+
* introspection command that used to just vanish as `[]`. Every rule but
|
|
113
|
+
* advisory.finder treats this exactly like an empty invocation list — see
|
|
114
|
+
* its `match()`, which is the only one that inspects `name`.
|
|
115
|
+
*/
|
|
116
|
+
| { readonly kind: 'read-only'; readonly name: string }
|
|
117
|
+
/**
|
|
118
|
+
* A command whose arguments reach into a tree agents must not touch
|
|
119
|
+
* (`cfg.deniedTrees`). Emitted for ANY command, ahead of every other
|
|
120
|
+
* classification, because the alternative does not exist: `cat`/`ls` and the
|
|
121
|
+
* other read-only consumers return `[]` here and are invisible to rules, and
|
|
122
|
+
* `read-only` carries only a name. Without this, a path-based deny could see
|
|
123
|
+
* nothing to deny. See impl/fs-canonical-checkout.ts.
|
|
124
|
+
*/
|
|
125
|
+
| { readonly kind: 'denied-path'; readonly name: string; readonly path: string }
|
|
126
|
+
/**
|
|
127
|
+
* An otherwise-unclassified top-level command. Emitted alongside whatever
|
|
128
|
+
* `walkNested` finds inside its arguments — every built-in rule but
|
|
129
|
+
* `tool.lean-ctx-global-state` ignores this kind's extra `prefix` field, so
|
|
130
|
+
* adding it changes no existing decision; the kind itself exists purely so
|
|
131
|
+
* `custom.ts`'s declarative `category: "tool"` rules (name-based tool
|
|
132
|
+
* bans) have something to match. See custom.ts's module doc.
|
|
133
|
+
*/
|
|
134
|
+
| {
|
|
135
|
+
readonly kind: 'command'
|
|
136
|
+
readonly name: string
|
|
137
|
+
readonly args: readonly Token[]
|
|
138
|
+
/**
|
|
139
|
+
* The tokens `shell/prefixes.ts`'s `skipPrefixes` consumed to resolve
|
|
140
|
+
* `name` — every leading `NAME=value` assignment word, and every
|
|
141
|
+
* wrapper command's own name/flags (`sudo`/`env`/`timeout`/…), in
|
|
142
|
+
* their original order. Present so a rule can tell whether THIS
|
|
143
|
+
* specific invocation carries its own inline assignment (e.g.
|
|
144
|
+
* `tool.lean-ctx-global-state`'s per-invocation `LEAN_CTX_CONFIG_DIR=`
|
|
145
|
+
* check) without re-deriving it from the whole raw command text — a
|
|
146
|
+
* `NAME=value` immediately before the command name, or one consumed as
|
|
147
|
+
* an `env NAME=value cmd` operand (`skipEnvFlags`'s own
|
|
148
|
+
* "value-taking flag" branch), both land here. Empty when nothing was
|
|
149
|
+
* skipped. Optional only so hand-built test fixtures that predate this
|
|
150
|
+
* field keep compiling.
|
|
151
|
+
*/
|
|
152
|
+
readonly prefix?: readonly Token[]
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A non-shell tool call — Read, Write, Edit, Grep, Glob, an MCP tool, and
|
|
156
|
+
* so on. The original's `decide-render.mjs:25` gated on `SHELL_TOOLS`, so
|
|
157
|
+
* these never reached the engine at all. Opening that gate (default OFF;
|
|
158
|
+
* see engine.ts) lets a `[[rules.custom]]` entry with `category:
|
|
159
|
+
* "tool-call"` deny a call by its tool name and input shape — e.g. an Edit
|
|
160
|
+
* whose `file_path` sits inside a protected tree, catching the edit
|
|
161
|
+
* instead of only the commit.
|
|
162
|
+
*/
|
|
163
|
+
| { readonly kind: 'tool-call'; readonly tool: string; readonly input: Readonly<Record<string, unknown>> }
|
|
164
|
+
|
|
165
|
+
/** The name-vocabulary walk() needs to classify a command. Defaults mirror constants.mjs exactly. */
|
|
166
|
+
export interface ClassifyConfig {
|
|
167
|
+
readonly shellNames: ReadonlySet<string>
|
|
168
|
+
readonly shellExecutors: ReadonlySet<string>
|
|
169
|
+
readonly introspectionNames: ReadonlySet<string>
|
|
170
|
+
readonly subcommandTools: ReadonlySet<string>
|
|
171
|
+
readonly readOnlyConsumers: ReadonlySet<string>
|
|
172
|
+
readonly rmNames: ReadonlySet<string>
|
|
173
|
+
readonly trashNames: ReadonlySet<string>
|
|
174
|
+
readonly azureDevopsNames: ReadonlySet<string>
|
|
175
|
+
readonly azureDevopsSubcommands: ReadonlySet<string>
|
|
176
|
+
readonly cargoNames: ReadonlySet<string>
|
|
177
|
+
readonly justNames: ReadonlySet<string>
|
|
178
|
+
readonly frontendToolNames: ReadonlySet<string>
|
|
179
|
+
readonly bunNames: ReadonlySet<string>
|
|
180
|
+
readonly trustedScriptPrefixes: readonly string[]
|
|
181
|
+
/** `SUBSTITUTABLE_FINDERS` (constants.mjs:123) — `find` is deliberately absent; see tool-find.ts. */
|
|
182
|
+
readonly substitutableFinders: ReadonlySet<string>
|
|
183
|
+
/**
|
|
184
|
+
* Absolute trees no command may reference. Empty by default, so classification
|
|
185
|
+
* is unchanged unless an engine supplies one (engine.ts's classifyConfigFor).
|
|
186
|
+
*/
|
|
187
|
+
readonly deniedTrees: readonly string[]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const DEFAULT_CLASSIFY_CONFIG: ClassifyConfig = {
|
|
191
|
+
deniedTrees: [],
|
|
192
|
+
shellNames: new Set(['sh', 'bash', 'zsh', 'dash', 'ksh', 'fish']),
|
|
193
|
+
shellExecutors: new Set(['.', 'eval', 'source']),
|
|
194
|
+
introspectionNames: new Set(['echo', 'printf', 'which', 'type', 'whereis', 'whatis', 'man', 'apropos', 'info']),
|
|
195
|
+
subcommandTools: new Set(['docker', 'podman', 'nerdctl', 'kubectl', 'helm', 'buildah', 'crictl']),
|
|
196
|
+
readOnlyConsumers: new Set([
|
|
197
|
+
'rg',
|
|
198
|
+
'rg.exe',
|
|
199
|
+
'cat',
|
|
200
|
+
'cat.exe',
|
|
201
|
+
'sed',
|
|
202
|
+
'sed.exe',
|
|
203
|
+
'head',
|
|
204
|
+
'head.exe',
|
|
205
|
+
'tail',
|
|
206
|
+
'tail.exe',
|
|
207
|
+
'less',
|
|
208
|
+
'less.exe',
|
|
209
|
+
'more',
|
|
210
|
+
'more.exe',
|
|
211
|
+
'awk',
|
|
212
|
+
'awk.exe',
|
|
213
|
+
'grep',
|
|
214
|
+
'grep.exe',
|
|
215
|
+
]),
|
|
216
|
+
rmNames: new Set(['rm', 'rm.exe', 'unlink', 'unlink.exe']),
|
|
217
|
+
trashNames: new Set(['trash', 'trash.exe']),
|
|
218
|
+
azureDevopsNames: new Set(['az', 'az.exe']),
|
|
219
|
+
azureDevopsSubcommands: new Set(['devops', 'repos', 'pipelines', 'boards', 'artifacts']),
|
|
220
|
+
cargoNames: new Set(['cargo', 'cargo.exe']),
|
|
221
|
+
justNames: new Set(['just', 'just.exe']),
|
|
222
|
+
frontendToolNames: new Set(['npx', 'npx.exe', 'vitest', 'vitest.exe', 'jest', 'jest.exe', 'biome', 'biome.exe', 'tsc', 'tsc.exe']),
|
|
223
|
+
bunNames: new Set(['bun', 'bun.exe']),
|
|
224
|
+
trustedScriptPrefixes: ['workflows/scripts/', '.code-quality/', '.agents/hooks/'],
|
|
225
|
+
substitutableFinders: new Set(['grep', 'grep.exe', 'egrep', 'fgrep', 'rgrep', 'ack', 'ag']),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isClassifiedCommandName(token: Token, cfg: ClassifyConfig): boolean {
|
|
229
|
+
const name = basename(token)
|
|
230
|
+
return (
|
|
231
|
+
cfg.rmNames.has(name) ||
|
|
232
|
+
cfg.trashNames.has(name) ||
|
|
233
|
+
cfg.azureDevopsNames.has(name) ||
|
|
234
|
+
cfg.cargoNames.has(name) ||
|
|
235
|
+
cfg.justNames.has(name) ||
|
|
236
|
+
cfg.frontendToolNames.has(name) ||
|
|
237
|
+
cfg.bunNames.has(name) ||
|
|
238
|
+
name === 'git' ||
|
|
239
|
+
name === 'git.exe' ||
|
|
240
|
+
name === 'git-worktree' ||
|
|
241
|
+
name === 'git-worktree.exe' ||
|
|
242
|
+
name === 'busybox'
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function rmIsHelpOnly(args: readonly Token[]): boolean {
|
|
247
|
+
const flags = new Set<string>()
|
|
248
|
+
for (const token of args) {
|
|
249
|
+
const arg = tokenValue(token)
|
|
250
|
+
if (arg === '--') break
|
|
251
|
+
if (arg === '--help' || arg === '--version') flags.add('help')
|
|
252
|
+
if (arg === '-h' && args.length === 1) flags.add('help')
|
|
253
|
+
}
|
|
254
|
+
return flags.has('help') && args.every((token) => tokenValue(token).startsWith('-') && tokenValue(token) !== '--')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function isHelpOrVersionOnly(args: readonly Token[]): boolean {
|
|
258
|
+
let sawHelp = false
|
|
259
|
+
for (const token of args) {
|
|
260
|
+
const arg = tokenValue(token)
|
|
261
|
+
if (arg === '--') return false
|
|
262
|
+
if (!arg.startsWith('-')) return false
|
|
263
|
+
if (arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V') {
|
|
264
|
+
sawHelp = true
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
return false
|
|
268
|
+
}
|
|
269
|
+
return sawHelp
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isShellCommandOption(value: string): boolean {
|
|
273
|
+
return value === '-c' || /^-[^-]*c$/.test(value) || value === '--command' || value.startsWith('--command=')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isShellNoExecFlag(token: Token): boolean {
|
|
277
|
+
return /^-[a-zA-Z]*n[a-zA-Z]*$/.test(tokenValue(token))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const LEADING_ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Whether `tokens` begins (after any leading `NAME=value` assignments) with
|
|
284
|
+
* an `env`/`env.exe` invocation that carries `-S`/`--split-string` among its
|
|
285
|
+
* own flags — R3 (STATUS.md 12p). Checked at `walk()`'s
|
|
286
|
+
* own call site BEFORE `skipPrefixes` (../prefixes.ts's `skipEnvFlags`)
|
|
287
|
+
* consumes `-S`/`--split-string` and its value as an opaque, value-taking
|
|
288
|
+
* flag argument; this mirrors that same flag-scanning loop just far enough
|
|
289
|
+
* to detect the flag's PRESENCE, without trying to parse or classify what
|
|
290
|
+
* its value names. Narrow by design: only a leading `env`, not `env` nested
|
|
291
|
+
* behind another wrapper (`sudo env -S …`) — the common, and only currently
|
|
292
|
+
* observed, shape.
|
|
293
|
+
*/
|
|
294
|
+
function envUsesSplitString(tokens: readonly Token[]): boolean {
|
|
295
|
+
let index = 0
|
|
296
|
+
while (index < tokens.length && LEADING_ASSIGNMENT_RE.test(tokenValue(tokens[index] as Token))) index += 1
|
|
297
|
+
if (index >= tokens.length) return false
|
|
298
|
+
const name = basename(tokens[index] as Token)
|
|
299
|
+
if (name !== 'env' && name !== 'env.exe') return false
|
|
300
|
+
index += 1
|
|
301
|
+
while (index < tokens.length) {
|
|
302
|
+
const value = tokenValue(tokens[index] as Token)
|
|
303
|
+
if (value === '--') return false
|
|
304
|
+
if (value === '-S' || value === '--split-string') return true
|
|
305
|
+
if (value.startsWith('--split-string=')) return true
|
|
306
|
+
if (value.startsWith('--unset=') || value.startsWith('--chdir=') || value.startsWith('-') || value.includes('=')) {
|
|
307
|
+
index += ['-u', '--unset', '-C', '--chdir'].includes(value) ? 2 : 1
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function walkShellArgument(
|
|
316
|
+
args: readonly Token[],
|
|
317
|
+
look: number,
|
|
318
|
+
cfg: ClassifyConfig,
|
|
319
|
+
parseScript: (script: string, feed?: StdinFeed) => readonly Invocation[],
|
|
320
|
+
feed: StdinFeed | undefined,
|
|
321
|
+
): readonly Invocation[] | null {
|
|
322
|
+
const arg = args[look] as Token
|
|
323
|
+
const value = tokenValue(arg)
|
|
324
|
+
if (value === '--') return null
|
|
325
|
+
if (value === '-c' || /^-[^-]*c$/.test(value) || value === '--command') {
|
|
326
|
+
const next = args[look + 1]
|
|
327
|
+
return next === undefined ? uncertain('dynamic shell script') : parseScript(tokenValue(next), feed)
|
|
328
|
+
}
|
|
329
|
+
if (value.startsWith('--command=')) {
|
|
330
|
+
const script = value.slice('--command='.length)
|
|
331
|
+
return script ? parseScript(script, feed) : uncertain('dynamic shell script')
|
|
332
|
+
}
|
|
333
|
+
if (!value.startsWith('-')) {
|
|
334
|
+
return isTrustedRepoScript(arg, cfg.trustedScriptPrefixes) ? [] : uncertain('shell script execution')
|
|
335
|
+
}
|
|
336
|
+
return null
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** `-n` (read but do not execute) without a `-c` script. */
|
|
340
|
+
const shellRunsNothing = (args: readonly Token[]): boolean =>
|
|
341
|
+
!args.some((token) => isShellCommandOption(tokenValue(token))) && args.some(isShellNoExecFlag)
|
|
342
|
+
|
|
343
|
+
/** Shell options that consume the following word. */
|
|
344
|
+
const SHELL_OPTIONS_WITH_VALUE = new Set(['-o', '+o', '-O', '+O', '--rcfile', '--init-file'])
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Whether a shell invoked with `args` (redirections removed) reads its script
|
|
348
|
+
* from stdin: no `-c` script and no script operand, or `-s`/`-` given.
|
|
349
|
+
*/
|
|
350
|
+
function shellScriptFromStdin(args: readonly Token[]): boolean {
|
|
351
|
+
let fromStdin = false
|
|
352
|
+
for (let look = 0; look < args.length; look += 1) {
|
|
353
|
+
const value = tokenValue(args[look] as Token)
|
|
354
|
+
if (isShellCommandOption(value)) return false
|
|
355
|
+
if (value === '-') return true
|
|
356
|
+
if (value === '--') return fromStdin || look + 1 >= args.length
|
|
357
|
+
if (SHELL_OPTIONS_WITH_VALUE.has(value)) {
|
|
358
|
+
look += 1
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
if (/^[-+][a-zA-Z]+$/.test(value)) {
|
|
362
|
+
if (value.startsWith('-') && value.includes('s')) fromStdin = true
|
|
363
|
+
continue
|
|
364
|
+
}
|
|
365
|
+
if (value.startsWith('--')) continue
|
|
366
|
+
return fromStdin
|
|
367
|
+
}
|
|
368
|
+
return true
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Whether `xargs ... <shell> ...` builds its shell's SCRIPT from xargs input:
|
|
373
|
+
* no `-c` (input items become script operands), a `-c` with no script, or a
|
|
374
|
+
* `-c` script containing the `-I`/`-J`/`--replace` string.
|
|
375
|
+
*/
|
|
376
|
+
function xargsShellReadsInput(args: readonly Token[], cfg: ClassifyConfig): boolean {
|
|
377
|
+
let replace: string | null = null
|
|
378
|
+
for (let look = 0; look < args.length; look += 1) {
|
|
379
|
+
const token = args[look] as Token
|
|
380
|
+
const value = tokenValue(token)
|
|
381
|
+
if (cfg.shellNames.has(basename(token))) {
|
|
382
|
+
const rest = withoutRedirections(args.slice(look + 1))
|
|
383
|
+
const option = rest.findIndex((word) => isShellCommandOption(tokenValue(word)))
|
|
384
|
+
if (option < 0) return true
|
|
385
|
+
const optionValue = tokenValue(rest[option] as Token)
|
|
386
|
+
const scriptToken = rest[option + 1]
|
|
387
|
+
const script = optionValue.startsWith('--command=') ? optionValue.slice('--command='.length) : scriptToken && tokenValue(scriptToken)
|
|
388
|
+
if (script === undefined || script === '') return true
|
|
389
|
+
return replace !== null && script.includes(replace)
|
|
390
|
+
}
|
|
391
|
+
if (value === '-I' || value === '-J') {
|
|
392
|
+
replace = tokenValue(args[look + 1] ?? token)
|
|
393
|
+
look += 1
|
|
394
|
+
} else if (value === '-i' || value === '--replace') replace = '{}'
|
|
395
|
+
else if (value.startsWith('--replace=')) replace = value.slice('--replace='.length)
|
|
396
|
+
else if (/^-[IJi]./.test(value)) replace = value.slice(2)
|
|
397
|
+
}
|
|
398
|
+
return false
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** A remote fetch executed as code — plus a fail-closed uncertain, so disabling the rule never turns it into an allow. */
|
|
402
|
+
const remoteExec = (fetcher: string): readonly Invocation[] => [
|
|
403
|
+
{ kind: 'remote-script-exec', fetcher },
|
|
404
|
+
...uncertain('remote content executed by a shell'),
|
|
405
|
+
]
|
|
406
|
+
|
|
407
|
+
/** A shell's stdin redirected from a file or descriptor (`bash < script.sh`, `sh <&3`) is a script nobody can see. */
|
|
408
|
+
function readsStdinFromFile(tokens: readonly Token[]): boolean {
|
|
409
|
+
return tokens.some((token, index) => {
|
|
410
|
+
const op = token.redirect
|
|
411
|
+
if (op !== '<' && op !== '<>' && op !== '<&') return false
|
|
412
|
+
if (!/^0?</.test(tokenValue(token))) return false
|
|
413
|
+
return !(op === '<&' && tokenValue(tokens[index + 1] ?? token) === '-')
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function walkShell(
|
|
418
|
+
rawArgs: readonly Token[],
|
|
419
|
+
cfg: ClassifyConfig,
|
|
420
|
+
parseScript: (script: string, feed?: StdinFeed) => readonly Invocation[],
|
|
421
|
+
feed: StdinFeed | undefined,
|
|
422
|
+
): readonly Invocation[] {
|
|
423
|
+
// Redirections are not script operands; here-document/here-string stdin is walked by `shellStdinInvocations`.
|
|
424
|
+
const args = withoutRedirections(rawArgs)
|
|
425
|
+
if (shellRunsNothing(args)) return []
|
|
426
|
+
for (let look = 0; look < args.length; look += 1) {
|
|
427
|
+
const result = walkShellArgument(args, look, cfg, parseScript, feed)
|
|
428
|
+
if (result !== null) return result
|
|
429
|
+
}
|
|
430
|
+
return readsStdinFromFile(rawArgs) ? uncertain('shell script execution') : []
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function staticEnvironment(tokens: readonly Token[], gitIndex: number): ReadonlyMap<string, string> {
|
|
434
|
+
const environment = new Map<string, string>()
|
|
435
|
+
for (const token of tokens.slice(0, gitIndex)) {
|
|
436
|
+
const assignment = tokenValue(token)
|
|
437
|
+
const equals = assignment.indexOf('=')
|
|
438
|
+
if (equals >= 0) environment.set(assignment.slice(0, equals), assignment.slice(equals + 1))
|
|
439
|
+
}
|
|
440
|
+
return environment
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function environmentGitAlias(environment: ReadonlyMap<string, string>, subcommand: string): string | null {
|
|
444
|
+
let count: number | null = null
|
|
445
|
+
const keys = new Map<number, string>()
|
|
446
|
+
const values = new Map<number, string>()
|
|
447
|
+
for (const [name, value] of environment) {
|
|
448
|
+
if (name === 'GIT_CONFIG_COUNT') count = /^\d+$/.test(value) ? Number(value) : null
|
|
449
|
+
const key = /^GIT_CONFIG_KEY_(\d+)$/.exec(name)
|
|
450
|
+
if (key) keys.set(Number(key[1]), value)
|
|
451
|
+
const configured = /^GIT_CONFIG_VALUE_(\d+)$/.exec(name)
|
|
452
|
+
if (configured) values.set(Number(configured[1]), value)
|
|
453
|
+
}
|
|
454
|
+
if (count === null || !Number.isSafeInteger(count)) return null
|
|
455
|
+
let target: string | null = null
|
|
456
|
+
for (const index of [...keys.keys()].filter((candidate) => candidate < count).sort((left, right) => left - right)) {
|
|
457
|
+
if (keys.get(index)?.toLowerCase() === `alias.${subcommand}` && values.has(index)) target = values.get(index) as string
|
|
458
|
+
}
|
|
459
|
+
return target
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Resolve alias definitions statically visible in this command; repository/global configuration is not inspectable here. */
|
|
463
|
+
function configuredGitAlias(tokens: readonly Token[], gitIndex: number, globals: readonly Token[], subcommand: string): string | null {
|
|
464
|
+
const environment = staticEnvironment(tokens, gitIndex)
|
|
465
|
+
let target = environmentGitAlias(environment, subcommand)
|
|
466
|
+
for (let index = 0; index < globals.length; index += 1) {
|
|
467
|
+
const value = tokenValue(globals[index] as Token)
|
|
468
|
+
if (value === '--config-env' || value.startsWith('--config-env=')) {
|
|
469
|
+
const setting = value === '--config-env' ? tokenValue(globals[index += 1] as Token) : value.slice('--config-env='.length)
|
|
470
|
+
const equals = setting.indexOf('=')
|
|
471
|
+
if (equals >= 0 && setting.slice(0, equals).toLowerCase() === `alias.${subcommand}`) {
|
|
472
|
+
target = environment.get(setting.slice(equals + 1)) ?? null
|
|
473
|
+
}
|
|
474
|
+
continue
|
|
475
|
+
}
|
|
476
|
+
const setting = value === '-c' ? tokenValue(globals[index += 1] as Token) : value.startsWith('-c') ? value.slice(2) : ''
|
|
477
|
+
const equals = setting.indexOf('=')
|
|
478
|
+
if (equals < 0 || setting.slice(0, equals).toLowerCase() !== `alias.${subcommand}`) continue
|
|
479
|
+
target = setting.slice(equals + 1).trim()
|
|
480
|
+
}
|
|
481
|
+
return target
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function classifyGitArgv(argv: readonly Token[], binary: string): readonly Invocation[] {
|
|
485
|
+
const scan = scanGitGlobals(argv, 0)
|
|
486
|
+
if (scan.terminal) return [{ kind: 'read-only', name: 'git' }]
|
|
487
|
+
const subIndex = scan.index
|
|
488
|
+
if (subIndex >= argv.length) return uncertain('git command missing subcommand')
|
|
489
|
+
const subToken = argv[subIndex] as Token
|
|
490
|
+
if (dynamicToken(subToken)) return uncertain('dynamic git subcommand')
|
|
491
|
+
return [
|
|
492
|
+
{
|
|
493
|
+
kind: 'git',
|
|
494
|
+
subcommand: tokenValue(subToken).toLowerCase(),
|
|
495
|
+
globals: argv.slice(0, subIndex),
|
|
496
|
+
rest: argv.slice(subIndex + 1),
|
|
497
|
+
binary,
|
|
498
|
+
},
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function walkGit(tokens: readonly Token[], index: number, parser: Parser): readonly Invocation[] {
|
|
503
|
+
const binary = tokenValue(tokens[index] as Token)
|
|
504
|
+
const argv = tokens.slice(index + 1)
|
|
505
|
+
const scan = scanGitGlobals(argv, 0)
|
|
506
|
+
if (scan.terminal) return [{ kind: 'read-only', name: 'git' }]
|
|
507
|
+
if (scan.index >= argv.length) return uncertain('git command missing subcommand')
|
|
508
|
+
const subToken = argv[scan.index] as Token
|
|
509
|
+
if (dynamicToken(subToken)) return uncertain('dynamic git subcommand')
|
|
510
|
+
const globals = argv.slice(0, scan.index)
|
|
511
|
+
const alias = configuredGitAlias(tokens, index, globals, tokenValue(subToken).toLowerCase())
|
|
512
|
+
if (alias === null) return classifyGitArgv(argv, binary)
|
|
513
|
+
if (alias.startsWith('!')) return parser.parseScript(alias.slice(1))
|
|
514
|
+
const expanded = tokenize(alias)
|
|
515
|
+
if (expanded.invalid) return uncertain('unparsed git alias')
|
|
516
|
+
return classifyGitArgv([...expanded.tokens, ...argv.slice(scan.index + 1)], binary)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function walkShellCommand(name: string, args: readonly Token[], cfg: ClassifyConfig, parser: Parser, feed: StdinFeed | undefined): readonly Invocation[] | null {
|
|
520
|
+
if (cfg.shellNames.has(name)) return walkShell(args, cfg, parser.parseScript, feed)
|
|
521
|
+
if (cfg.shellExecutors.has(name)) return uncertain(`dynamic ${name} execution`)
|
|
522
|
+
if (name === 'busybox') return args.length ? parser.walk(args, feed) : []
|
|
523
|
+
return null
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const LEAN_CTX_COMMAND_NAMES: ReadonlySet<string> = new Set(['lean-ctx', 'lean-ctx.exe'])
|
|
527
|
+
/** `lean-ctx raw`'s documented alias, plus `exec` ("Same as -c" per `lean-ctx help all`). */
|
|
528
|
+
const LEAN_CTX_EXEC_WORDS: ReadonlySet<string> = new Set(['exec', 'raw', 'bypass'])
|
|
529
|
+
/**
|
|
530
|
+
* lean-ctx's own shell-hook re-exec placeholder (`exec lean-ctx -c
|
|
531
|
+
* "$BASH_EXECUTION_STRING"`, injected into `~/.bashenv` by `lean-ctx setup`
|
|
532
|
+
* — see `../impl/tool-lean-ctx-global.ts`'s module doc). Bash substitutes
|
|
533
|
+
* the REAL command text into this variable inside the interactive shell's
|
|
534
|
+
* OWN DEBUG-trap machinery — never inside a Bash-tool `command` string an
|
|
535
|
+
* agent hands this hook. Reaching here unresolved means lean-ctx execs an
|
|
536
|
+
* EMPTY command (the variable is unset in every context this static
|
|
537
|
+
* scanner actually observes), not an unknown one, so it is recognized as
|
|
538
|
+
* this ONE specific, known placeholder and left alone — rather than
|
|
539
|
+
* failing closed on "any `$...`", which `dynamicToken` would otherwise do
|
|
540
|
+
* for every other unresolvable value below.
|
|
541
|
+
*/
|
|
542
|
+
const LEAN_CTX_SHELL_HOOK_PLACEHOLDER = '$BASH_EXECUTION_STRING'
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* lean-ctx's own arbitrary-command-execution surface — independent-review
|
|
546
|
+
* finding 1 (HIGH, pre-existing, affects every rule): `-c [--raw]`, `-t`,
|
|
547
|
+
* `exec`, `raw`/`bypass <command>` all hand a shell command to a REAL
|
|
548
|
+
* shell, exactly like `bash -c`'s own script argument, but nothing walked
|
|
549
|
+
* that argument before this fix — it was an opaque word inside an
|
|
550
|
+
* unclassified `{kind:'command', name:'lean-ctx', ...}`.
|
|
551
|
+
*
|
|
552
|
+
* Verified against the real `lean-ctx 3.10.5` binary: `lean-ctx -c echo a
|
|
553
|
+
* b` prints `a b` — every remaining word is joined with a single space
|
|
554
|
+
* into ONE command string (not only the first), so `rest.map(tokenValue).
|
|
555
|
+
* join(' ')` mirrors that exactly; a single already-quoted argument
|
|
556
|
+
* (`-c "npm install"`) round-trips unchanged through the same join.
|
|
557
|
+
*
|
|
558
|
+
* `lean-ctx shell` (an interactive compressed shell) is the READ side of
|
|
559
|
+
* the same risk when its stdin is fed by a pipe or a file redirection —
|
|
560
|
+
* the commands it will run are whatever that feed contains, invisible to
|
|
561
|
+
* this static scanner, so it fails closed as `uncertain` (matching how
|
|
562
|
+
* every other unreadable script source in this file is handled) rather
|
|
563
|
+
* than silently falling through to an unclassified allow. A bare `lean-ctx
|
|
564
|
+
* shell` with no stdin feed is left unclassified (out of scope for this
|
|
565
|
+
* finding — no probe or finding names that shape, and an interactive
|
|
566
|
+
* session an agent cannot feed input into is not the arbitrary-command
|
|
567
|
+
* risk this fix closes).
|
|
568
|
+
*
|
|
569
|
+
* Redirections between `lean-ctx` and its first argument (`lean-ctx
|
|
570
|
+
* >/dev/null -c "cmd"`) are stripped here the same way `walkShell` strips
|
|
571
|
+
* them for a real shell's own `-c` — `rawArgs` (`args` in `classify()`)
|
|
572
|
+
* still carries them; the generic `{kind:'command', ...}` fallback and
|
|
573
|
+
* `tool.lean-ctx-global-state`'s own `firstPositional` scan handle THAT
|
|
574
|
+
* redirection-before-subcommand case for the subcommand forms this
|
|
575
|
+
* function does not otherwise touch.
|
|
576
|
+
*/
|
|
577
|
+
function walkLeanCtxExec(
|
|
578
|
+
name: string,
|
|
579
|
+
rawArgs: readonly Token[],
|
|
580
|
+
rawTokens: readonly Token[],
|
|
581
|
+
parser: Parser,
|
|
582
|
+
feed: StdinFeed | undefined,
|
|
583
|
+
): readonly Invocation[] | null {
|
|
584
|
+
if (!LEAN_CTX_COMMAND_NAMES.has(name)) return null
|
|
585
|
+
const args = withoutRedirections(rawArgs)
|
|
586
|
+
if (args.length === 0) return null
|
|
587
|
+
const first = tokenValue(args[0] as Token)
|
|
588
|
+
const firstLower = first.toLowerCase()
|
|
589
|
+
|
|
590
|
+
if (firstLower === 'shell') {
|
|
591
|
+
return feed !== undefined || readsStdinFromFile(rawTokens) ? uncertain('lean-ctx shell fed from stdin') : null
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
let rest: readonly Token[] | null = null
|
|
595
|
+
if (first === '-c') {
|
|
596
|
+
const second = args[1]
|
|
597
|
+
rest = second !== undefined && tokenValue(second) === '--raw' ? args.slice(2) : args.slice(1)
|
|
598
|
+
} else if (first === '-t') {
|
|
599
|
+
rest = args.slice(1)
|
|
600
|
+
} else if (LEAN_CTX_EXEC_WORDS.has(firstLower)) {
|
|
601
|
+
rest = args.slice(1)
|
|
602
|
+
}
|
|
603
|
+
if (rest === null) return null
|
|
604
|
+
if (rest.length === 0) return uncertain('dynamic lean-ctx script')
|
|
605
|
+
if (rest.length === 1 && tokenValue(rest[0] as Token) === LEAN_CTX_SHELL_HOOK_PLACEHOLDER) return []
|
|
606
|
+
const joined = rest.map(tokenValue).join(' ')
|
|
607
|
+
return parser.parseScript(joined, feed)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const isTrashDestination = (token: Token): boolean => {
|
|
611
|
+
const value = tokenValue(token).replace(/\/+$/, '')
|
|
612
|
+
return value.endsWith('/.Trash') || value === '~/.Trash' || value.endsWith('/Trash')
|
|
613
|
+
}
|
|
614
|
+
const movesIntoTrash = (args: readonly Token[]): boolean => args.some((token) => isTrashDestination(token))
|
|
615
|
+
|
|
616
|
+
function walkFileCommand(name: string, args: readonly Token[], cfg: ClassifyConfig): readonly Invocation[] | null {
|
|
617
|
+
if (cfg.rmNames.has(name)) return [{ kind: 'rm', args }]
|
|
618
|
+
if (cfg.trashNames.has(name)) return [{ kind: 'trash', args }]
|
|
619
|
+
if (name === 'mv' && movesIntoTrash(args)) {
|
|
620
|
+
return [{ kind: 'trash', args: args.filter((token) => !isTrashDestination(token)) }]
|
|
621
|
+
}
|
|
622
|
+
if (name === 'mv') return [{ kind: 'mv', args }]
|
|
623
|
+
if (name !== 'chmod' && name !== 'chmod.exe') return null
|
|
624
|
+
const disarms = args.some((token) => {
|
|
625
|
+
const value = tokenValue(token)
|
|
626
|
+
return /(^|\/)workflows\/hooks(\/|$)/.test(value) || /(^|\/)\.git\/hooks(\/|$)/.test(value)
|
|
627
|
+
})
|
|
628
|
+
return disarms ? [{ kind: 'hook-disarm' }] : null
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function walkJustCommand(args: readonly Token[]): readonly Invocation[] | null {
|
|
632
|
+
// `just _cargo ...` shells out straight to cargo (walk.mjs:145) and is kept.
|
|
633
|
+
// The recipe-name branch that followed it (walk.mjs:146-150) is not ported
|
|
634
|
+
// — see the module comment for why.
|
|
635
|
+
if (args.some((token) => tokenValue(token) === '_cargo')) return [{ kind: 'cargo' }]
|
|
636
|
+
return null
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** bun's own global flags that take a directory value ahead of the subcommand — `bun -C <dir> test`, `bun --cwd <dir> test`. */
|
|
640
|
+
const BUN_CWD_FLAGS = new Set(['-C', '--cwd'])
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Whether `bun <args>` invokes the `test` subcommand, after skipping bun's
|
|
644
|
+
* own `-C <dir>` / `--cwd <dir>` / `--cwd=<dir>` global (bun accepts that
|
|
645
|
+
* ahead of the subcommand to run in a different directory). Any operand
|
|
646
|
+
* after `test` — a file, a pattern, `--coverage`, `--help` — leaves this
|
|
647
|
+
* true: per Tarun's decision (2026-09-27, feature az372) `bun test` is
|
|
648
|
+
* denied with ANY arguments, unlike cargo/frontend-tool which carve out
|
|
649
|
+
* `--help`/`--version`.
|
|
650
|
+
*/
|
|
651
|
+
function isBunTestSubcommand(args: readonly Token[]): boolean {
|
|
652
|
+
let index = 0
|
|
653
|
+
while (index < args.length) {
|
|
654
|
+
const value = tokenValue(args[index] as Token)
|
|
655
|
+
if (BUN_CWD_FLAGS.has(value)) {
|
|
656
|
+
index += 2
|
|
657
|
+
continue
|
|
658
|
+
}
|
|
659
|
+
if (value.startsWith('--cwd=')) {
|
|
660
|
+
index += 1
|
|
661
|
+
continue
|
|
662
|
+
}
|
|
663
|
+
break
|
|
664
|
+
}
|
|
665
|
+
const subcommand = args[index]
|
|
666
|
+
return subcommand !== undefined && tokenValue(subcommand) === 'test'
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* The real Azure CLI global parameters that can appear before ANY
|
|
671
|
+
* subcommand group — https://learn.microsoft.com/cli/azure/reference-index
|
|
672
|
+
* (checked 2026-09-28) — M6 (STATUS.md 12p):
|
|
673
|
+
* without skipping these, `az --output json devops project list` read
|
|
674
|
+
* `args[0]` as `--output`, not `devops`, never matched
|
|
675
|
+
* `azureDevopsSubcommands`, and fell through to the generic `'command'` kind
|
|
676
|
+
* — invisible to `tool.azure-devops`'s outright deny. `--help`/`-h`/
|
|
677
|
+
* `--version`/`-v` are deliberately NOT skipped here: `isHelpOrVersionOnly`
|
|
678
|
+
* already gates a purely-help invocation before this runs, and one
|
|
679
|
+
* appearing BEFORE the subcommand group is real az's own "show generic
|
|
680
|
+
* help, run nothing" shape — not worth threading through this scan too.
|
|
681
|
+
*/
|
|
682
|
+
const AZ_GLOBAL_VALUE_FLAGS: ReadonlySet<string> = new Set(['--output', '-o', '--query', '--subscription'])
|
|
683
|
+
const AZ_GLOBAL_BOOLEAN_FLAGS: ReadonlySet<string> = new Set(['--debug', '--only-show-errors', '--verbose'])
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Index of the first word in `args` that is not a recognized `az` global
|
|
687
|
+
* flag (or its value) — see `AZ_GLOBAL_VALUE_FLAGS`/`AZ_GLOBAL_BOOLEAN_FLAGS`.
|
|
688
|
+
* `--flag=value` is a single word and is skipped as one. Exported (not just
|
|
689
|
+
* used internally here) so `impl/tool-azure-devops.ts`'s own separate
|
|
690
|
+
* `account get-access-token`/`rest` positional checks (M6) use the SAME
|
|
691
|
+
* skip, rather than a second copy that could drift from this one.
|
|
692
|
+
*/
|
|
693
|
+
export function skipAzGlobalFlags(args: readonly Token[]): number {
|
|
694
|
+
let index = 0
|
|
695
|
+
while (index < args.length) {
|
|
696
|
+
const value = tokenValue(args[index] as Token)
|
|
697
|
+
if (AZ_GLOBAL_VALUE_FLAGS.has(value)) {
|
|
698
|
+
index += 2
|
|
699
|
+
continue
|
|
700
|
+
}
|
|
701
|
+
const equals = value.indexOf('=')
|
|
702
|
+
if (equals > 0 && AZ_GLOBAL_VALUE_FLAGS.has(value.slice(0, equals))) {
|
|
703
|
+
index += 1
|
|
704
|
+
continue
|
|
705
|
+
}
|
|
706
|
+
if (AZ_GLOBAL_BOOLEAN_FLAGS.has(value)) {
|
|
707
|
+
index += 1
|
|
708
|
+
continue
|
|
709
|
+
}
|
|
710
|
+
break
|
|
711
|
+
}
|
|
712
|
+
return index
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function walkToolCommand(name: string, args: readonly Token[], cfg: ClassifyConfig, walk: (tokens: readonly Token[]) => readonly Invocation[]): readonly Invocation[] | null {
|
|
716
|
+
if (cfg.azureDevopsNames.has(name)) {
|
|
717
|
+
if (isHelpOrVersionOnly(args)) return []
|
|
718
|
+
const subIndex = skipAzGlobalFlags(args)
|
|
719
|
+
const subcommand = subIndex < args.length ? tokenValue(args[subIndex] as Token) : ''
|
|
720
|
+
if (!cfg.azureDevopsSubcommands.has(subcommand)) return null
|
|
721
|
+
if (isHelpOrVersionOnly(args.slice(subIndex + 1))) return []
|
|
722
|
+
return [{ kind: 'azure-devops', args }]
|
|
723
|
+
}
|
|
724
|
+
if (cfg.cargoNames.has(name)) return isHelpOrVersionOnly(args) ? [] : [{ kind: 'cargo' }]
|
|
725
|
+
if (cfg.frontendToolNames.has(name)) return isHelpOrVersionOnly(args) ? [] : [{ kind: 'frontend-tool', tool: name }]
|
|
726
|
+
if (cfg.bunNames.has(name)) return isBunTestSubcommand(args) ? [{ kind: 'bun-test' }] : null
|
|
727
|
+
if (cfg.justNames.has(name)) return walkJustCommand(args)
|
|
728
|
+
if (name === 'xargs' || name === 'xargs.exe') return xargsInvocation(args, walk)
|
|
729
|
+
// `find` is denied outright, not merely inspected for -exec/-delete
|
|
730
|
+
// payloads: it emits PATHS, so piping it to grep/rg/awk/etc. filters
|
|
731
|
+
// FILENAMES, never file contents (see impl/tool-find.ts for the message).
|
|
732
|
+
if (name === 'find' || name === 'find.exe') return [{ kind: 'find' }]
|
|
733
|
+
// `grep` is denied outright, in every shape including a pure stdin filter:
|
|
734
|
+
// `rg` does that job too, and faster, so there is no capability to carve out
|
|
735
|
+
// (see impl/tool-grep.ts for the message and the replacement set).
|
|
736
|
+
if (GREP_NAMES.has(name)) return [{ kind: 'grep' }]
|
|
737
|
+
return null
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/** The grep family, plus the Windows spellings the other tool checks accept. */
|
|
741
|
+
const GREP_NAMES: ReadonlySet<string> = new Set([
|
|
742
|
+
'grep', 'egrep', 'fgrep', 'rgrep',
|
|
743
|
+
'grep.exe', 'egrep.exe', 'fgrep.exe', 'rgrep.exe',
|
|
744
|
+
])
|
|
745
|
+
|
|
746
|
+
function walkNested(tokens: readonly Token[], index: number, args: readonly Token[], cfg: ClassifyConfig, walk: (tokens: readonly Token[]) => readonly Invocation[]): readonly Invocation[] {
|
|
747
|
+
const nested: Invocation[] = []
|
|
748
|
+
for (let look = 0; look < args.length; look += 1) {
|
|
749
|
+
const arg = args[look] as Token
|
|
750
|
+
if (dynamicToken(arg) || !isClassifiedCommandName(arg, cfg)) continue
|
|
751
|
+
for (const invocation of walk(tokens.slice(index + 1 + look))) {
|
|
752
|
+
if (invocation.kind === 'uncertain' && invocation.reason === 'git command missing subcommand') continue
|
|
753
|
+
nested.push(invocation)
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return nested
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Nested shell scripts (`-c` arguments, here-document and here-string bodies fed to a shell) are walked at most this deep. */
|
|
760
|
+
export const MAX_SHELL_SCRIPT_DEPTH = 16
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* A piece's stdin when a pipe feeds it: every earlier stage of its pipeline
|
|
764
|
+
* (nearest last), and whether the pipeline starts at a group/subshell
|
|
765
|
+
* (`{ ...; } | b`) preceded by a here-document — that group's output is unknown.
|
|
766
|
+
*/
|
|
767
|
+
interface StdinFeed {
|
|
768
|
+
readonly upstream: readonly (readonly Token[])[]
|
|
769
|
+
/** Raw source text of each `upstream` stage (parallel to it), for static echo/printf reconstruction. */
|
|
770
|
+
readonly upstreamText: readonly string[]
|
|
771
|
+
readonly groupAfterHereText: boolean
|
|
772
|
+
/** Every piece before a group/subshell that pipes into this one (`(curl x) | bash`); empty otherwise. */
|
|
773
|
+
readonly groupUpstream: readonly (readonly Token[])[]
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
interface Parser {
|
|
777
|
+
readonly walk: (tokens: readonly Token[], feed?: StdinFeed) => readonly Invocation[]
|
|
778
|
+
/**
|
|
779
|
+
* Parse a shell script that a shell will run, one nesting level deeper.
|
|
780
|
+
* `feed` is the outer pipe's stdin, passed through only to the script's
|
|
781
|
+
* own first top-level piece — a `-c` script inherits the invoking
|
|
782
|
+
* process's stdin, so `bash -c 'bash'` fed by a pipe reads that pipe's
|
|
783
|
+
* content as ITS script, exactly like a bare piped `bash` would.
|
|
784
|
+
*/
|
|
785
|
+
readonly parseScript: (script: string, feed?: StdinFeed) => readonly Invocation[]
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** A command's tokens with its here-document placeholders detached, plus the text it reads from here-documents/strings. */
|
|
789
|
+
interface ShellInput {
|
|
790
|
+
readonly tokens: readonly Token[]
|
|
791
|
+
readonly documents: readonly HereDocument[]
|
|
792
|
+
readonly hereStrings: readonly Token[]
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Remove each `<<`/`<<-` + placeholder pair (so every rule sees exactly the
|
|
797
|
+
* tokens it saw when bodies were simply stripped) and collect the bodies and
|
|
798
|
+
* `<<<` words the command reads.
|
|
799
|
+
*/
|
|
800
|
+
function detachShellInput(tokens: readonly Token[], documents: readonly HereDocument[]): ShellInput {
|
|
801
|
+
const kept: Token[] = []
|
|
802
|
+
const attached: HereDocument[] = []
|
|
803
|
+
const hereStrings: Token[] = []
|
|
804
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
805
|
+
const token = tokens[index] as Token
|
|
806
|
+
const word = tokens[index + 1]
|
|
807
|
+
if ((token.redirect === '<<' || token.redirect === '<<-') && word !== undefined) {
|
|
808
|
+
const placeholder = hereDocumentPlaceholderIndex(tokenValue(word))
|
|
809
|
+
const document = placeholder === null ? undefined : documents[placeholder]
|
|
810
|
+
if (document !== undefined) {
|
|
811
|
+
attached.push(document)
|
|
812
|
+
index += 1
|
|
813
|
+
continue
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (token.redirect === '<<<' && word !== undefined) hereStrings.push(word)
|
|
817
|
+
kept.push(token)
|
|
818
|
+
}
|
|
819
|
+
return { tokens: kept, documents: attached, hereStrings }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const readsHereText = (input: ShellInput): boolean => input.documents.length > 0 || input.hereStrings.length > 0
|
|
823
|
+
const hasUnquotedDocument = (input: ShellInput): boolean => input.documents.some((document) => !document.quoted)
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Walk here-document bodies and here-string words as the script a shell reads
|
|
827
|
+
* from them. The literal text is walked FIRST, so a recognisable command
|
|
828
|
+
* reports under its own rule; an unquoted body or an expanding here-string is
|
|
829
|
+
* then still uninspectable (the outer shell expands it before the inner one
|
|
830
|
+
* parses it) and fails closed as before.
|
|
831
|
+
*/
|
|
832
|
+
function hereTextScript(input: ShellInput, parser: Parser): readonly Invocation[] {
|
|
833
|
+
const found: Invocation[] = []
|
|
834
|
+
for (const document of input.documents) found.push(...parser.parseScript(document.body))
|
|
835
|
+
for (const word of input.hereStrings) found.push(...parser.parseScript(tokenValue(word)))
|
|
836
|
+
if (hasUnquotedDocument(input)) found.push(...uncertain('unparsed unquoted here-document'))
|
|
837
|
+
if (input.hereStrings.some((word) => /[$`]/.test(tokenValue(word)))) found.push(...uncertain('dynamic here-string'))
|
|
838
|
+
return found
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function commandOf(input: ShellInput): { readonly name: string; readonly args: readonly Token[] } {
|
|
842
|
+
const index = skipPrefixes(input.tokens)
|
|
843
|
+
const name = index < input.tokens.length ? basename(input.tokens[index] as Token) : ''
|
|
844
|
+
return { name, args: withoutRedirections(input.tokens.slice(index + 1)) }
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const pipelineReadsHereText = (feed: StdinFeed, documents: readonly HereDocument[]): boolean =>
|
|
848
|
+
feed.groupAfterHereText || feed.upstream.some((tokens) => readsHereText(detachShellInput(tokens, documents)))
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* `cat <<'EOF' | bash`: a single bare `cat` stage passes its here-text through
|
|
852
|
+
* unchanged, so it is walked as the script; any other stage (or more than one)
|
|
853
|
+
* may transform it unseen, so it fails closed.
|
|
854
|
+
*/
|
|
855
|
+
function pipedHereTextScript(feed: StdinFeed, documents: readonly HereDocument[], parser: Parser): readonly Invocation[] {
|
|
856
|
+
if (!pipelineReadsHereText(feed, documents)) return []
|
|
857
|
+
const only = feed.upstream.length === 1 && !feed.groupAfterHereText ? detachShellInput(feed.upstream[0] as readonly Token[], documents) : null
|
|
858
|
+
if (only === null) return uncertain('here-document piped to shell')
|
|
859
|
+
const { name, args } = commandOf(only)
|
|
860
|
+
const plainCat = (name === 'cat' || name === 'cat.exe') && args.every((token) => tokenValue(token) === '-')
|
|
861
|
+
return plainCat ? hereTextScript(only, parser) : uncertain('here-document piped to shell')
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const pipedFetcher = (feed: StdinFeed): string | null => {
|
|
865
|
+
for (const stage of [...feed.groupUpstream, ...feed.upstream]) {
|
|
866
|
+
const fetcher = stageFetcher(stage)
|
|
867
|
+
if (fetcher !== null) return fetcher
|
|
868
|
+
}
|
|
869
|
+
return null
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/** The first fetcher run inside a substitution among a code runner's words (`bash <(curl x)`, `eval "$(curl x)"`). */
|
|
873
|
+
const substitutedFetcher = (tokens: readonly Token[]): string | null => {
|
|
874
|
+
for (const token of tokens) {
|
|
875
|
+
const fetcher = wordFetcher(tokenValue(token))
|
|
876
|
+
if (fetcher !== null) return fetcher
|
|
877
|
+
}
|
|
878
|
+
return null
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function makeParser(cfg: ClassifyConfig): Parser & { readonly parse: (command: string) => readonly Invocation[] } {
|
|
882
|
+
const documents: HereDocument[] = []
|
|
883
|
+
let depth = 0
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* A shell fed by a pipe (no here-text anywhere in the pipeline): remote
|
|
887
|
+
* content is denied outright; otherwise, when the shell reads its script
|
|
888
|
+
* from stdin, a lone static echo/printf is reconstructed and walked and any
|
|
889
|
+
* other producer fails closed.
|
|
890
|
+
*/
|
|
891
|
+
const pipedShellScript = (args: readonly Token[], input: ShellInput, feed: StdinFeed): readonly Invocation[] => {
|
|
892
|
+
if (pipelineReadsHereText(feed, documents)) return pipedHereTextScript(feed, documents, parser)
|
|
893
|
+
const fetcher = pipedFetcher(feed)
|
|
894
|
+
if (fetcher !== null) return remoteExec(fetcher)
|
|
895
|
+
if (readsHereText(input) || readsStdinFromFile(args) || !shellScriptFromStdin(withoutRedirections(args))) return []
|
|
896
|
+
const only = feed.upstream.length === 1 && feed.groupUpstream.length === 0 ? feed.upstreamText[0] : undefined
|
|
897
|
+
const script = only === undefined ? null : staticProducerOutput(only)
|
|
898
|
+
return script === null ? uncertain('script piped to shell') : parser.parseScript(script)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const stdinInvocations = (name: string, args: readonly Token[], input: ShellInput, feed: StdinFeed | undefined): readonly Invocation[] => {
|
|
902
|
+
const found: Invocation[] = []
|
|
903
|
+
const runsCode = cfg.shellNames.has(name) || cfg.shellExecutors.has(name) || EXPRESSION_RUNNERS.has(name)
|
|
904
|
+
const substituted = runsCode ? substitutedFetcher(args) : null
|
|
905
|
+
if (substituted !== null) found.push(...remoteExec(substituted))
|
|
906
|
+
if (EXPRESSION_RUNNERS.has(name) && feed !== undefined) {
|
|
907
|
+
const fetcher = pipedFetcher(feed)
|
|
908
|
+
found.push(...(fetcher === null ? uncertain('script piped to expression runner') : remoteExec(fetcher)))
|
|
909
|
+
return found
|
|
910
|
+
}
|
|
911
|
+
if (cfg.shellNames.has(name) && !shellRunsNothing(withoutRedirections(args))) {
|
|
912
|
+
found.push(...hereTextScript(input, parser))
|
|
913
|
+
if (feed !== undefined) found.push(...pipedShellScript(args, input, feed))
|
|
914
|
+
return found
|
|
915
|
+
}
|
|
916
|
+
const pipedHereText = feed !== undefined && pipelineReadsHereText(feed, documents)
|
|
917
|
+
const fedHereText = readsHereText(input) || pipedHereText
|
|
918
|
+
const xargsShell = (name === 'xargs' || name === 'xargs.exe') && args.some((token) => cfg.shellNames.has(basename(token)))
|
|
919
|
+
if (xargsShell && feed !== undefined && !pipedHereText) {
|
|
920
|
+
const fetcher = pipedFetcher(feed)
|
|
921
|
+
if (fetcher !== null) return [...found, ...remoteExec(fetcher)]
|
|
922
|
+
if (xargsShellReadsInput(args, cfg)) return [...found, ...uncertain('dynamic xargs execution')]
|
|
923
|
+
}
|
|
924
|
+
if (fedHereText && xargsShell) return [...found, ...uncertain('dynamic xargs execution')]
|
|
925
|
+
return hasUnquotedDocument(input) ? [...found, ...uncertain('unparsed unquoted here-document')] : found
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const walk = (rawTokens: readonly Token[], feed?: StdinFeed): readonly Invocation[] => {
|
|
929
|
+
const input = detachShellInput(rawTokens, documents)
|
|
930
|
+
const tokens = input.tokens
|
|
931
|
+
// R3 (STATUS.md 12p): `env -S`/`--split-string` splits its
|
|
932
|
+
// value into argv and execs it — but `skipPrefixes`'s own `skipEnvFlags`
|
|
933
|
+
// (../prefixes.ts) treats that value as an opaque, value-taking flag
|
|
934
|
+
// argument and skips straight past it, so whatever it actually names was
|
|
935
|
+
// previously invisible to every rule (a bare `env -S '<value>'` made
|
|
936
|
+
// `invocations()` return `[]` — no Invocation, so no rule's match() ever
|
|
937
|
+
// ran). Checked BEFORE `skipPrefixes` runs, so this fires instead of
|
|
938
|
+
// silently vanishing; see `envUsesSplitString`'s own doc.
|
|
939
|
+
if (envUsesSplitString(tokens)) return [...uncertain('env -S/--split-string execution')]
|
|
940
|
+
const index = skipPrefixes(tokens)
|
|
941
|
+
const name = index < tokens.length ? basename(tokens[index] as Token) : ''
|
|
942
|
+
const args = tokens.slice(index + 1)
|
|
943
|
+
// Emitted FIRST, so a `.git` write reports as fs.git-internals rather than
|
|
944
|
+
// as the generic rm/trash/find denial the same command also trips.
|
|
945
|
+
const targets = gitInternalsWriteTargets(name, args, tokens)
|
|
946
|
+
const found: Invocation[] = targets.length > 0 ? [{ kind: 'git-internals-write', name, targets }] : []
|
|
947
|
+
const carrierTargets = protectedCarrierWriteTargets(name, args, tokens)
|
|
948
|
+
if (carrierTargets.length > 0) found.push({ kind: 'protected-carrier-write', name, targets: carrierTargets })
|
|
949
|
+
// A write whose target is an unresolvable command substitution fails closed, like any other dynamic construct.
|
|
950
|
+
if (unresolvableRedirectionWriteTargets(tokens).length > 0) found.push(...uncertain('dynamic redirection target'))
|
|
951
|
+
if (unresolvableOperandWriteTargets(name, args).length > 0) found.push(...uncertain('dynamic write target'))
|
|
952
|
+
found.push(...stdinInvocations(name, args, input, feed))
|
|
953
|
+
found.push(...classify(tokens, index, name, args, feed))
|
|
954
|
+
return found
|
|
955
|
+
}
|
|
956
|
+
const classify = (tokens: readonly Token[], index: number, name: string, args: readonly Token[], feed: StdinFeed | undefined): readonly Invocation[] => {
|
|
957
|
+
if (index >= tokens.length) return []
|
|
958
|
+
const first = tokens[index] as Token
|
|
959
|
+
if (name === 'git-worktree' || name === 'git-worktree.exe') return [{ kind: 'git', subcommand: 'worktree', globals: [], rest: args }]
|
|
960
|
+
if (dynamicToken(first)) return uncertain('dynamic shell command')
|
|
961
|
+
|
|
962
|
+
// Independent-review finding 1 (HIGH, pre-existing, affects every rule):
|
|
963
|
+
// lean-ctx's own -c/-t/exec/raw(/bypass) run an ARBITRARY shell command
|
|
964
|
+
// that nothing below this point ever inspected — see walkLeanCtxExec's
|
|
965
|
+
// own doc for the fix and the verified-against-the-real-binary behavior
|
|
966
|
+
// it relies on. Checked before walkShellCommand/walkToolCommand/the
|
|
967
|
+
// generic fallback, same priority as a real shell's own -c.
|
|
968
|
+
const leanCtxExec = walkLeanCtxExec(name, args, tokens, parser, feed)
|
|
969
|
+
if (leanCtxExec !== null) return leanCtxExec
|
|
970
|
+
|
|
971
|
+
const shellCommand = walkShellCommand(name, args, cfg, parser, feed)
|
|
972
|
+
const denied = deniedPathInvocation(name, args, cfg)
|
|
973
|
+
if (denied !== null) return [denied]
|
|
974
|
+
if (shellCommand !== null) return shellCommand
|
|
975
|
+
if (name === 'git' || name === 'git.exe') return walkGit(tokens, index, parser)
|
|
976
|
+
const fileCommand = walkFileCommand(name, args, cfg)
|
|
977
|
+
if (fileCommand !== null) return fileCommand
|
|
978
|
+
const toolCommand = walkToolCommand(name, args, cfg, walk)
|
|
979
|
+
if (toolCommand !== null) return toolCommand
|
|
980
|
+
if (cfg.substitutableFinders.has(name)) return [{ kind: 'read-only', name }]
|
|
981
|
+
if (cfg.introspectionNames.has(name) || cfg.subcommandTools.has(name) || cfg.readOnlyConsumers.has(name)) return []
|
|
982
|
+
// `prefix` is omitted entirely (rather than set to `[]`) when nothing was
|
|
983
|
+
// skipped, so every existing `toEqual({kind:'command', name, args})`
|
|
984
|
+
// fixture — none of which name a wrapper or leading assignment — keeps
|
|
985
|
+
// matching exactly; only an invocation that actually had one gains the
|
|
986
|
+
// extra field.
|
|
987
|
+
const prefix = tokens.slice(0, index)
|
|
988
|
+
const command: Invocation = prefix.length > 0 ? { kind: 'command', name, args, prefix } : { kind: 'command', name, args }
|
|
989
|
+
return [command, ...walkNested(tokens, index, args, cfg, walk)]
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const feedFor = (
|
|
993
|
+
feeds: readonly PieceFeed[],
|
|
994
|
+
pieces: readonly (readonly Token[])[],
|
|
995
|
+
texts: readonly string[],
|
|
996
|
+
index: number,
|
|
997
|
+
): StdinFeed | undefined => {
|
|
998
|
+
if (feeds[index] !== 'piece' && feeds[index] !== 'group') return undefined
|
|
999
|
+
let start = index
|
|
1000
|
+
while (start > 0 && feeds[start] === 'piece') start -= 1
|
|
1001
|
+
const upstream = pieces.slice(start, index)
|
|
1002
|
+
const groupUpstream = feeds[start] === 'group' ? pieces.slice(0, start) : []
|
|
1003
|
+
const groupAfterHereText = groupUpstream.some((tokens) => readsHereText(detachShellInput(tokens, documents)))
|
|
1004
|
+
return { upstream, upstreamText: texts.slice(start, index), groupAfterHereText, groupUpstream }
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* `outerFeed` is a `-c` script's inherited stdin (the outer pipe): it
|
|
1009
|
+
* applies only to the script's own first top-level piece, when that piece
|
|
1010
|
+
* isn't already fed by a pipe/group inside the script itself — a `-c`
|
|
1011
|
+
* script's first stage reads the invoking process's stdin exactly like a
|
|
1012
|
+
* bare piped shell would (`producer | outer -c 'bash'` reads `producer`'s
|
|
1013
|
+
* output as the SCRIPT `bash` then reads, same as `producer | bash`).
|
|
1014
|
+
* Nested `$(...)`/backtick sources never inherit it.
|
|
1015
|
+
*/
|
|
1016
|
+
const parse = (command: string, outerFeed?: StdinFeed): readonly Invocation[] => {
|
|
1017
|
+
const offset = documents.length
|
|
1018
|
+
const scan = scanHereDocuments(command, (n) => `<<${hereDocumentPlaceholder(offset + n)} `)
|
|
1019
|
+
if (scan.invalid) return uncertain(scan.reason ?? 'unparsed here-document')
|
|
1020
|
+
documents.push(...scan.documents)
|
|
1021
|
+
|
|
1022
|
+
const nested = nestedSources(scan.text)
|
|
1023
|
+
if (nested.invalid) return uncertain(nested.reason ?? 'unclosed shell construct')
|
|
1024
|
+
const found: Invocation[] = []
|
|
1025
|
+
for (const source of nested.sources) found.push(...parse(source))
|
|
1026
|
+
|
|
1027
|
+
const split = splitShell(scan.text)
|
|
1028
|
+
if (split.invalid) return uncertain(split.reason ?? 'unparsed shell input')
|
|
1029
|
+
const pieces: (readonly Token[])[] = []
|
|
1030
|
+
for (const piece of split.pieces) {
|
|
1031
|
+
const tokenized = tokenize(piece)
|
|
1032
|
+
if (tokenized.invalid) return uncertain('unparsed shell token')
|
|
1033
|
+
pieces.push(tokenized.tokens)
|
|
1034
|
+
}
|
|
1035
|
+
pieces.forEach((tokens, index) => {
|
|
1036
|
+
const localFeed = feedFor(split.feeds, pieces, split.pieces, index)
|
|
1037
|
+
found.push(...walk(tokens, localFeed ?? (index === 0 ? outerFeed : undefined)))
|
|
1038
|
+
})
|
|
1039
|
+
return found
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const parseScript = (script: string, feed?: StdinFeed): readonly Invocation[] => {
|
|
1043
|
+
if (depth >= MAX_SHELL_SCRIPT_DEPTH) return uncertain('shell input nested too deeply')
|
|
1044
|
+
depth += 1
|
|
1045
|
+
try {
|
|
1046
|
+
return parse(script, feed)
|
|
1047
|
+
} finally {
|
|
1048
|
+
depth -= 1
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const parser = { walk, parseScript, parse }
|
|
1053
|
+
return parser
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/** Parse and classify a raw shell command line into zero or more Invocations. */
|
|
1057
|
+
export function invocations(command: string, cfg: ClassifyConfig = DEFAULT_CLASSIFY_CONFIG): readonly Invocation[] {
|
|
1058
|
+
return makeParser(cfg).parse(command)
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* The first argument that reaches into one of `cfg.deniedTrees`, or null.
|
|
1063
|
+
*
|
|
1064
|
+
* Matches a tree EXACTLY or as a parent directory — `<tree>` and `<tree>/...`,
|
|
1065
|
+
* never `<tree>-m5`. A clone whose name merely starts with the denied one is a
|
|
1066
|
+
* different repository and stays usable; that boundary is the whole point.
|
|
1067
|
+
*/
|
|
1068
|
+
function deniedPathInvocation(
|
|
1069
|
+
name: string,
|
|
1070
|
+
args: readonly Token[],
|
|
1071
|
+
cfg: ClassifyConfig,
|
|
1072
|
+
): Invocation | null {
|
|
1073
|
+
if (cfg.deniedTrees.length === 0) return null
|
|
1074
|
+
for (const token of args) {
|
|
1075
|
+
const raw = tokenValue(token)
|
|
1076
|
+
if (raw.startsWith('-')) continue
|
|
1077
|
+
const path = raw.replace(/\/+$/, '')
|
|
1078
|
+
for (const tree of cfg.deniedTrees) {
|
|
1079
|
+
if (path === tree || path.startsWith(`${tree}/`)) return { kind: 'denied-path', name, path: raw }
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return null
|
|
1083
|
+
}
|