@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,1415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carrying the session identity (`ADL_AGENT_ID_AGENT`) into every network git
|
|
3
|
+
* invocation of a command-executing tool call.
|
|
4
|
+
*
|
|
5
|
+
* Live gap this closes (2026-09-28, codex session 01a0e611): an
|
|
6
|
+
* `adl run -- codex` session ran `git push -u origin feat/adl-m3-finish`
|
|
7
|
+
* through the lean-ctx MCP tool `mcp__lean_ctx__ctx_shell`; Codex gives
|
|
8
|
+
* `ADL_AGENT_ID_AGENT` only to its own shell tool, so the push executed with
|
|
9
|
+
* no identity and `bin/git-ado`'s HUMAN pass-through (bin/git-ado:191-194)
|
|
10
|
+
* pushed with Tarun's own credential helper. The hook process had the
|
|
11
|
+
* identity all along — it arrives in the hook's own environment, which `adl
|
|
12
|
+
* run` sets — but had no way to put it on the command. This scanner is that
|
|
13
|
+
* way: `git push …` becomes `ADL_AGENT_ID_AGENT=agent-tarun git push …`, and
|
|
14
|
+
* the pre-tool-use handler returns the rewritten command as the host's native
|
|
15
|
+
* "replace the tool input" envelope (`updatedInput` on Claude/Codex,
|
|
16
|
+
* `updated_input` on Cursor). On hosts with no such envelope the handler
|
|
17
|
+
* denies instead — see impl/adl-git-identity.ts.
|
|
18
|
+
*
|
|
19
|
+
* The verb list (`NETWORK_GIT_VERBS`) started as exactly `bin/git-ado`'s
|
|
20
|
+
* credential path (`fetch|pull|clone|ls-remote|push`, bin/git-ado:159-165):
|
|
21
|
+
* those are the commands for which a missing identity silently becomes the
|
|
22
|
+
* human. F2 (independent review, 2026-09-29) widened what this file (and
|
|
23
|
+
* `impl/adl-session-required.ts`'s deny side) actually TREATS as network —
|
|
24
|
+
* see `isNetworkGitInvocation`'s own doc below for the additional shapes
|
|
25
|
+
* (`git remote show|prune|update`/`set-head -a`, `git maintenance run
|
|
26
|
+
* --task=prefetch` specifically — not a bare `git maintenance run`, aligned
|
|
27
|
+
* with `bin/git-ado`'s own narrower routing there, another executor's
|
|
28
|
+
* surface — the transport helpers, `git submodule update|sync --recursive`,
|
|
29
|
+
* `git archive --remote`) — this claim about the bare five-verb
|
|
30
|
+
* `NETWORK_GIT_VERBS` set is no longer the WHOLE story for what this file
|
|
31
|
+
* classifies as network, only for what `bin/git-ado`'s OWN credential path
|
|
32
|
+
* covers today. Reads and local operations (`status`/`log`/`diff`/…) never
|
|
33
|
+
* touch git-ado's identity branch and are deliberately left untouched,
|
|
34
|
+
* idempotency included — a command already prefixed with the SAME identity
|
|
35
|
+
* is returned unchanged, and one prefixed with a DIFFERENT (or emptied)
|
|
36
|
+
* identity is a deny, never a
|
|
37
|
+
* rewrite, because the override would either spoof attribution or launder the
|
|
38
|
+
* command back onto the human path.
|
|
39
|
+
*
|
|
40
|
+
* The scanner is offset-aware and mirrors the shell front end's semantics
|
|
41
|
+
* (`./split.ts`, `./token.ts`, `./prefixes.ts`) for the constructs that
|
|
42
|
+
* matter: quotes, backslash escapes, `$(…)`/`${…}`/backtick/`<(…)` spans,
|
|
43
|
+
* `#` comments, IO operators, `&&`/`||`/`;`/`|`/`&`/newline/paren/brace
|
|
44
|
+
* separators, leading assignments, wrapper commands and git's own global
|
|
45
|
+
* flags. Nested `bash -c '<script>'` script strings are scanned one level at
|
|
46
|
+
* a time (depth-capped), the same shapes `./invocation.ts` walks for the deny
|
|
47
|
+
* rules; a script FILE operand (`sh deploy.sh`) is not on this command line
|
|
48
|
+
* and is not inspected. `sudo`/`doas` are denied rather than rewritten:
|
|
49
|
+
* they strip the environment, so a prefixed identity would silently not
|
|
50
|
+
* survive to git.
|
|
51
|
+
*
|
|
52
|
+
* The identity is validated against `^[A-Za-z0-9_-]+$` before it ever enters
|
|
53
|
+
* shell text — an identity outside that set means the session itself is
|
|
54
|
+
* misconfigured, and injecting it would be code execution.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import { findMatchingBacktick, findMatchingParen } from './substitution.ts'
|
|
58
|
+
import { GIT_GLOBAL_EQ, GIT_GLOBAL_TAKES_ARG, GIT_TERMINAL_GLOBALS } from './prefixes.ts'
|
|
59
|
+
import { ADL_AGENT_ID_ENV_VAR, isValidAgent } from '../impl/adl-identity.ts'
|
|
60
|
+
import {
|
|
61
|
+
ADL_AGENT_ID_BINARY_NAMES,
|
|
62
|
+
ADL_AGENT_ID_COMMAND_SHAPES,
|
|
63
|
+
ADL_BINARY_NAMES,
|
|
64
|
+
ADL_IDENTITY_COMMAND_SHAPES,
|
|
65
|
+
HTTP_CLIENT_BINARY_NAMES,
|
|
66
|
+
isAdoUrl,
|
|
67
|
+
matchesIdentityShape,
|
|
68
|
+
skipLeadingDashFlags,
|
|
69
|
+
} from '../impl/git-helpers.ts'
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The five network verbs `bin/git-ado` routes through the agent identity /
|
|
73
|
+
* human pass-through decision. Only these ever need the identity on the
|
|
74
|
+
* command line.
|
|
75
|
+
*/
|
|
76
|
+
export const NETWORK_GIT_VERBS: ReadonlySet<string> = new Set(['fetch', 'pull', 'clone', 'ls-remote', 'push'])
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* F2 (independent review, 2026-09-29): network-reaching git shapes the
|
|
80
|
+
* walker missed entirely — subcommands other than the five `NETWORK_GIT_VERBS`
|
|
81
|
+
* above, or ones where only a SPECIFIC sub-action/flag combination reaches
|
|
82
|
+
* the network (an innocuous sibling form of the same subcommand must NOT be
|
|
83
|
+
* swept in): `git remote show|prune|update <remote>` (each contacts the
|
|
84
|
+
* remote; `git remote -v`/`add`/`rename`/`remove` never do), `git remote
|
|
85
|
+
* set-head <r> -a` (auto-detects HEAD over the network; `set-head <r>
|
|
86
|
+
* <branch>` with an explicit branch does not), `git maintenance run
|
|
87
|
+
* --task=prefetch` (fetches every registered remote in the background;
|
|
88
|
+
* `--task=gc`/`commit-graph`/… do not), the internal transport helpers
|
|
89
|
+
* `git remote-https`/`git remote-http`/`git http-fetch` (network by
|
|
90
|
+
* construction when invoked directly), `git submodule update|sync
|
|
91
|
+
* --recursive` (recurses into and can fetch nested submodule remotes;
|
|
92
|
+
* without `--recursive` it only touches the top-level submodules already
|
|
93
|
+
* configured), and `git archive --remote[=<repo>]` (fetches the archive
|
|
94
|
+
* data from a remote repository over the git protocol; a bare `git archive`
|
|
95
|
+
* only reads the local tree). `git fetch` (and the other four) via a leading
|
|
96
|
+
* `git -c ...` global is already covered without any change here —
|
|
97
|
+
* `scanGitGlobals`/`scanGitGlobalsValues` (`./prefixes.ts`) already skip
|
|
98
|
+
* every global flag, `-c` included, before either call site below ever
|
|
99
|
+
* inspects the subcommand word.
|
|
100
|
+
*
|
|
101
|
+
* Used by BOTH sides `adl.session-required-for-writes`'s own header names
|
|
102
|
+
* ("deny with no identity / rewrite with one"): `identityBearingReason`
|
|
103
|
+
* (`impl/adl-session-required.ts`) for the deny side, and the REWRITE
|
|
104
|
+
* scanner's own `NETWORK_GIT_VERBS` gate below for the rewrite side — one
|
|
105
|
+
* shared predicate, not two independently-maintained shape lists.
|
|
106
|
+
*/
|
|
107
|
+
const REMOTE_NETWORK_ACTIONS: ReadonlySet<string> = new Set(['show', 'prune', 'update'])
|
|
108
|
+
const SUBMODULE_RECURSIVE_ACTIONS: ReadonlySet<string> = new Set(['update', 'sync'])
|
|
109
|
+
const REMOTE_TRANSPORT_HELPERS: ReadonlySet<string> = new Set(['remote-https', 'remote-http', 'http-fetch'])
|
|
110
|
+
|
|
111
|
+
function firstNonOptionValue(values: readonly string[]): string | undefined {
|
|
112
|
+
return values.find((value) => !value.startsWith('-'))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isNetworkGitInvocation(subcommand: string, restValues: readonly string[]): boolean {
|
|
116
|
+
if (NETWORK_GIT_VERBS.has(subcommand)) return true
|
|
117
|
+
if (REMOTE_TRANSPORT_HELPERS.has(subcommand)) return true
|
|
118
|
+
if (subcommand === 'remote') {
|
|
119
|
+
const action = firstNonOptionValue(restValues)?.toLowerCase()
|
|
120
|
+
if (action === undefined) return false
|
|
121
|
+
if (REMOTE_NETWORK_ACTIONS.has(action)) return true
|
|
122
|
+
if (action === 'set-head') return restValues.some((value) => value === '-a' || value === '--auto')
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
if (subcommand === 'maintenance') {
|
|
126
|
+
const action = firstNonOptionValue(restValues)?.toLowerCase()
|
|
127
|
+
if (action !== 'run') return false
|
|
128
|
+
return restValues.some((value, index) => value === '--task=prefetch' || (value === '--task' && restValues[index + 1] === 'prefetch'))
|
|
129
|
+
}
|
|
130
|
+
if (subcommand === 'submodule') {
|
|
131
|
+
const action = firstNonOptionValue(restValues)?.toLowerCase()
|
|
132
|
+
if (action === undefined || !SUBMODULE_RECURSIVE_ACTIONS.has(action)) return false
|
|
133
|
+
// P2 (independent review, 2026-09-29): `--init` clones every submodule
|
|
134
|
+
// not yet checked out — network, same as `--recursive` recursing into
|
|
135
|
+
// nested ones.
|
|
136
|
+
return restValues.some((value) => value === '--recursive' || value === '--init')
|
|
137
|
+
}
|
|
138
|
+
if (subcommand === 'archive') {
|
|
139
|
+
return restValues.some((value) => value === '--remote' || value.startsWith('--remote='))
|
|
140
|
+
}
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const SAFE_IDENTITY = /^[A-Za-z0-9_-]+$/
|
|
145
|
+
|
|
146
|
+
/** Shells whose `-c`/`--command` script string is re-scanned one level deeper (mirrors invocation.ts's shellNames). */
|
|
147
|
+
const SHELL_NAMES: ReadonlySet<string> = new Set(['sh', 'bash', 'zsh', 'dash', 'ksh', 'fish'])
|
|
148
|
+
|
|
149
|
+
const MAX_NESTED_SCRIPT_DEPTH = 16
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* `identity-mismatch` covers BOTH shapes the standard treats the same way
|
|
153
|
+
* (STATUS.md 12p, "Decision per identity-bearing
|
|
154
|
+
* shell command"): an inline `ADL_AGENT_ID_AGENT=<value>` that is empty
|
|
155
|
+
* (would launder the command back onto git-ado's human pass-through) or
|
|
156
|
+
* names an agent outside `VALID_AGENTS`. A VALID inline value — even one
|
|
157
|
+
* that differs from the resolved fallback — is no longer a conflict at all:
|
|
158
|
+
* the override wins and the command is left unchanged (`kind: 'unchanged'`).
|
|
159
|
+
* See `applyIdentityOutcome` below.
|
|
160
|
+
*/
|
|
161
|
+
/**
|
|
162
|
+
* `network-alias-redefinition` (H2, STATUS.md 12p):
|
|
163
|
+
* a self-referential `git -c alias.<name>=<value>`/`--config-env[=]
|
|
164
|
+
* alias.<name>=<value>` where `<name>` is the literal subcommand this
|
|
165
|
+
* invocation runs and `<value>`'s own verb is network/credential-bearing —
|
|
166
|
+
* see `networkAliasRedefinition`'s own doc.
|
|
167
|
+
*/
|
|
168
|
+
/**
|
|
169
|
+
* `dynamic-agent` (M5, STATUS.md 12p): `adl id
|
|
170
|
+
* token <agent>`/`adl-agent-id exec --agent <agent>` whose agent argument
|
|
171
|
+
* is itself dynamic ($-expansion, backtick, or a glob character) — denied
|
|
172
|
+
* outright rather than silently treated as "not identity-bearing", since a
|
|
173
|
+
* dynamically computed agent name could name ANY agent at runtime, evading
|
|
174
|
+
* `adl.token-minting-role`'s static "own identity only" check entirely.
|
|
175
|
+
*/
|
|
176
|
+
export type GitIdentityDenyReason =
|
|
177
|
+
| 'identity-mismatch'
|
|
178
|
+
| 'unrewritable-wrapper'
|
|
179
|
+
| 'unsafe-identity'
|
|
180
|
+
| 'network-alias-redefinition'
|
|
181
|
+
| 'dynamic-agent'
|
|
182
|
+
|
|
183
|
+
export type GitIdentityScan =
|
|
184
|
+
| { readonly kind: 'rewrite'; readonly command: string }
|
|
185
|
+
| { readonly kind: 'unchanged' }
|
|
186
|
+
/**
|
|
187
|
+
* An identity-bearing simple command was found, it carries no (valid)
|
|
188
|
+
* inline override, and no fallback identity was resolved (the `fallback`
|
|
189
|
+
* argument was `null`). Nothing was rewritten or denied here — this is a
|
|
190
|
+
* signal for the CALLER (`adl-session-required.ts`) to deny with the
|
|
191
|
+
* standard's own message; this scanner's job is only to rewrite/detect
|
|
192
|
+
* conflicts once an identity — inline or fallback — actually exists.
|
|
193
|
+
*/
|
|
194
|
+
| { readonly kind: 'needs-identity' }
|
|
195
|
+
| { readonly kind: 'deny'; readonly reason: GitIdentityDenyReason }
|
|
196
|
+
|
|
197
|
+
/** One scanned word: `value` is the dequoted shell word (token.ts semantics), `start`/`end` its raw span (end exclusive) in the original command. */
|
|
198
|
+
interface Word {
|
|
199
|
+
readonly value: string
|
|
200
|
+
readonly start: number
|
|
201
|
+
readonly end: number
|
|
202
|
+
readonly operator: boolean
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface Piece {
|
|
206
|
+
readonly words: readonly Word[]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Longest-match-first IO operators, so `>&` wins over `>` and `&>>` over `&>`. `|`/`&` alone are separators, never operators. */
|
|
210
|
+
const IO_OPERATORS = ['&>>', '<<<', '<<-', '&>', '>&', '>|', '<&', '>>', '<<', '<>', '<', '>'] as const
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Longest IO operator at `text[index]`, or the bare character when none of
|
|
214
|
+
* the table matches. Every scanPieces call site guarantees `<`, `>`, `&>` or
|
|
215
|
+
* `&>>`, so the fallback is a defensive contract pin, exercised directly in
|
|
216
|
+
* tests/unit/shell-git-identity.test.ts.
|
|
217
|
+
*/
|
|
218
|
+
export function matchIoOperator(text: string, index: number): string {
|
|
219
|
+
for (const op of IO_OPERATORS) {
|
|
220
|
+
if (text.startsWith(op, index)) return op
|
|
221
|
+
}
|
|
222
|
+
return text[index] as string
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** End index (inclusive) of a `$(…)`, `${…}`, backtick or `<(`/`>(` span starting at `index`, or null — port of token.ts's expansionEnd. */
|
|
226
|
+
function expansionEnd(text: string, index: number): number | null {
|
|
227
|
+
const char = text[index]
|
|
228
|
+
const next = text[index + 1]
|
|
229
|
+
if ((char === '$' || char === '<' || char === '>') && next === '(') {
|
|
230
|
+
const close = findMatchingParen(text, index + 1)
|
|
231
|
+
return close < 0 ? text.length - 1 : close
|
|
232
|
+
}
|
|
233
|
+
if (char === '$' && next === '{') {
|
|
234
|
+
const close = text.indexOf('}', index + 2)
|
|
235
|
+
return close < 0 ? text.length - 1 : close
|
|
236
|
+
}
|
|
237
|
+
if (char === '`') {
|
|
238
|
+
const close = findMatchingBacktick(text, index)
|
|
239
|
+
return close < 0 ? text.length - 1 : close
|
|
240
|
+
}
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isAssignmentValue(value: string): boolean {
|
|
245
|
+
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(value)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Lower-cased final path segment of a word value — port of token.ts's basename. */
|
|
249
|
+
function basenameValue(value: string): string {
|
|
250
|
+
const segments = value.split(/[/\\]/)
|
|
251
|
+
return (segments[segments.length - 1] ?? '').toLowerCase()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Port of token.ts's dynamicToken, on dequoted values. */
|
|
255
|
+
function dynamicValue(value: string): boolean {
|
|
256
|
+
return value.includes('$') || value.includes('`') || /[*?]/.test(value)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Port of git-helpers.ts's isHelpOnlyInvocation, on dequoted values. */
|
|
260
|
+
function valuesHelpOnly(values: readonly string[]): boolean {
|
|
261
|
+
const flags = new Set<string>()
|
|
262
|
+
for (const arg of values) {
|
|
263
|
+
if (arg === '--') break
|
|
264
|
+
if (arg === '--help' || arg === '--version') flags.add('help')
|
|
265
|
+
if (arg === '-h' && values.length === 1) flags.add('help')
|
|
266
|
+
}
|
|
267
|
+
return flags.has('help') && values.every((arg) => arg.startsWith('-') && arg !== '--')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The first `http(s)://` value among `values` — port of git-helpers.ts's `classifyGitPushDestination`'s private `firstUrl`, on dequoted string values instead of Tokens. */
|
|
271
|
+
function firstHttpUrl(values: readonly string[]): string | null {
|
|
272
|
+
for (const value of values) {
|
|
273
|
+
if (/^https?:\/\//i.test(value)) return value
|
|
274
|
+
}
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Split `text` (a command, or a nested script string) into pieces at unquoted
|
|
280
|
+
* `&&`/`||`/`;`/`|`/`&`/newlines and group boundaries, tokenizing each piece
|
|
281
|
+
* into words with raw source spans offset by `base` in the original command.
|
|
282
|
+
* Mirrors split.ts's structure handling (quote-aware, comment-aware,
|
|
283
|
+
* substitution-opaque, `>&`/`&>` folding) closely enough for the git-identity
|
|
284
|
+
* analysis; it does not need split.ts's pipe feeds.
|
|
285
|
+
*/
|
|
286
|
+
function scanPieces(text: string, base: number, pieces: Piece[]): void {
|
|
287
|
+
const words: Word[] = []
|
|
288
|
+
let value = ''
|
|
289
|
+
let start = -1
|
|
290
|
+
let quote: '"' | "'" | null = null
|
|
291
|
+
let wordStart = true
|
|
292
|
+
|
|
293
|
+
const flush = (endLocal: number): void => {
|
|
294
|
+
if (start >= 0) {
|
|
295
|
+
words.push({ value, start: base + start, end: base + endLocal, operator: false })
|
|
296
|
+
value = ''
|
|
297
|
+
start = -1
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const endPiece = (endLocal: number): void => {
|
|
301
|
+
flush(endLocal)
|
|
302
|
+
if (words.length > 0) pieces.push({ words: [...words] })
|
|
303
|
+
words.length = 0
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
307
|
+
const char = text[index] as string
|
|
308
|
+
|
|
309
|
+
if (quote === "'") {
|
|
310
|
+
if (char === "'") quote = null
|
|
311
|
+
else value += char
|
|
312
|
+
continue
|
|
313
|
+
}
|
|
314
|
+
if (char === '\\') {
|
|
315
|
+
// Unquoted or inside double quotes (token.ts escapes any next char in both).
|
|
316
|
+
if (start < 0) start = index
|
|
317
|
+
if (index + 1 >= text.length) return // unterminated escape: leave the partial word unflushed; the command fails closed upstream
|
|
318
|
+
value += text[index + 1]
|
|
319
|
+
index += 1
|
|
320
|
+
wordStart = false
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
if (quote === '"') {
|
|
324
|
+
if (char === '"') {
|
|
325
|
+
quote = null
|
|
326
|
+
continue
|
|
327
|
+
}
|
|
328
|
+
const end = char === '$' || char === '`' ? expansionEnd(text, index) : null
|
|
329
|
+
if (end !== null) {
|
|
330
|
+
if (start < 0) start = index
|
|
331
|
+
value += text.slice(index, end + 1)
|
|
332
|
+
index = end
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
value += char
|
|
336
|
+
continue
|
|
337
|
+
}
|
|
338
|
+
if (char === "'" || char === '"') {
|
|
339
|
+
if (start < 0) start = index
|
|
340
|
+
quote = char
|
|
341
|
+
wordStart = false
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const expansion = expansionEnd(text, index)
|
|
346
|
+
if (expansion !== null) {
|
|
347
|
+
if (start < 0) start = index
|
|
348
|
+
value += text.slice(index, expansion + 1)
|
|
349
|
+
index = expansion
|
|
350
|
+
wordStart = false
|
|
351
|
+
continue
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (char === '\n' || char === ';') {
|
|
355
|
+
flush(index)
|
|
356
|
+
endPiece(index)
|
|
357
|
+
wordStart = true
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (/\s/.test(char)) {
|
|
362
|
+
flush(index)
|
|
363
|
+
wordStart = true
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (char === '#' && wordStart) {
|
|
368
|
+
flush(index)
|
|
369
|
+
let end = index
|
|
370
|
+
while (end + 1 < text.length && text[end + 1] !== '\n') end += 1
|
|
371
|
+
index = end
|
|
372
|
+
wordStart = true
|
|
373
|
+
continue
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (char === '&') {
|
|
377
|
+
if (text[index + 1] === '&') {
|
|
378
|
+
flush(index)
|
|
379
|
+
endPiece(index)
|
|
380
|
+
index += 1
|
|
381
|
+
wordStart = true
|
|
382
|
+
continue
|
|
383
|
+
}
|
|
384
|
+
if (text[index + 1] === '>') {
|
|
385
|
+
flush(index)
|
|
386
|
+
const op = matchIoOperator(text, index)
|
|
387
|
+
words.push({ value: op, start: base + index, end: base + index + op.length, operator: true })
|
|
388
|
+
index += op.length - 1
|
|
389
|
+
wordStart = false
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
// A bare `&` backgrounds the preceding command.
|
|
393
|
+
flush(index)
|
|
394
|
+
endPiece(index)
|
|
395
|
+
wordStart = true
|
|
396
|
+
continue
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (char === '|') {
|
|
400
|
+
if (text[index + 1] === '|') {
|
|
401
|
+
flush(index)
|
|
402
|
+
endPiece(index)
|
|
403
|
+
index += 1
|
|
404
|
+
} else {
|
|
405
|
+
flush(index)
|
|
406
|
+
endPiece(index)
|
|
407
|
+
}
|
|
408
|
+
wordStart = true
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const groupBrace =
|
|
413
|
+
(char === '{' && (index + 1 >= text.length || /\s/.test(text[index + 1] as string))) ||
|
|
414
|
+
(char === '}' && (index === 0 || /[\s;]/.test(text[index - 1] as string)))
|
|
415
|
+
if (char === '(' || char === ')' || groupBrace) {
|
|
416
|
+
flush(index)
|
|
417
|
+
endPiece(index)
|
|
418
|
+
wordStart = true
|
|
419
|
+
continue
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (char === '<' || char === '>') {
|
|
423
|
+
flush(index)
|
|
424
|
+
const op = matchIoOperator(text, index)
|
|
425
|
+
words.push({ value: op, start: base + index, end: base + index + op.length, operator: true })
|
|
426
|
+
index += op.length - 1
|
|
427
|
+
wordStart = false
|
|
428
|
+
continue
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (start < 0) start = index
|
|
432
|
+
value += char
|
|
433
|
+
wordStart = false
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
endPiece(text.length)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Port of prefixes.ts's skipDashOptions, on words (operators end the option run). */
|
|
440
|
+
function skipDashOptions(words: readonly Word[], start: number, valueOptions: readonly string[], stopAtDoubleDash: boolean): number {
|
|
441
|
+
let index = start
|
|
442
|
+
while (index < words.length) {
|
|
443
|
+
const word = words[index] as Word
|
|
444
|
+
if (word.operator) break
|
|
445
|
+
const value = word.value
|
|
446
|
+
if (stopAtDoubleDash && value === '--') return index + 1
|
|
447
|
+
if (!value.startsWith('-') || value === '-') break
|
|
448
|
+
index += valueOptions.includes(value) ? 2 : 1
|
|
449
|
+
}
|
|
450
|
+
return index
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Port of prefixes.ts's skipWrapperFlags, on words. */
|
|
454
|
+
function skipWrapperFlagArgs(words: readonly Word[], start: number, withValue: readonly string[]): number {
|
|
455
|
+
let index = start
|
|
456
|
+
while (index < words.length) {
|
|
457
|
+
const word = words[index] as Word
|
|
458
|
+
if (word.operator) break
|
|
459
|
+
const value = word.value
|
|
460
|
+
if (value === '--') return index + 1
|
|
461
|
+
if (withValue.includes(value)) {
|
|
462
|
+
index += 2
|
|
463
|
+
continue
|
|
464
|
+
}
|
|
465
|
+
if (value.startsWith('-') && value !== '-') {
|
|
466
|
+
index += 1
|
|
467
|
+
continue
|
|
468
|
+
}
|
|
469
|
+
break
|
|
470
|
+
}
|
|
471
|
+
return index
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** One wrapper's flag scan: the index past its own flags, and whether it strips the environment (H1). */
|
|
475
|
+
interface WrapperScan {
|
|
476
|
+
readonly index: number
|
|
477
|
+
readonly strips: boolean
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Port of prefixes.ts's skipEnvFlags, on words; collects the env command's
|
|
482
|
+
* `NAME=value` arguments for identity-conflict checks, and reports whether
|
|
483
|
+
* `-u`/`--unset[=NAME]` or `-i`/`--ignore-environment` were used (H1).
|
|
484
|
+
*
|
|
485
|
+
* `-u`/`--unset` denies regardless of WHICH name it names — not only
|
|
486
|
+
* `ADL_AGENT_ID_AGENT` itself — the same blanket treatment `sudo`/`doas` get
|
|
487
|
+
* below even though `sudo -E` would actually preserve the environment: this
|
|
488
|
+
* scanner does not try to prove a specific unset is safe, it treats the
|
|
489
|
+
* FLAG as the risk, matching the existing sudo/doas precedent rather than
|
|
490
|
+
* adding a second, narrower standard only env gets.
|
|
491
|
+
*/
|
|
492
|
+
function skipEnvArgs(words: readonly Word[], start: number, assignments: string[]): WrapperScan {
|
|
493
|
+
let index = start
|
|
494
|
+
let strips = false
|
|
495
|
+
while (index < words.length) {
|
|
496
|
+
const word = words[index] as Word
|
|
497
|
+
if (word.operator) break
|
|
498
|
+
const value = word.value
|
|
499
|
+
if (value === '--') return { index: index + 1, strips }
|
|
500
|
+
if (value === '-u' || value === '--unset') {
|
|
501
|
+
strips = true
|
|
502
|
+
index += 2
|
|
503
|
+
continue
|
|
504
|
+
}
|
|
505
|
+
if (value === '-i' || value === '--ignore-environment') {
|
|
506
|
+
strips = true
|
|
507
|
+
index += 1
|
|
508
|
+
continue
|
|
509
|
+
}
|
|
510
|
+
if (['-C', '--chdir', '-S', '--split-string'].includes(value)) {
|
|
511
|
+
index += 2
|
|
512
|
+
continue
|
|
513
|
+
}
|
|
514
|
+
if (value.startsWith('--unset=')) {
|
|
515
|
+
strips = true
|
|
516
|
+
index += 1
|
|
517
|
+
continue
|
|
518
|
+
}
|
|
519
|
+
if (value.startsWith('--chdir=') || value.startsWith('-') || value.includes('=')) {
|
|
520
|
+
if (isAssignmentValue(value)) assignments.push(value)
|
|
521
|
+
index += 1
|
|
522
|
+
continue
|
|
523
|
+
}
|
|
524
|
+
break
|
|
525
|
+
}
|
|
526
|
+
return { index, strips }
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* `exec`'s `-c`/`-l` flags run the wrapped command with an empty environment
|
|
531
|
+
* (`-c`) or as a login shell that can re-source profiles which reset it
|
|
532
|
+
* (`-l`) — H1 treats both as environment-stripping, like sudo/doas. `-a
|
|
533
|
+
* name` (argv0 only) never strips and is skipped transparently. Bash's own
|
|
534
|
+
* `exec` builtin accepts these bundled (`-cl`, `-la name`, …); any bundle
|
|
535
|
+
* containing `c` or `l` is treated as stripping, fail-closed, rather than
|
|
536
|
+
* trying to prove a particular bundling is safe.
|
|
537
|
+
*/
|
|
538
|
+
function skipExecArgs(words: readonly Word[], start: number): WrapperScan {
|
|
539
|
+
let index = start
|
|
540
|
+
let strips = false
|
|
541
|
+
for (;;) {
|
|
542
|
+
const word = words[index]
|
|
543
|
+
if (word === undefined || word.operator) break
|
|
544
|
+
const value = word.value
|
|
545
|
+
if (value === '--') {
|
|
546
|
+
index += 1
|
|
547
|
+
break
|
|
548
|
+
}
|
|
549
|
+
if (/^-[acl]+$/.test(value)) {
|
|
550
|
+
if (value.includes('c') || value.includes('l')) strips = true
|
|
551
|
+
index += value.includes('a') ? 2 : 1
|
|
552
|
+
continue
|
|
553
|
+
}
|
|
554
|
+
break
|
|
555
|
+
}
|
|
556
|
+
return { index, strips }
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** Port of prefixes.ts's skipCommandFlags + skipRepeatedTokens(['--','-p']). */
|
|
560
|
+
function skipCommandArgs(words: readonly Word[], start: number): number {
|
|
561
|
+
const first = words[start]
|
|
562
|
+
if (first !== undefined && !first.operator && ['-v', '-V'].includes(first.value)) return words.length
|
|
563
|
+
let index = start
|
|
564
|
+
while (index < words.length) {
|
|
565
|
+
const word = words[index] as Word
|
|
566
|
+
if (word.operator || !['--', '-p'].includes(word.value)) break
|
|
567
|
+
index += 1
|
|
568
|
+
}
|
|
569
|
+
return index
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Port of prefixes.ts's skipTimeoutFlags (wrapper flags, then the one duration operand). */
|
|
573
|
+
function skipTimeoutArgs(words: readonly Word[], start: number): number {
|
|
574
|
+
const index = skipWrapperFlagArgs(words, start, ['-s', '--signal', '-k', '--kill-after'])
|
|
575
|
+
return index < words.length ? index + 1 : index
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Wrapper commands whose environment passes through to the wrapped command —
|
|
580
|
+
* port of prefixes.ts's COMMAND_PREFIXES/PREFIX_HANDLERS. Each value skips the
|
|
581
|
+
* wrapper's own flags/operands and lands on the wrapped command; none of
|
|
582
|
+
* these ever strip the environment. `env` and `exec` are NOT here — H1
|
|
583
|
+
* treats their environment-stripping flags (`env -u`/`--unset`/`-i`, `exec
|
|
584
|
+
* -c`/`-l`) like sudo/doas, so `analyzePiece` special-cases them the same
|
|
585
|
+
* way it special-cases sudo/doas, rather than folding a `strips` result into
|
|
586
|
+
* every entry in this map for the sake of two wrappers that need it.
|
|
587
|
+
*/
|
|
588
|
+
const WRAPPER_SKIP: Readonly<Record<string, (words: readonly Word[], start: number, assignments: string[]) => number>> = {
|
|
589
|
+
command: (words, start) => skipCommandArgs(words, start),
|
|
590
|
+
nice: (words, start) => skipDashOptions(words, start, ['-n', '--adjustment'], false),
|
|
591
|
+
time: (words, start) => skipDashOptions(words, start, ['-o', '--output'], false),
|
|
592
|
+
timeout: skipTimeoutArgs,
|
|
593
|
+
gtimeout: skipTimeoutArgs,
|
|
594
|
+
stdbuf: (words, start) => skipWrapperFlagArgs(words, start, ['-i', '-o', '-e']),
|
|
595
|
+
caffeinate: (words, start) => skipWrapperFlagArgs(words, start, ['-t', '-w']),
|
|
596
|
+
arch: (words, start) => skipWrapperFlagArgs(words, start, ['-arch']),
|
|
597
|
+
setsid: (words, start) => skipWrapperFlagArgs(words, start, []),
|
|
598
|
+
nohup: (words, start) => skipDashOptions(words, start, [], true),
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Port of prefixes.ts's scanGitGlobals, on words; operators end the global scan. */
|
|
602
|
+
function scanGitGlobalsValues(words: readonly Word[], start: number): { readonly index: number; readonly terminal: boolean } {
|
|
603
|
+
let index = start
|
|
604
|
+
while (index < words.length) {
|
|
605
|
+
const word = words[index] as Word
|
|
606
|
+
if (word.operator) break
|
|
607
|
+
const value = word.value
|
|
608
|
+
if (GIT_TERMINAL_GLOBALS.has(value) || value.startsWith('--list-cmds=')) return { index, terminal: true }
|
|
609
|
+
if (GIT_GLOBAL_TAKES_ARG.has(value)) {
|
|
610
|
+
index += 2
|
|
611
|
+
continue
|
|
612
|
+
}
|
|
613
|
+
if (GIT_GLOBAL_EQ.some((prefix) => value.startsWith(prefix)) || (value.startsWith('-c') && value.includes('=') && value !== '-c')) {
|
|
614
|
+
index += 1
|
|
615
|
+
continue
|
|
616
|
+
}
|
|
617
|
+
if (value === '--') {
|
|
618
|
+
index += 1
|
|
619
|
+
break
|
|
620
|
+
}
|
|
621
|
+
if (value.startsWith('-') && value !== '-') {
|
|
622
|
+
index += 1
|
|
623
|
+
continue
|
|
624
|
+
}
|
|
625
|
+
break
|
|
626
|
+
}
|
|
627
|
+
return { index, terminal: false }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const CREDENTIAL_HELPER_VERBS: ReadonlySet<string> = new Set(['fill', 'approve', 'reject'])
|
|
631
|
+
const SUBMODULE_NETWORK_VERBS: ReadonlySet<string> = new Set(['update', 'sync', 'foreach'])
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* The alias VALUE's own verb — split on whitespace, a leading `!` (a shell
|
|
635
|
+
* alias, e.g. `!git push origin main`) stripped first and, when what
|
|
636
|
+
* follows still starts with `git`/`git.exe`, that word skipped too so
|
|
637
|
+
* `!git push …` reads as verb `push`, not `git`. A fully arbitrary shell
|
|
638
|
+
* one-liner alias (`!curl … | sh`) is not further inspected here — that is
|
|
639
|
+
* `shell.remote-script-exec`'s/`parse.uncertain`'s concern, not this one's;
|
|
640
|
+
* this only recognizes the exact network/credential VERB shapes H2 names.
|
|
641
|
+
*/
|
|
642
|
+
export function networkAliasVerb(aliasValue: string): boolean {
|
|
643
|
+
const stripped = aliasValue.startsWith('!') ? aliasValue.slice(1).trim() : aliasValue
|
|
644
|
+
const words = stripped.split(/\s+/).filter((word) => word.length > 0)
|
|
645
|
+
const start = words[0]?.toLowerCase() === 'git' || words[0]?.toLowerCase() === 'git.exe' ? 1 : 0
|
|
646
|
+
const first = words[start]?.toLowerCase()
|
|
647
|
+
if (first === undefined) return false
|
|
648
|
+
const second = words[start + 1]?.toLowerCase()
|
|
649
|
+
if (NETWORK_GIT_VERBS.has(first)) return true
|
|
650
|
+
if (first === 'remote' && second === 'update') return true
|
|
651
|
+
if (first === 'submodule' && second !== undefined && SUBMODULE_NETWORK_VERBS.has(second)) return true
|
|
652
|
+
if (first === 'credential' && second !== undefined && CREDENTIAL_HELPER_VERBS.has(second)) return true
|
|
653
|
+
return false
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* True when `globalWords` (a git invocation's `-c`/`--config-env` globals,
|
|
658
|
+
* BEFORE alias resolution) define `alias.<subcommand>` — SELF-referential:
|
|
659
|
+
* the literal word this invocation actually runs — to a value whose own
|
|
660
|
+
* verb is network/credential-bearing (`networkAliasVerb`). See H2's comment
|
|
661
|
+
* at this function's one call site for why this is narrower than a blanket
|
|
662
|
+
* `-c alias.*` deny.
|
|
663
|
+
*/
|
|
664
|
+
function networkAliasRedefinition(globalWords: readonly Word[], subcommand: string): boolean {
|
|
665
|
+
const targetKey = `alias.${subcommand}`
|
|
666
|
+
for (let index = 0; index < globalWords.length; index += 1) {
|
|
667
|
+
const word = globalWords[index] as Word
|
|
668
|
+
if (word.operator) continue
|
|
669
|
+
const value = word.value
|
|
670
|
+
let setting: string | null = null
|
|
671
|
+
if (value === '-c' || value === '--config-env') {
|
|
672
|
+
const next = globalWords[index + 1]
|
|
673
|
+
if (next !== undefined && !next.operator) setting = next.value
|
|
674
|
+
index += 1
|
|
675
|
+
} else if (value.startsWith('-c') && value.includes('=') && value !== '-c') {
|
|
676
|
+
setting = value.slice(2)
|
|
677
|
+
} else if (value.startsWith('--config-env=')) {
|
|
678
|
+
setting = value.slice('--config-env='.length)
|
|
679
|
+
}
|
|
680
|
+
if (setting === null) continue
|
|
681
|
+
const equals = setting.indexOf('=')
|
|
682
|
+
if (equals < 0) continue
|
|
683
|
+
if (setting.slice(0, equals).toLowerCase() !== targetKey) continue
|
|
684
|
+
if (networkAliasVerb(setting.slice(equals + 1).trim())) return true
|
|
685
|
+
}
|
|
686
|
+
return false
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* R4 (STATUS.md 12p, re-review 2026-09-28 night): the
|
|
691
|
+
* ENV-VAR-based sibling of `networkAliasRedefinition` — `GIT_CONFIG_COUNT`/
|
|
692
|
+
* `GIT_CONFIG_KEY_<n>`/`GIT_CONFIG_VALUE_<n>` are git's OWN alternative to
|
|
693
|
+
* `-c`/`--config-env` for supplying ad-hoc config, read the same way
|
|
694
|
+
* `../shell/invocation.ts`'s `environmentGitAlias` already reads them for
|
|
695
|
+
* CLASSIFICATION (so this repo's other rules see the alias's resolved
|
|
696
|
+
* verb) — but classification alone throws away the fact that an alias was
|
|
697
|
+
* used at all once resolved, which is exactly what this SELF-referential
|
|
698
|
+
* check needs to catch: `bin/git-ado` routes on the literal argv verb, so
|
|
699
|
+
* `GIT_CONFIG_KEY_0=alias.p GIT_CONFIG_VALUE_0=push git p` never reaches its
|
|
700
|
+
* identity routing at all, the same live gap `networkAliasRedefinition`
|
|
701
|
+
* closes for the `-c`/`--config-env` spelling.
|
|
702
|
+
*/
|
|
703
|
+
function environmentAliasRedefinition(assignments: readonly string[], subcommand: string): boolean {
|
|
704
|
+
const targetKey = `alias.${subcommand}`
|
|
705
|
+
let count: number | null = null
|
|
706
|
+
const keys = new Map<number, string>()
|
|
707
|
+
const values = new Map<number, string>()
|
|
708
|
+
for (const assignment of assignments) {
|
|
709
|
+
const equals = assignment.indexOf('=')
|
|
710
|
+
if (equals < 0) continue
|
|
711
|
+
const name = assignment.slice(0, equals)
|
|
712
|
+
const value = assignment.slice(equals + 1)
|
|
713
|
+
if (name === 'GIT_CONFIG_COUNT') count = /^\d+$/.test(value) ? Number(value) : null
|
|
714
|
+
const keyMatch = /^GIT_CONFIG_KEY_(\d+)$/.exec(name)
|
|
715
|
+
if (keyMatch !== null) keys.set(Number(keyMatch[1]), value)
|
|
716
|
+
const valueMatch = /^GIT_CONFIG_VALUE_(\d+)$/.exec(name)
|
|
717
|
+
if (valueMatch !== null) values.set(Number(valueMatch[1]), value)
|
|
718
|
+
}
|
|
719
|
+
if (count === null || !Number.isSafeInteger(count)) return false
|
|
720
|
+
for (const index of [...keys.keys()].filter((candidate) => candidate < count)) {
|
|
721
|
+
if (keys.get(index)?.toLowerCase() !== targetKey) continue
|
|
722
|
+
const value = values.get(index)
|
|
723
|
+
if (value !== undefined && networkAliasVerb(value)) return true
|
|
724
|
+
}
|
|
725
|
+
return false
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** `--global`/`--local`/`--system`/`--worktree`/`--file <path>` etc. ahead of `git config`'s own positional key/value — skipped so `git config --global alias.p push` still finds `alias.p` as the key. */
|
|
729
|
+
function skipGitConfigScopeFlags(words: readonly Word[], start: number): number {
|
|
730
|
+
const VALUE_FLAGS = new Set(['--file', '-f', '--blob'])
|
|
731
|
+
let index = start
|
|
732
|
+
while (index < words.length) {
|
|
733
|
+
const word = words[index] as Word
|
|
734
|
+
if (word.operator) break
|
|
735
|
+
const value = word.value
|
|
736
|
+
if (value === '--') return index + 1
|
|
737
|
+
if (VALUE_FLAGS.has(value)) {
|
|
738
|
+
index += 2
|
|
739
|
+
continue
|
|
740
|
+
}
|
|
741
|
+
if (value.startsWith('-') && value !== '-') {
|
|
742
|
+
index += 1
|
|
743
|
+
continue
|
|
744
|
+
}
|
|
745
|
+
break
|
|
746
|
+
}
|
|
747
|
+
return index
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* R4 (STATUS.md 12p, re-review 2026-09-28 night):
|
|
752
|
+
* `git config [--global|--local|…] alias.<verb> <value>` WRITES a
|
|
753
|
+
* persistent alias a LATER, separate command can invoke — pre-loading the
|
|
754
|
+
* exact bypass `networkAliasRedefinition`/`environmentAliasRedefinition`
|
|
755
|
+
* catch inline, just deferred to whenever the alias is next used (by which
|
|
756
|
+
* point this scanner has no visibility into a different command line at
|
|
757
|
+
* all). Denied at the point of DEFINITION instead: `git config alias.p
|
|
758
|
+
* push` never needs `git p` to actually run on the same line to be a real
|
|
759
|
+
* bypass being set up.
|
|
760
|
+
*/
|
|
761
|
+
function gitConfigWritesNetworkAlias(rest: readonly Word[]): boolean {
|
|
762
|
+
const index = skipGitConfigScopeFlags(rest, 0)
|
|
763
|
+
const keyWord = rest[index]
|
|
764
|
+
if (keyWord === undefined || keyWord.operator) return false
|
|
765
|
+
const match = /^alias\.(.+)$/i.exec(keyWord.value)
|
|
766
|
+
if (match === null) return false
|
|
767
|
+
const valueWord = rest[index + 1]
|
|
768
|
+
if (valueWord === undefined || valueWord.operator || valueWord.value.startsWith('-')) return false
|
|
769
|
+
return networkAliasVerb(valueWord.value)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const COMMAND_OPTION = '--command='
|
|
773
|
+
const SCRIPT_OPTION_TAKES_WORD = new Set(['-c', '--command'])
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* The script string a shell's `-c`/`--command` option carries, as a raw
|
|
777
|
+
* content span into the original command — port of invocation.ts's
|
|
778
|
+
* walkShellArgument. `--` ends the option scan; a non-option operand is a
|
|
779
|
+
* script FILE and is not inspected; an unterminated option scan yields null.
|
|
780
|
+
*/
|
|
781
|
+
function scriptContentSpan(command: string, words: readonly Word[], start: number): { readonly start: number; readonly end: number } | null {
|
|
782
|
+
for (let index = start; index < words.length; index += 1) {
|
|
783
|
+
const word = words[index] as Word
|
|
784
|
+
if (word.operator) return null
|
|
785
|
+
const value = word.value
|
|
786
|
+
if (value === '--') return null
|
|
787
|
+
if (SCRIPT_OPTION_TAKES_WORD.has(value) || (/^-[^-]*c$/.test(value) && !value.startsWith('--'))) {
|
|
788
|
+
const script = words[index + 1]
|
|
789
|
+
if (script === undefined || script.operator) return null
|
|
790
|
+
// A quoted script: insertions land inside the quotes; unquoted: at the word.
|
|
791
|
+
const rawFirst = command[script.start]
|
|
792
|
+
return rawFirst === "'" || rawFirst === '"' ? { start: script.start + 1, end: script.end - 1 } : { start: script.start, end: script.end }
|
|
793
|
+
}
|
|
794
|
+
if (value.startsWith(COMMAND_OPTION)) {
|
|
795
|
+
const valueStart = word.start + COMMAND_OPTION.length
|
|
796
|
+
if (valueStart >= word.end) return null
|
|
797
|
+
// A quoted inline script inserts INSIDE the quotes; an unquoted one at
|
|
798
|
+
// the value start — either way the prefix becomes part of the script
|
|
799
|
+
// string, never a separate word that would shift the shell's $0.
|
|
800
|
+
const rawFirst = command[valueStart]
|
|
801
|
+
return rawFirst === "'" || rawFirst === '"' ? { start: valueStart + 1, end: word.end - 1 } : { start: valueStart, end: word.end }
|
|
802
|
+
}
|
|
803
|
+
if (!value.startsWith('-')) return null
|
|
804
|
+
}
|
|
805
|
+
return null
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
interface ScanState {
|
|
809
|
+
deny: Extract<GitIdentityScan, { readonly kind: 'deny' }>['reason'] | null
|
|
810
|
+
/** Original-command offsets (ascending) where the identity prefix is inserted. */
|
|
811
|
+
insertions: number[]
|
|
812
|
+
/** Rule mode: detect conflicts only, never compute insertions. */
|
|
813
|
+
conflictsOnly: boolean
|
|
814
|
+
/** Set true the moment an identity-bearing command is found with no valid inline override and `fallback === null`. */
|
|
815
|
+
needsIdentity: boolean
|
|
816
|
+
/** The fallback identity (steps 2-3 of the standard), or null when none resolved — see `applyIdentityOutcome`. */
|
|
817
|
+
fallback: string | null
|
|
818
|
+
/** Set true the moment a valid inline `ADL_AGENT_ID_AGENT=` override is found on any identity-bearing piece — see `commandHasValidInlineIdentity`. */
|
|
819
|
+
foundInlineOverride: boolean
|
|
820
|
+
/**
|
|
821
|
+
* The value of the FIRST valid inline `ADL_AGENT_ID_AGENT=` override found
|
|
822
|
+
* on any identity-bearing piece, or null — same coarseness as
|
|
823
|
+
* `foundInlineOverride` (whole-command, not per-piece); see
|
|
824
|
+
* `effectiveIdentityForCommand`.
|
|
825
|
+
*/
|
|
826
|
+
inlineIdentity: string | null
|
|
827
|
+
/**
|
|
828
|
+
* Every token-minting invocation found (`adl id token <Y>` /
|
|
829
|
+
* `adl-agent-id exec --agent <Y>`), paired with the effective identity for
|
|
830
|
+
* THAT piece — see `TokenMintingPiece` and `scanTokenMintingPieces`.
|
|
831
|
+
*/
|
|
832
|
+
tokenMinting: TokenMintingPiece[]
|
|
833
|
+
/**
|
|
834
|
+
* Every network git invocation found (`fetch`/`pull`/`clone`/`ls-remote`/
|
|
835
|
+
* `push`), paired with the effective identity for THAT SPECIFIC piece —
|
|
836
|
+
* see `GitInvocationPiece` and `scanGitInvocationPieces`. M2/M3
|
|
837
|
+
* (STATUS.md 12p): a role-grading rule
|
|
838
|
+
* (`adl-git-push-policy.ts`) needs the identity a PARTICULAR push on a
|
|
839
|
+
* compound command line actually carries, not the whole command's coarse
|
|
840
|
+
* `inlineIdentity` (which is set from whichever piece happens to be first
|
|
841
|
+
* — a reviewer session could otherwise smuggle a denied push past the
|
|
842
|
+
* rule by chaining an unrelated, later push with its own valid override
|
|
843
|
+
* on the same line).
|
|
844
|
+
*/
|
|
845
|
+
gitInvocations: GitInvocationPiece[]
|
|
846
|
+
/**
|
|
847
|
+
* Every `adl mcp cli <command> …` invocation found, paired with the
|
|
848
|
+
* effective identity for THAT SPECIFIC piece — R5
|
|
849
|
+
* (STATUS.md 12p): the role rules that gate these
|
|
850
|
+
* commands (`adl-workitem-role.ts`, `adl-pr-role.ts`,
|
|
851
|
+
* `adl-pr-review-role.ts`) used to grade `resolveFromEnvOrFile` alone,
|
|
852
|
+
* ignoring a valid inline override on the SAME piece — the exact M3 gap,
|
|
853
|
+
* just for `adl mcp cli` instead of `git push`. See `AdlMcpCliPiece` and
|
|
854
|
+
* `scanAdlMcpCliPieces`.
|
|
855
|
+
*/
|
|
856
|
+
mcpCliInvocations: AdlMcpCliPiece[]
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* One network git invocation (`fetch`/`pull`/`clone`/`ls-remote`/`push`)
|
|
861
|
+
* found while scanning a command, paired with the effective identity for
|
|
862
|
+
* THAT specific piece: a valid inline `ADL_AGENT_ID_AGENT=` override on the
|
|
863
|
+
* SAME piece, else the resolved fallback, else null. `rest` is the dequoted
|
|
864
|
+
* values after the subcommand — the same shape `Invocation.rest` (Token
|
|
865
|
+
* array, dequoted via `tokenValue`) carries for a `'git'`-kind Invocation
|
|
866
|
+
* from `../shell/invocation.ts`'s classifier, so a rule holding one of
|
|
867
|
+
* those can correlate it to the piece here by comparing values. See
|
|
868
|
+
* `scanGitInvocationPieces`.
|
|
869
|
+
*/
|
|
870
|
+
export interface GitInvocationPiece {
|
|
871
|
+
readonly subcommand: string
|
|
872
|
+
readonly rest: readonly string[]
|
|
873
|
+
readonly effectiveAgent: string | null
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* One `adl mcp cli <command> …` invocation found while scanning a command,
|
|
878
|
+
* paired with the effective identity for THAT specific piece — R5
|
|
879
|
+
* (STATUS.md 12p). `command` is the gated command name
|
|
880
|
+
* (e.g. `workitem-create-or-update`), lower-cased; `rest` is the dequoted
|
|
881
|
+
* values after it, the same shape `impl/adl-mcp-cli.ts`'s `adlMcpCliCall`
|
|
882
|
+
* reads off `Invocation.args.slice(3)`, so a role rule can correlate a
|
|
883
|
+
* specific `Invocation` to its own piece here by comparing values, exactly
|
|
884
|
+
* like `GitInvocationPiece`/`scanGitInvocationPieces` for `git push`. See
|
|
885
|
+
* `scanAdlMcpCliPieces`.
|
|
886
|
+
*/
|
|
887
|
+
export interface AdlMcpCliPiece {
|
|
888
|
+
readonly command: string
|
|
889
|
+
readonly rest: readonly string[]
|
|
890
|
+
readonly effectiveAgent: string | null
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* One `adl id token <agent>` or `adl-agent-id exec --agent <agent>` (or
|
|
895
|
+
* `--agent=<agent>`) invocation found while scanning a command, paired with
|
|
896
|
+
* the effective identity for that specific simple command: a valid inline
|
|
897
|
+
* `ADL_AGENT_ID_AGENT=` override on the SAME piece, else the resolved
|
|
898
|
+
* fallback (steps 2-3 of the standard), else null (no identity at all —
|
|
899
|
+
* `adl.session-required-for-writes`'s problem, not this one's).
|
|
900
|
+
* `requestedAgent` is the raw text the command names, unvalidated — the
|
|
901
|
+
* caller (`impl/adl-token-minting-role.ts`) is the one that compares it
|
|
902
|
+
* against `effectiveAgent`.
|
|
903
|
+
*/
|
|
904
|
+
export interface TokenMintingPiece {
|
|
905
|
+
readonly kind: 'adl-id-token' | 'adl-agent-id-exec'
|
|
906
|
+
readonly requestedAgent: string
|
|
907
|
+
readonly effectiveAgent: string | null
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** The value of a valid (non-empty, `VALID_AGENTS`-member) inline `ADL_AGENT_ID_AGENT=` assignment among `assignments`, or undefined. */
|
|
911
|
+
function validInlineIdentity(assignments: readonly string[]): string | undefined {
|
|
912
|
+
let inlineValue: string | undefined
|
|
913
|
+
for (const assignment of assignments) {
|
|
914
|
+
const equals = assignment.indexOf('=')
|
|
915
|
+
if (assignment.slice(0, equals) !== ADL_AGENT_ID_ENV_VAR) continue
|
|
916
|
+
inlineValue = assignment.slice(equals + 1).trim()
|
|
917
|
+
}
|
|
918
|
+
if (inlineValue === undefined) return undefined
|
|
919
|
+
return inlineValue.length > 0 && isValidAgent(inlineValue) ? inlineValue : undefined
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/** `adl-agent-id exec`'s `--agent <value>` or `--agent=<value>` argument, or undefined when absent/malformed. */
|
|
923
|
+
function execAgentFlagValue(restValues: readonly string[]): string | undefined {
|
|
924
|
+
const equalsForm = restValues.find((value) => value.startsWith('--agent='))
|
|
925
|
+
if (equalsForm !== undefined) return equalsForm.slice('--agent='.length) || undefined
|
|
926
|
+
const flagIndex = restValues.indexOf('--agent')
|
|
927
|
+
if (flagIndex < 0) return undefined
|
|
928
|
+
const value = restValues[flagIndex + 1]
|
|
929
|
+
return value === undefined || value.startsWith('-') ? undefined : value
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* The WORD (not the dequoted string `execAgentFlagValue` reads) carrying
|
|
934
|
+
* `adl-agent-id exec`'s `--agent` value — `--agent=<value>` is itself the
|
|
935
|
+
* carrying word; `--agent <value>` is the word AFTER the flag. Used only to
|
|
936
|
+
* check DYNAMISM (M5, STATUS.md 12p): a
|
|
937
|
+
* `--agent "$AGENT"` argument could name ANY agent at runtime, evading
|
|
938
|
+
* `adl.token-minting-role`'s static "own identity only" check entirely if
|
|
939
|
+
* it were silently treated as "not identity-bearing" the way any other
|
|
940
|
+
* unrelated dynamic flag on the same command is.
|
|
941
|
+
*/
|
|
942
|
+
function execAgentFlagWord(rest: readonly Word[]): Word | undefined {
|
|
943
|
+
const equalsForm = rest.find((word) => !word.operator && word.value.startsWith('--agent='))
|
|
944
|
+
if (equalsForm !== undefined) return equalsForm
|
|
945
|
+
const flagIndex = rest.findIndex((word) => !word.operator && word.value === '--agent')
|
|
946
|
+
if (flagIndex < 0) return undefined
|
|
947
|
+
const value = rest[flagIndex + 1]
|
|
948
|
+
return value === undefined || value.operator || value.value.startsWith('-') ? undefined : value
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Shared identity decision for ONE matched identity-bearing simple command
|
|
953
|
+
* (STATUS.md 12p, "Decision per identity-bearing
|
|
954
|
+
* shell command"):
|
|
955
|
+
*
|
|
956
|
+
* - an inline `ADL_AGENT_ID_AGENT=<value>` among `assignments` that is
|
|
957
|
+
* empty or not one of `VALID_AGENTS` is a deny (`identity-mismatch`) —
|
|
958
|
+
* never rewritten over;
|
|
959
|
+
* - a VALID inline value — regardless of whether it matches `state.fallback`
|
|
960
|
+
* — is the override: PASS, nothing inserted (the command already carries
|
|
961
|
+
* a safe, complete identity);
|
|
962
|
+
* - no inline value at all: a `sudo`/`doas` wrapper is `unrewritable-wrapper`
|
|
963
|
+
* (the environment a prefix relies on is stripped); otherwise, with
|
|
964
|
+
* `state.fallback === null`, this piece `needsIdentity` (the caller —
|
|
965
|
+
* `adl-session-required.ts` — is the one that denies this); otherwise the
|
|
966
|
+
* fallback is inserted at `insertAt` (skipped in `conflictsOnly` mode).
|
|
967
|
+
*/
|
|
968
|
+
function applyIdentityOutcome(assignments: readonly string[], unrewritableWrapper: boolean, insertAt: number, state: ScanState): void {
|
|
969
|
+
let inlineValue: string | undefined
|
|
970
|
+
for (const assignment of assignments) {
|
|
971
|
+
const equals = assignment.indexOf('=')
|
|
972
|
+
if (assignment.slice(0, equals) !== ADL_AGENT_ID_ENV_VAR) continue
|
|
973
|
+
inlineValue = assignment.slice(equals + 1).trim()
|
|
974
|
+
}
|
|
975
|
+
// An empty or invalid inline value is a deny regardless of a sudo/doas
|
|
976
|
+
// wrapper (it is already unsafe on its own); checked first, exactly like
|
|
977
|
+
// the pre-2026-09-28 mismatch check did.
|
|
978
|
+
if (inlineValue !== undefined && (inlineValue.length === 0 || !isValidAgent(inlineValue))) {
|
|
979
|
+
state.deny = 'identity-mismatch'
|
|
980
|
+
return
|
|
981
|
+
}
|
|
982
|
+
// sudo/doas strips the environment before the wrapped command runs, so
|
|
983
|
+
// even a VALID inline assignment sitting before it never reaches git —
|
|
984
|
+
// this check must come before "a valid inline override is a pass".
|
|
985
|
+
if (unrewritableWrapper) {
|
|
986
|
+
state.deny = 'unrewritable-wrapper'
|
|
987
|
+
return
|
|
988
|
+
}
|
|
989
|
+
if (inlineValue !== undefined) {
|
|
990
|
+
// A valid inline override, no unrewritable wrapper in the way: the
|
|
991
|
+
// command already carries a complete, safe identity — nothing to
|
|
992
|
+
// insert, nothing to deny.
|
|
993
|
+
state.foundInlineOverride = true
|
|
994
|
+
if (state.inlineIdentity === null) state.inlineIdentity = inlineValue
|
|
995
|
+
return
|
|
996
|
+
}
|
|
997
|
+
if (state.fallback === null) {
|
|
998
|
+
state.needsIdentity = true
|
|
999
|
+
return
|
|
1000
|
+
}
|
|
1001
|
+
if (!state.conflictsOnly) state.insertions.push(insertAt)
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Classify one simple command's words. Returns the insertion offset when the
|
|
1006
|
+
* piece is an identity-bearing invocation (a network git verb; an
|
|
1007
|
+
* identity-bearing `adl`/`adl-agent-id` subcommand; a curl/wget/httpie/xh
|
|
1008
|
+
* call to `dev.azure.com`/`*.visualstudio.com`) that needs the identity,
|
|
1009
|
+
* records a deny on conflict, and recurses into `bash -c '<script>'` shapes.
|
|
1010
|
+
* `insertAt` is the start of the simple command (before leading
|
|
1011
|
+
* assignments), so the new prefix binds to the whole command including any
|
|
1012
|
+
* wrappers.
|
|
1013
|
+
*/
|
|
1014
|
+
function analyzePiece(command: string, words: readonly Word[], state: ScanState, depth: number): void {
|
|
1015
|
+
let index = 0
|
|
1016
|
+
// Leading redirections (operator + target word) are not the command.
|
|
1017
|
+
while (index < words.length && (words[index] as Word).operator) {
|
|
1018
|
+
index += 1
|
|
1019
|
+
if (index < words.length && !(words[index] as Word).operator) index += 1
|
|
1020
|
+
}
|
|
1021
|
+
if (index >= words.length) return
|
|
1022
|
+
const insertAt = (words[index] as Word).start
|
|
1023
|
+
|
|
1024
|
+
const assignments: string[] = []
|
|
1025
|
+
while (index < words.length && isAssignmentValue((words[index] as Word).value)) {
|
|
1026
|
+
assignments.push((words[index] as Word).value)
|
|
1027
|
+
index += 1
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
let unrewritableWrapper = false
|
|
1031
|
+
for (;;) {
|
|
1032
|
+
if (index >= words.length) return
|
|
1033
|
+
const name = basenameValue((words[index] as Word).value)
|
|
1034
|
+
if (name === 'sudo' || name === 'doas') {
|
|
1035
|
+
// sudo/doas strip the environment: a prefix would silently not reach git.
|
|
1036
|
+
unrewritableWrapper = true
|
|
1037
|
+
index = skipDashOptions(words, index + 1, ['-u', '-g', '-C', '--user', '--group'], true)
|
|
1038
|
+
while (index < words.length && isAssignmentValue((words[index] as Word).value)) {
|
|
1039
|
+
assignments.push((words[index] as Word).value)
|
|
1040
|
+
index += 1
|
|
1041
|
+
}
|
|
1042
|
+
continue
|
|
1043
|
+
}
|
|
1044
|
+
// H1: env -u/--unset[=NAME]/-i and exec -c/-l can strip the environment
|
|
1045
|
+
// a prefix would rely on, the same way sudo/doas do — see skipEnvArgs's
|
|
1046
|
+
// and skipExecArgs's own docs for why this is blanket (any use of the
|
|
1047
|
+
// flag), not scoped to "only when it names ADL_AGENT_ID_AGENT".
|
|
1048
|
+
if (name === 'env') {
|
|
1049
|
+
// No trailing-assignment loop here, unlike sudo/doas and exec below:
|
|
1050
|
+
// skipEnvArgs already consumes every `NAME=value` argument itself (its
|
|
1051
|
+
// own catch-all branch — anything containing `=` and not a recognized
|
|
1052
|
+
// flag — pushes straight into `assignments`), so by construction it
|
|
1053
|
+
// never returns sitting on an unconsumed assignment.
|
|
1054
|
+
const scan = skipEnvArgs(words, index + 1, assignments)
|
|
1055
|
+
if (scan.strips) unrewritableWrapper = true
|
|
1056
|
+
index = scan.index
|
|
1057
|
+
continue
|
|
1058
|
+
}
|
|
1059
|
+
if (name === 'exec') {
|
|
1060
|
+
const scan = skipExecArgs(words, index + 1)
|
|
1061
|
+
if (scan.strips) unrewritableWrapper = true
|
|
1062
|
+
index = scan.index
|
|
1063
|
+
while (index < words.length && isAssignmentValue((words[index] as Word).value)) {
|
|
1064
|
+
assignments.push((words[index] as Word).value)
|
|
1065
|
+
index += 1
|
|
1066
|
+
}
|
|
1067
|
+
continue
|
|
1068
|
+
}
|
|
1069
|
+
const skipWrapper = WRAPPER_SKIP[name]
|
|
1070
|
+
if (skipWrapper !== undefined) {
|
|
1071
|
+
index = skipWrapper(words, index + 1, assignments)
|
|
1072
|
+
while (index < words.length && isAssignmentValue((words[index] as Word).value)) {
|
|
1073
|
+
assignments.push((words[index] as Word).value)
|
|
1074
|
+
index += 1
|
|
1075
|
+
}
|
|
1076
|
+
continue
|
|
1077
|
+
}
|
|
1078
|
+
break
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const commandName = basenameValue((words[index] as Word).value)
|
|
1082
|
+
|
|
1083
|
+
if (commandName === 'git' || commandName === 'git.exe') {
|
|
1084
|
+
const globals = scanGitGlobalsValues(words, index + 1)
|
|
1085
|
+
if (globals.terminal) return
|
|
1086
|
+
const subcommandWord = words[globals.index]
|
|
1087
|
+
if (subcommandWord === undefined || subcommandWord.operator) return
|
|
1088
|
+
if (dynamicValue(subcommandWord.value)) return
|
|
1089
|
+
// H2 (STATUS.md 12p, narrowed 2026-09-28): a
|
|
1090
|
+
// SELF-REFERENTIAL `-c alias.<name>=<value>` (or `--config-env[=]
|
|
1091
|
+
// alias.<name>=<value>`) where `<name>` is the LITERAL subcommand word
|
|
1092
|
+
// this invocation runs, and `<value>`'s own verb is a network/credential
|
|
1093
|
+
// one — checked BEFORE the NETWORK_GIT_VERBS gate below, since the
|
|
1094
|
+
// literal word (e.g. `status`) need not itself be a network verb at all.
|
|
1095
|
+
// `/usr/local/bin/git` (bin/git-ado) routes by the literal argv verb; it
|
|
1096
|
+
// does not resolve git aliases itself, so an aliased network verb never
|
|
1097
|
+
// reaches its identity routing. A NON-self-referential alias (`git -c
|
|
1098
|
+
// alias.wt=worktree wt …`) is untouched — see `networkAliasRedefinition`'s
|
|
1099
|
+
// own doc for why this is deliberately narrower than a blanket
|
|
1100
|
+
// `-c alias.*` deny.
|
|
1101
|
+
const globalWords = words.slice(index + 1, globals.index)
|
|
1102
|
+
const literalSubcommand = subcommandWord.value.toLowerCase()
|
|
1103
|
+
if (networkAliasRedefinition(globalWords, literalSubcommand)) {
|
|
1104
|
+
state.deny = 'network-alias-redefinition'
|
|
1105
|
+
return
|
|
1106
|
+
}
|
|
1107
|
+
// R4: the ENV-VAR-based sibling of the check above (`GIT_CONFIG_COUNT`/
|
|
1108
|
+
// `GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n`, already collected into
|
|
1109
|
+
// `assignments` by the leading-assignment loop above this function's
|
|
1110
|
+
// wrapper-skip loop) — see `environmentAliasRedefinition`'s own doc.
|
|
1111
|
+
if (environmentAliasRedefinition(assignments, literalSubcommand)) {
|
|
1112
|
+
state.deny = 'network-alias-redefinition'
|
|
1113
|
+
return
|
|
1114
|
+
}
|
|
1115
|
+
// R4: `git config [--global|…] alias.<verb> <value>` WRITES a
|
|
1116
|
+
// persistent alias for a LATER command to exploit — see
|
|
1117
|
+
// `gitConfigWritesNetworkAlias`'s own doc. Checked before the
|
|
1118
|
+
// NETWORK_GIT_VERBS gate below since `config` itself is never a network
|
|
1119
|
+
// verb.
|
|
1120
|
+
if (literalSubcommand === 'config' && gitConfigWritesNetworkAlias(words.slice(globals.index + 1))) {
|
|
1121
|
+
state.deny = 'network-alias-redefinition'
|
|
1122
|
+
return
|
|
1123
|
+
}
|
|
1124
|
+
const rest = words.slice(globals.index + 1).map((word) => word.value)
|
|
1125
|
+
// F2 (independent review, 2026-09-29): widened from a plain
|
|
1126
|
+
// `NETWORK_GIT_VERBS.has(literalSubcommand)` gate — see
|
|
1127
|
+
// `isNetworkGitInvocation`'s own doc for the additional shapes.
|
|
1128
|
+
if (!isNetworkGitInvocation(literalSubcommand, rest)) return
|
|
1129
|
+
if (valuesHelpOnly(rest)) return
|
|
1130
|
+
// M2/M3: record this SPECIFIC piece's own effective identity — see
|
|
1131
|
+
// `GitInvocationPiece`'s doc — before the destination exemption below,
|
|
1132
|
+
// so a role-grading rule can correlate to it by subcommand+rest
|
|
1133
|
+
// regardless of whether THIS scanner itself treats the piece as
|
|
1134
|
+
// identity-bearing for rewriting purposes.
|
|
1135
|
+
state.gitInvocations.push({
|
|
1136
|
+
subcommand: subcommandWord.value.toLowerCase(),
|
|
1137
|
+
rest,
|
|
1138
|
+
effectiveAgent: validInlineIdentity(assignments) ?? state.fallback,
|
|
1139
|
+
})
|
|
1140
|
+
// H3 (STATUS.md 12p): an EXPLICIT non-Azure-DevOps
|
|
1141
|
+
// destination is provably safe without an identity — same exemption
|
|
1142
|
+
// `git-helpers.ts`'s `classifyGitPushDestination` gives
|
|
1143
|
+
// `adl.session-required-for-writes`. No explicit URL (a bare `git push`,
|
|
1144
|
+
// or one naming a remote this scanner cannot look up) stays UNRESOLVED
|
|
1145
|
+
// and is treated as identity-bearing, same fail-closed rule. Needed so
|
|
1146
|
+
// `../engine.ts`'s H3 defense-in-depth deny on `needs-identity` does not
|
|
1147
|
+
// deny a legitimate `git fetch https://github.com/...` that has no
|
|
1148
|
+
// identity at all — this scanner previously flagged every network git
|
|
1149
|
+
// verb as identity-bearing regardless of destination, which was harmless
|
|
1150
|
+
// when `needs-identity` was never denied on its own, but is not harmless
|
|
1151
|
+
// now that it is.
|
|
1152
|
+
const explicitUrl = firstHttpUrl(rest)
|
|
1153
|
+
if (explicitUrl !== null && !isAdoUrl(explicitUrl)) return
|
|
1154
|
+
applyIdentityOutcome(assignments, unrewritableWrapper, insertAt, state)
|
|
1155
|
+
return
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (ADL_BINARY_NAMES.has(commandName)) {
|
|
1159
|
+
const rest = words.slice(index + 1)
|
|
1160
|
+
const restValues = rest.map((word) => word.value)
|
|
1161
|
+
// The shape check itself does not care about dynamism (a dynamic
|
|
1162
|
+
// position's raw text is never literally "id"/"token"/… anyway), so it
|
|
1163
|
+
// can run before the dynamic checks below — that lets the M5 check just
|
|
1164
|
+
// after it single out the SPECIFIC agent-argument position rather than
|
|
1165
|
+
// bailing on ANY dynamic content anywhere on the line.
|
|
1166
|
+
if (!matchesIdentityShape(restValues, ADL_IDENTITY_COMMAND_SHAPES)) return
|
|
1167
|
+
const isIdToken = restValues[0]?.toLowerCase() === 'id' && restValues[1]?.toLowerCase() === 'token'
|
|
1168
|
+
const isMcpCli = restValues[0]?.toLowerCase() === 'mcp' && restValues[1]?.toLowerCase() === 'cli'
|
|
1169
|
+
// M5 (STATUS.md 12p): `adl id token <agent>` with
|
|
1170
|
+
// a DYNAMIC agent argument is denied outright — a dynamically computed
|
|
1171
|
+
// agent name could name ANY agent at runtime, evading
|
|
1172
|
+
// adl.token-minting-role's static "own identity only" check entirely if
|
|
1173
|
+
// this were silently treated as "not identity-bearing" instead.
|
|
1174
|
+
if (isIdToken) {
|
|
1175
|
+
const agentWord = rest[2]
|
|
1176
|
+
if (agentWord !== undefined && !agentWord.operator && dynamicValue(agentWord.value)) {
|
|
1177
|
+
state.deny = 'dynamic-agent'
|
|
1178
|
+
return
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
// Any OTHER dynamic content (an unrelated flag elsewhere on the line)
|
|
1182
|
+
// cannot be proven identity-bearing beyond doubt; leave it alone rather
|
|
1183
|
+
// than guess.
|
|
1184
|
+
if (rest.some((word) => !word.operator && dynamicValue(word.value))) return
|
|
1185
|
+
// `adl id token <agent>`: the requested agent is the third positional
|
|
1186
|
+
// word (after `id token`), when it names one at all (not a flag).
|
|
1187
|
+
if (isIdToken) {
|
|
1188
|
+
const requested = restValues[2]
|
|
1189
|
+
if (requested !== undefined && requested.length > 0 && !requested.startsWith('-')) {
|
|
1190
|
+
state.tokenMinting.push({
|
|
1191
|
+
kind: 'adl-id-token',
|
|
1192
|
+
requestedAgent: requested,
|
|
1193
|
+
effectiveAgent: validInlineIdentity(assignments) ?? state.fallback,
|
|
1194
|
+
})
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
// R5: record this SPECIFIC `adl mcp cli <command>` piece's own effective
|
|
1198
|
+
// identity — see `AdlMcpCliPiece`'s doc — so the role rules that gate
|
|
1199
|
+
// these commands can grade the piece actually being invoked, not the
|
|
1200
|
+
// whole command's coarse `inlineIdentity`.
|
|
1201
|
+
if (isMcpCli) {
|
|
1202
|
+
const gatedCommand = restValues[2]
|
|
1203
|
+
if (gatedCommand !== undefined && gatedCommand.length > 0 && !gatedCommand.startsWith('-')) {
|
|
1204
|
+
state.mcpCliInvocations.push({
|
|
1205
|
+
command: gatedCommand.toLowerCase(),
|
|
1206
|
+
rest: restValues.slice(3),
|
|
1207
|
+
effectiveAgent: validInlineIdentity(assignments) ?? state.fallback,
|
|
1208
|
+
})
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
applyIdentityOutcome(assignments, unrewritableWrapper, insertAt, state)
|
|
1212
|
+
return
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (ADL_AGENT_ID_BINARY_NAMES.has(commandName)) {
|
|
1216
|
+
const rest = words.slice(index + 1)
|
|
1217
|
+
const restValues = rest.map((word) => word.value)
|
|
1218
|
+
// M5: leading dash-prefixed global flags (e.g. `adl-agent-id --verbose
|
|
1219
|
+
// exec --agent …`) are skipped before matching the `['exec']` shape —
|
|
1220
|
+
// see skipLeadingDashFlags's own doc for why this must not require
|
|
1221
|
+
// `exec` sitting at position 0 exactly.
|
|
1222
|
+
if (!matchesIdentityShape(skipLeadingDashFlags(restValues), ADL_AGENT_ID_COMMAND_SHAPES)) return
|
|
1223
|
+
// M5: a DYNAMIC --agent value is denied outright — same reasoning as
|
|
1224
|
+
// the `adl id token` case above, applied to this shape's own
|
|
1225
|
+
// agent-bearing flag instead of a positional argument.
|
|
1226
|
+
const agentWord = execAgentFlagWord(rest)
|
|
1227
|
+
if (agentWord !== undefined && dynamicValue(agentWord.value)) {
|
|
1228
|
+
state.deny = 'dynamic-agent'
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
if (rest.some((word) => !word.operator && dynamicValue(word.value))) return
|
|
1232
|
+
const requested = execAgentFlagValue(restValues)
|
|
1233
|
+
if (requested !== undefined) {
|
|
1234
|
+
state.tokenMinting.push({
|
|
1235
|
+
kind: 'adl-agent-id-exec',
|
|
1236
|
+
requestedAgent: requested,
|
|
1237
|
+
effectiveAgent: validInlineIdentity(assignments) ?? state.fallback,
|
|
1238
|
+
})
|
|
1239
|
+
}
|
|
1240
|
+
applyIdentityOutcome(assignments, unrewritableWrapper, insertAt, state)
|
|
1241
|
+
return
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (HTTP_CLIENT_BINARY_NAMES.has(commandName)) {
|
|
1245
|
+
const rest = words.slice(index + 1)
|
|
1246
|
+
const targetsAdo = rest.some((word) => !word.operator && isAdoUrl(word.value))
|
|
1247
|
+
if (!targetsAdo) return
|
|
1248
|
+
applyIdentityOutcome(assignments, unrewritableWrapper, insertAt, state)
|
|
1249
|
+
return
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (depth < MAX_NESTED_SCRIPT_DEPTH && SHELL_NAMES.has(commandName)) {
|
|
1253
|
+
const span = scriptContentSpan(command, words, index + 1)
|
|
1254
|
+
if (span !== null && span.end > span.start) {
|
|
1255
|
+
const nested: Piece[] = []
|
|
1256
|
+
scanPieces(command.slice(span.start, span.end), span.start, nested)
|
|
1257
|
+
for (const nestedPiece of nested) {
|
|
1258
|
+
if (state.deny !== null) return
|
|
1259
|
+
analyzePiece(command, nestedPiece.words, state, depth + 1)
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function scan(command: string, fallback: string | null, conflictsOnly: boolean): ScanState {
|
|
1266
|
+
const state: ScanState = {
|
|
1267
|
+
deny: null,
|
|
1268
|
+
insertions: [],
|
|
1269
|
+
conflictsOnly,
|
|
1270
|
+
needsIdentity: false,
|
|
1271
|
+
fallback,
|
|
1272
|
+
foundInlineOverride: false,
|
|
1273
|
+
inlineIdentity: null,
|
|
1274
|
+
tokenMinting: [],
|
|
1275
|
+
gitInvocations: [],
|
|
1276
|
+
mcpCliInvocations: [],
|
|
1277
|
+
}
|
|
1278
|
+
const pieces: Piece[] = []
|
|
1279
|
+
scanPieces(command, 0, pieces)
|
|
1280
|
+
for (const piece of pieces) {
|
|
1281
|
+
if (state.deny !== null) break
|
|
1282
|
+
analyzePiece(command, piece.words, state, 0)
|
|
1283
|
+
}
|
|
1284
|
+
return state
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Rewrite every identity-bearing invocation in `command` to carry
|
|
1289
|
+
* `fallback`, or report why it cannot be carried. `fallback` is the resolved
|
|
1290
|
+
* identity from steps 2-3 of the standard (`adl-identity.ts`'s
|
|
1291
|
+
* `resolveFromEnvOrFile`), or `null` when nothing resolved — a piece that
|
|
1292
|
+
* needs an identity and has none at all yields `needs-identity` rather than
|
|
1293
|
+
* a deny; the caller (`adl-session-required.ts`) is the one that denies that
|
|
1294
|
+
* case, with the standard's own message. Idempotent; never emits a token or
|
|
1295
|
+
* an identity-bearing prefix the command did not already agree to.
|
|
1296
|
+
*
|
|
1297
|
+
* Narrow by design (Tarun, 2026-09-29, superseding an earlier
|
|
1298
|
+
* whole-command `export` attempt at this same finding — STATUS.md
|
|
1299
|
+
* 12p, R1): this only rewrites the identity-bearing shapes the piece
|
|
1300
|
+
* scanner (`scanPieces`/`analyzePiece`, above) actually recognizes — a
|
|
1301
|
+
* TOP-LEVEL simple command (or one nested inside a recognized `bash -c`/
|
|
1302
|
+
* `sh -c` script). It deliberately does NOT walk into `if`/`!`/`for..do`/
|
|
1303
|
+
* `xargs`/a heredoc or here-string/an interpreter one-liner to find a
|
|
1304
|
+
* buried git/adl/curl invocation — `bin/git-ado` itself now fails closed
|
|
1305
|
+
* (refuses) whenever `ADL_AGENT_ID_AGENT` is unset unless the caller is an
|
|
1306
|
+
* interactive human terminal, so a shape this scanner misses is refused
|
|
1307
|
+
* loudly by git-ado, never silently laundered onto the human credential
|
|
1308
|
+
* path — the risk a broader rewrite here would have hedged against no
|
|
1309
|
+
* longer exists once that source-level fix is in place.
|
|
1310
|
+
*/
|
|
1311
|
+
export function rewriteGitNetworkCommands(command: string, fallback: string | null): GitIdentityScan {
|
|
1312
|
+
if (fallback !== null && !SAFE_IDENTITY.test(fallback)) return { kind: 'deny', reason: 'unsafe-identity' }
|
|
1313
|
+
const state = scan(command, fallback, false)
|
|
1314
|
+
if (state.deny !== null) return { kind: 'deny', reason: state.deny }
|
|
1315
|
+
if (state.needsIdentity) return { kind: 'needs-identity' }
|
|
1316
|
+
if (state.insertions.length === 0) return { kind: 'unchanged' }
|
|
1317
|
+
const prefix = `${ADL_AGENT_ID_ENV_VAR}=${fallback} `
|
|
1318
|
+
let rewritten = command
|
|
1319
|
+
for (const at of [...state.insertions].sort((left, right) => right - left)) {
|
|
1320
|
+
rewritten = `${rewritten.slice(0, at)}${prefix}${rewritten.slice(at)}`
|
|
1321
|
+
}
|
|
1322
|
+
return { kind: 'rewrite', command: rewritten }
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* The deny-rule view: which conflict (if any) a command carries — an invalid
|
|
1327
|
+
* or empty inline override, or an unrewritable `sudo`/`doas` wrapper. `null`
|
|
1328
|
+
* when the command is clean (including a VALID inline override, which is no
|
|
1329
|
+
* longer a conflict) or needs no identity at all — the rule stays silent so
|
|
1330
|
+
* the rewrite stage (or plain allow) proceeds. `fallback` only affects
|
|
1331
|
+
* whether a missing-inline piece is treated as "needs identity" internally;
|
|
1332
|
+
* this view never reports that as a conflict (`adl-session-required.ts` owns
|
|
1333
|
+
* that message), so it is safe to call with `fallback: null`.
|
|
1334
|
+
*/
|
|
1335
|
+
export function scanGitIdentityConflicts(command: string, fallback: string | null): GitIdentityDenyReason | null {
|
|
1336
|
+
if (fallback !== null && !SAFE_IDENTITY.test(fallback)) return null
|
|
1337
|
+
return scan(command, fallback, true).deny
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* Whole-command, coarse signal used by `adl-session-required.ts`: true when
|
|
1342
|
+
* SOME identity-bearing simple command in `command` carries a syntactically
|
|
1343
|
+
* valid inline `ADL_AGENT_ID_AGENT=` override. This is intentionally
|
|
1344
|
+
* coarser than the per-piece precision `rewriteGitNetworkCommands` itself
|
|
1345
|
+
* uses for the actual rewrite/deny: a compound command mixing an overridden
|
|
1346
|
+
* identity-bearing call with a non-overridden one is not disambiguated here
|
|
1347
|
+
* — this function only answers "is there a valid inline override ANYWHERE on
|
|
1348
|
+
* this line", which is exactly what the common, single-command override
|
|
1349
|
+
* shape (`ADL_AGENT_ID_AGENT=<agent> git push …`) needs, and is a
|
|
1350
|
+
* strictly MORE PERMISSIVE signal than the old session-env-only check it
|
|
1351
|
+
* replaces (a command this returns `false` for was ALSO going to be denied
|
|
1352
|
+
* by the old check).
|
|
1353
|
+
*/
|
|
1354
|
+
export function commandHasValidInlineIdentity(command: string): boolean {
|
|
1355
|
+
return scan(command, null, true).foundInlineOverride
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* The EFFECTIVE identity for `command` (W2 build item 2,
|
|
1360
|
+
* STATUS.md 12p): a valid inline
|
|
1361
|
+
* `ADL_AGENT_ID_AGENT=` override anywhere on the command line (step 1 — same
|
|
1362
|
+
* whole-command coarseness as `commandHasValidInlineIdentity`, which is the
|
|
1363
|
+
* shape every real override takes: `ADL_AGENT_ID_AGENT=<agent> git push …`),
|
|
1364
|
+
* else `fallback` (steps 2-3, `adl-identity.ts`'s `resolveFromEnvOrFile`),
|
|
1365
|
+
* else null. Used by the role rules that must grade the identity a COMMAND
|
|
1366
|
+
* actually carries, not merely the session's own — `adl-git-push-policy.ts`'s
|
|
1367
|
+
* `git push`. Callers should check `scanGitIdentityConflicts` first: an
|
|
1368
|
+
* invalid/empty inline override or an unrewritable sudo/doas wrapper is
|
|
1369
|
+
* `adl.git-identity`'s problem to report, not a value this function silently
|
|
1370
|
+
* papers over (an invalid inline value is never picked up here since
|
|
1371
|
+
* `inlineIdentity` is only ever set from a value that already passed
|
|
1372
|
+
* `isValidAgent`).
|
|
1373
|
+
*/
|
|
1374
|
+
export function effectiveIdentityForCommand(command: string, fallback: string | null): string | null {
|
|
1375
|
+
const state = scan(command, fallback, true)
|
|
1376
|
+
return state.inlineIdentity ?? fallback
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Every token-minting invocation (`adl id token <agent>` /
|
|
1381
|
+
* `adl-agent-id exec --agent <agent>`) found in `command`, each paired with
|
|
1382
|
+
* the effective identity for that specific piece — see `TokenMintingPiece`.
|
|
1383
|
+
* Used by `impl/adl-token-minting-role.ts` (W2 build item 1).
|
|
1384
|
+
*/
|
|
1385
|
+
export function scanTokenMintingPieces(command: string, fallback: string | null): readonly TokenMintingPiece[] {
|
|
1386
|
+
return scan(command, fallback, true).tokenMinting
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Every network git invocation (`fetch`/`pull`/`clone`/`ls-remote`/`push`)
|
|
1391
|
+
* found in `command`, each paired with the effective identity for THAT
|
|
1392
|
+
* specific piece — see `GitInvocationPiece`. M2/M3 (STATUS.md
|
|
1393
|
+
* 12p): used by `impl/adl-git-push-policy.ts` to grade a
|
|
1394
|
+
* SPECIFIC push by its own piece's identity, not the whole command's coarse
|
|
1395
|
+
* `effectiveIdentityForCommand` (which a compound command with more than one
|
|
1396
|
+
* identity-bearing piece could use to smuggle one piece's override onto
|
|
1397
|
+
* another's grading).
|
|
1398
|
+
*/
|
|
1399
|
+
export function scanGitInvocationPieces(command: string, fallback: string | null): readonly GitInvocationPiece[] {
|
|
1400
|
+
return scan(command, fallback, true).gitInvocations
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Every `adl mcp cli <command> …` invocation found in `command`, each
|
|
1405
|
+
* paired with the effective identity for THAT specific piece — see
|
|
1406
|
+
* `AdlMcpCliPiece`. R5 (STATUS.md 12p): used by
|
|
1407
|
+
* `impl/adl-workitem-role.ts`/`adl-pr-role.ts`/`adl-pr-review-role.ts` to
|
|
1408
|
+
* grade a SPECIFIC `adl mcp cli` invocation by its own piece's inline
|
|
1409
|
+
* identity, else the fallback — the same M3 correlation
|
|
1410
|
+
* `scanGitInvocationPieces` already gives `git push`, extended to this
|
|
1411
|
+
* command class.
|
|
1412
|
+
*/
|
|
1413
|
+
export function scanAdlMcpCliPieces(command: string, fallback: string | null): readonly AdlMcpCliPiece[] {
|
|
1414
|
+
return scan(command, fallback, true).mcpCliInvocations
|
|
1415
|
+
}
|