@mmerterden/multi-agent-pipeline 20.2.1 → 20.3.1
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/CHANGELOG.md +919 -1
- package/README.md +104 -81
- package/README.tr.md +103 -62
- package/docs/FIGMA_PIPELINE.md +35 -35
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0007-multi-tool-adapter-framework.md +6 -0
- package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +7 -0
- package/docs/architecture.md +50 -14
- package/docs/best-practices.md +1 -1
- package/docs/ecosystem.md +56 -32
- package/docs/facts.json +10 -10
- package/docs/features.md +97 -5
- package/docs/recovery-guide.md +7 -14
- package/docs/server-readiness.md +31 -24
- package/index.js +1 -1
- package/install/_common.mjs +3 -5
- package/install/_platform-filter.mjs +23 -1
- package/install/_unattended-profile.mjs +321 -75
- package/install/claude.mjs +51 -10
- package/install/codex.mjs +2 -0
- package/install/copilot.mjs +2 -0
- package/install/index.mjs +30 -17
- package/install/templates/claude-hooks.json +16 -5
- package/install/templates/copilot-instructions.md +1 -1
- package/install/templates/multi-agent-autopilot-awake.plist.template +48 -0
- package/install/templates/multi-agent-autopilot.plist.template +12 -5
- package/install/unattended-profile-legacy.json +80 -0
- package/manifest.json +617 -483
- package/package.json +8 -3
- package/pipeline/agents/code-reviewer.md +10 -0
- package/pipeline/agents/plan-critic.md +98 -0
- package/pipeline/agents/security-auditor.md +10 -0
- package/pipeline/agents/task-clarifier.md +10 -0
- package/pipeline/commands/multi-agent/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +12 -1
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +36 -6
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +77 -12
- package/pipeline/commands/multi-agent/autopilot-status/SKILL.md +29 -8
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/design-check/SKILL.md +16 -11
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/doctor/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/feedback/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/forget/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/graph/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/help/SKILL.md +37 -39
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/issue/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/jira/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/kill/SKILL.md +9 -3
- package/pipeline/commands/multi-agent/language/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/log/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/purge/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/research/SKILL.md +49 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +29 -6
- package/pipeline/commands/multi-agent/review/SKILL.md +28 -9
- package/pipeline/commands/multi-agent/review-analysis/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/review-issue/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/review-jira/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-off/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-on/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-status/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/routines/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/save/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/scaffold/SKILL.md +47 -0
- package/pipeline/commands/multi-agent/scan/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/search/SKILL.md +12 -8
- package/pipeline/commands/multi-agent/security-review/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/serve/SKILL.md +62 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +27 -36
- package/pipeline/commands/multi-agent/stack/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +10 -9
- package/pipeline/commands/multi-agent/steer/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
- package/pipeline/commands/multi-agent/test/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-accessibility/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-dark-mode/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-dynamic-type/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-screenshots/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +4 -0
- package/pipeline/contract/CHANGELOG.md +74 -0
- package/pipeline/contract/README.md +126 -0
- package/pipeline/contract/build.mjs +427 -0
- package/pipeline/contract/fixtures/answer-result.json +11 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +8 -0
- package/pipeline/contract/fixtures/error-unauthorized.json +5 -0
- package/pipeline/contract/fixtures/error-unsigned.json +5 -0
- package/pipeline/contract/fixtures/issues-empty.json +18 -0
- package/pipeline/contract/fixtures/launch-plan.json +31 -0
- package/pipeline/contract/fixtures/runs-awaiting-question.json +70 -0
- package/pipeline/contract/fixtures/runs-empty.json +6 -0
- package/pipeline/contract/fixtures/runs-failed.json +84 -0
- package/pipeline/contract/fixtures/runs-old-schema.json +70 -0
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +101 -0
- package/pipeline/contract/fixtures/runs-pr-opened.json +106 -0
- package/pipeline/contract/fixtures/runs-running.json +84 -0
- package/pipeline/contract/fixtures/worktrees-empty.json +4 -0
- package/pipeline/contract/frozen/toolbox.json +107 -0
- package/pipeline/contract/manifest.json +263 -0
- package/pipeline/contract/types/index.d.ts +343 -0
- package/pipeline/lib/_jira-auth.sh +6 -2
- package/pipeline/lib/account-resolver.sh +1 -1
- package/pipeline/lib/autopilot-state.sh +19 -0
- package/pipeline/lib/context-link-extractor.sh +12 -5
- package/pipeline/lib/credential-inventory.sh +12 -5
- package/pipeline/lib/credential-store.sh +116 -185
- package/pipeline/lib/fetch-confluence.sh +44 -3
- package/pipeline/lib/fetch-document.sh +3 -4
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/figma-mcp-refresh.sh +2 -2
- package/pipeline/lib/figma-token.sh +5 -1
- package/pipeline/lib/issue-fetcher.sh +233 -16
- package/pipeline/lib/json-file-lock.mjs +172 -0
- package/pipeline/lib/model-dispatch.sh +21 -12
- package/pipeline/lib/model-rung.sh +6 -1
- package/pipeline/lib/multi-repo-pipeline.sh +1 -1
- package/pipeline/lib/outbound-gate.mjs +46 -16
- package/pipeline/lib/parse-complaints.sh +14 -7
- package/pipeline/lib/plan-todos.sh +3 -3
- package/pipeline/lib/post-pr-review.sh +9 -9
- package/pipeline/lib/pr-request-location.mjs +85 -0
- package/pipeline/lib/regular-file.mjs +153 -0
- package/pipeline/lib/repo-hygiene.sh +17 -0
- package/pipeline/lib/route-state.sh +5 -1
- package/pipeline/lib/run-paths.sh +3 -2
- package/pipeline/lib/stack-detect.sh +19 -1
- package/pipeline/lib/unattended-profile-check.mjs +178 -0
- package/pipeline/lib/unattended-settings-location.mjs +28 -0
- package/pipeline/lib/unattended.mjs +76 -0
- package/pipeline/lib/unattended.sh +32 -0
- package/pipeline/lib/untrusted.mjs +76 -0
- package/pipeline/lib/usage-endpoint.mjs +33 -0
- package/pipeline/lib/user-facing.mjs +82 -0
- package/pipeline/lib/user-facing.sh +58 -0
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +4 -0
- package/pipeline/multi-agent-refs/analysis/locked.md +2 -2
- package/pipeline/multi-agent-refs/analysis/render.md +4 -3
- package/pipeline/multi-agent-refs/analysis/resolve.md +3 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +4 -5
- package/pipeline/multi-agent-refs/analysis-template.md +10 -17
- package/pipeline/multi-agent-refs/channels/jira.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
- package/pipeline/multi-agent-refs/conventions-defaults.md +32 -32
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -17
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +37 -9
- package/pipeline/multi-agent-refs/features/autopilot-operations.md +277 -0
- package/pipeline/multi-agent-refs/features/constitution.md +196 -0
- package/pipeline/multi-agent-refs/features/doctor.md +6 -3
- package/pipeline/multi-agent-refs/features/external-context-injection.md +6 -1
- package/pipeline/multi-agent-refs/features/jira-context.md +1 -1
- package/pipeline/multi-agent-refs/features/maturity-followup.md +43 -23
- package/pipeline/multi-agent-refs/features/model-fallback.md +9 -0
- package/pipeline/multi-agent-refs/features/phone-api.md +306 -0
- package/pipeline/multi-agent-refs/features/plan-critic.md +159 -0
- package/pipeline/multi-agent-refs/features/research.md +150 -0
- package/pipeline/multi-agent-refs/features/review-decision.md +185 -0
- package/pipeline/multi-agent-refs/features/scaffold.md +160 -0
- package/pipeline/multi-agent-refs/features/security-audit.md +6 -2
- package/pipeline/multi-agent-refs/features/skill-conformance.md +4 -1
- package/pipeline/multi-agent-refs/features/stack-adapters.md +116 -0
- package/pipeline/multi-agent-refs/features/unattended-gates.md +316 -0
- package/pipeline/multi-agent-refs/features/unattended-security.md +731 -0
- package/pipeline/multi-agent-refs/features/url-enrichment.md +2 -2
- package/pipeline/multi-agent-refs/features/usage-reporting.md +127 -35
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/features/visual-evidence.md +7 -7
- package/pipeline/multi-agent-refs/keychain.md +6 -11
- package/pipeline/multi-agent-refs/payload-contracts.md +2 -2
- package/pipeline/multi-agent-refs/phases/log-format.md +2 -3
- package/pipeline/multi-agent-refs/phases/modes.md +10 -12
- package/pipeline/multi-agent-refs/phases/operations.md +11 -5
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +73 -81
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +22 -22
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +42 -66
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +45 -79
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +28 -38
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +27 -25
- package/pipeline/multi-agent-refs/phases.md +1 -1
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/progress-contract.md +13 -16
- package/pipeline/multi-agent-refs/readiness-review.md +1 -1
- package/pipeline/multi-agent-refs/research/engine.md +91 -0
- package/pipeline/multi-agent-refs/rules.md +6 -4
- package/pipeline/multi-agent-refs/setup/repo-discovery.md +2 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +2 -2
- package/pipeline/multi-agent-refs/unattended-contract.md +68 -16
- package/pipeline/rules/figma-pipeline.md +12 -12
- package/pipeline/schemas/agent-state.schema.json +480 -18
- package/pipeline/schemas/analysis-spec.schema.json +4 -4
- package/pipeline/schemas/answer-request.schema.json +24 -0
- package/pipeline/schemas/answer-result.schema.json +28 -0
- package/pipeline/schemas/autopilot-config.schema.json +111 -13
- package/pipeline/schemas/command-parameters.schema.json +99 -0
- package/pipeline/schemas/constitution.schema.json +56 -0
- package/pipeline/schemas/contract-error.schema.json +52 -0
- package/pipeline/schemas/design-check-config.schema.json +5 -1
- package/pipeline/schemas/issues.schema.json +61 -0
- package/pipeline/schemas/launch-plan.schema.json +46 -0
- package/pipeline/schemas/launch-request.schema.json +45 -0
- package/pipeline/schemas/launch.json +61 -0
- package/pipeline/schemas/launch.schema.json +84 -0
- package/pipeline/schemas/phases.json +2 -2
- package/pipeline/schemas/phases.schema.json +68 -0
- package/pipeline/schemas/phone-devices.schema.json +61 -0
- package/pipeline/schemas/phone-signed-request.schema.json +67 -0
- package/pipeline/schemas/plan-critique.schema.json +99 -0
- package/pipeline/schemas/plan-todos.schema.json +7 -7
- package/pipeline/schemas/planning-output.schema.json +5 -0
- package/pipeline/schemas/pr-request.schema.json +46 -0
- package/pipeline/schemas/prefs.schema.json +79 -8
- package/pipeline/schemas/research-output.schema.json +118 -0
- package/pipeline/schemas/review-file-exclusions.schema.json +25 -0
- package/pipeline/schemas/reviewer-output.schema.json +40 -4
- package/pipeline/schemas/run-questions.json +392 -0
- package/pipeline/schemas/run-questions.schema.json +118 -0
- package/pipeline/schemas/runs-index.schema.json +189 -0
- package/pipeline/schemas/scaffold-manifest.schema.json +43 -0
- package/pipeline/schemas/secret-patterns.schema.json +28 -0
- package/pipeline/schemas/stack-adapters.json +527 -0
- package/pipeline/schemas/stack-adapters.schema.json +184 -0
- package/pipeline/schemas/token-budget.json +1 -1
- package/pipeline/schemas/token-budget.schema.json +26 -0
- package/pipeline/schemas/triage-output.schema.json +64 -3
- package/pipeline/schemas/unattended-policy.json +139 -0
- package/pipeline/schemas/unattended-policy.schema.json +73 -0
- package/pipeline/schemas/unattended-profile.json +248 -0
- package/pipeline/schemas/unattended-profile.schema.json +198 -0
- package/pipeline/schemas/worktrees.schema.json +51 -0
- package/pipeline/scripts/README.md +1 -0
- package/pipeline/scripts/_autopilot-config.mjs +130 -0
- package/pipeline/scripts/_autopilot-ops.mjs +567 -0
- package/pipeline/scripts/_autopilot-outcomes.mjs +174 -0
- package/pipeline/scripts/_command-contract.mjs +384 -0
- package/pipeline/scripts/_cost.mjs +40 -0
- package/pipeline/scripts/_notices.mjs +160 -0
- package/pipeline/scripts/_phone-auth.mjs +485 -0
- package/pipeline/scripts/_pre-existing.mjs +294 -0
- package/pipeline/scripts/_redact.mjs +77 -0
- package/pipeline/scripts/_run-paths.mjs +4 -2
- package/pipeline/scripts/_stack-adapter.mjs +678 -0
- package/pipeline/scripts/_stack-routing.mjs +1 -1
- package/pipeline/scripts/agent-guard.py +348 -37
- package/pipeline/scripts/agent-guard.sh +41 -13
- package/pipeline/scripts/analysis-story-tree.mjs +79 -3
- package/pipeline/scripts/answer-question.mjs +181 -0
- package/pipeline/scripts/audit-log-rotate.sh +1 -4
- package/pipeline/scripts/audit-log.sh +4 -4
- package/pipeline/scripts/autopilot-arming.mjs +389 -21
- package/pipeline/scripts/autopilot-awake.mjs +255 -0
- package/pipeline/scripts/autopilot-intake.mjs +137 -36
- package/pipeline/scripts/autopilot-menubar.swift +156 -44
- package/pipeline/scripts/autopilot-publish.mjs +1625 -0
- package/pipeline/scripts/autopilot-runner.mjs +1678 -222
- package/pipeline/scripts/autopilot-status.sh +198 -33
- package/pipeline/scripts/build-lock.sh +120 -0
- package/pipeline/scripts/build-references.mjs +4 -1
- package/pipeline/scripts/build-stack-plugins.mjs +59 -22
- package/pipeline/scripts/capture-flush.sh +1 -1
- package/pipeline/scripts/capture-resume.sh +13 -9
- package/pipeline/scripts/check-derived-drift.mjs +52 -11
- package/pipeline/scripts/commands.mjs +88 -0
- package/pipeline/scripts/constitution.mjs +362 -0
- package/pipeline/scripts/contract-server.mjs +776 -0
- package/pipeline/scripts/cost-analyze.mjs +89 -39
- package/pipeline/scripts/diff-explain.mjs +12 -1
- package/pipeline/scripts/doctor.mjs +77 -28
- package/pipeline/scripts/evidence-gate.mjs +192 -12
- package/pipeline/scripts/feedback-send.mjs +4 -2
- package/pipeline/scripts/gate-ledger.mjs +449 -0
- package/pipeline/scripts/gc-abandoned.sh +132 -13
- package/pipeline/scripts/gen-facts.mjs +31 -15
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/github-ssh-setup.sh +140 -29
- package/pipeline/scripts/graph-mermaid.mjs +4 -1
- package/pipeline/scripts/issues.mjs +236 -0
- package/pipeline/scripts/jira-attach.sh +6 -2
- package/pipeline/scripts/jira-search.sh +4 -3
- package/pipeline/scripts/keychain-save.sh +125 -24
- package/pipeline/scripts/keychain.py +63 -93
- package/pipeline/scripts/launch-request.mjs +747 -0
- package/pipeline/scripts/localize-commands.mjs +4 -10
- package/pipeline/scripts/log-metric.sh +6 -5
- package/pipeline/scripts/maturity-followup.mjs +13 -4
- package/pipeline/scripts/memory-save.sh +25 -0
- package/pipeline/scripts/migrate-prefs.mjs +4 -3
- package/pipeline/scripts/open-questions-gate.mjs +276 -0
- package/pipeline/scripts/phase-tracker.sh +41 -27
- package/pipeline/scripts/phase0-exit-gate.mjs +22 -4
- package/pipeline/scripts/phone-devices.mjs +224 -0
- package/pipeline/scripts/plan-coverage-gate.mjs +200 -66
- package/pipeline/scripts/plan-critique-gate.mjs +591 -0
- package/pipeline/scripts/pr-request.mjs +188 -0
- package/pipeline/scripts/pre-commit-check.sh +115 -4
- package/pipeline/scripts/probe-evidence-capability.sh +44 -5
- package/pipeline/scripts/record-phase.mjs +71 -0
- package/pipeline/scripts/render-agent-log-cost.sh +17 -2
- package/pipeline/scripts/render-cost-summary.sh +1 -1
- package/pipeline/scripts/render-work-summary.sh +1 -1
- package/pipeline/scripts/require-supported-version.sh +4 -1
- package/pipeline/scripts/research-gate.mjs +704 -0
- package/pipeline/scripts/review-decision-gate.mjs +403 -0
- package/pipeline/scripts/routine-registry.mjs +5 -2
- package/pipeline/scripts/runs-index.mjs +135 -27
- package/pipeline/scripts/scaffold-gate.mjs +393 -0
- package/pipeline/scripts/skill-conformance.mjs +25 -8
- package/pipeline/scripts/skill-siblings.mjs +2 -1
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +32 -6
- package/pipeline/scripts/smoke-schema-validation.sh +6 -2
- package/pipeline/scripts/spec-consistency-gate.mjs +469 -0
- package/pipeline/scripts/symbol-existence-gate.mjs +450 -0
- package/pipeline/scripts/test-gap-scan.mjs +40 -2
- package/pipeline/scripts/test-integrity-gate.mjs +20 -4
- package/pipeline/scripts/test-strength.mjs +484 -0
- package/pipeline/scripts/test-summary.mjs +651 -0
- package/pipeline/scripts/triage-memory.mjs +49 -9
- package/pipeline/scripts/unattended_policy.py +2786 -0
- package/pipeline/scripts/uninstall.mjs +10 -10
- package/pipeline/scripts/update-issue-progress.sh +1 -1
- package/pipeline/scripts/usage-identity.mjs +288 -0
- package/pipeline/scripts/usage-register.mjs +187 -67
- package/pipeline/scripts/usage-report.mjs +232 -67
- package/pipeline/scripts/validate-complaint-doc.mjs +28 -10
- package/pipeline/scripts/validate-planning.mjs +6 -0
- package/pipeline/scripts/verify-citations.mjs +151 -38
- package/pipeline/scripts/verify.mjs +58 -18
- package/pipeline/scripts/worktree-prepare.sh +126 -0
- package/pipeline/scripts/worktrees.mjs +124 -0
- package/pipeline/scripts/write-state.mjs +48 -17
- package/pipeline/skills/.skill-manifest.json +222 -226
- package/pipeline/skills/.skills-index.json +77 -88
- package/pipeline/skills/shared/README.md +44 -45
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +3 -2
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -9
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -5
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +11 -3
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-autopilot-off/SKILL.md +32 -6
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +66 -8
- package/pipeline/skills/shared/core/multi-agent-autopilot-status/SKILL.md +27 -9
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -4
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +7 -4
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-research/SKILL.md +35 -0
- package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +7 -3
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-scaffold/SKILL.md +30 -0
- package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +60 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +7 -8
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +10 -10
- package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-accessibility/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-dark-mode/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-dynamic-type/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-screenshots/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -1
- package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +46 -0
- package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +9 -3
- package/pipeline/skills/shared/external/NOTICE-paul-hudson-skills.md +54 -0
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +9 -11
- package/pipeline/skills/shared/external/NOTICE-vibeship-spawner-skills.md +203 -0
- package/pipeline/skills/shared/external/NOTICE-xcode-build-skills.md +10 -3
- package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +131 -25
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -3
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +85 -76
- package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +51 -3
- package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +9 -0
- package/pipeline/skills/shared/external/android-datastore/SKILL.md +4 -3
- package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +4 -3
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +106 -181
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +147 -0
- package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +45 -4
- package/pipeline/skills/shared/external/android-performance/SKILL.md +27 -2
- package/pipeline/skills/shared/external/android-performance/references/patterns.md +37 -37
- package/pipeline/skills/shared/external/api-patterns/SKILL.md +111 -67
- package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +128 -0
- package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +136 -186
- package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +140 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +207 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/operations.md +66 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +175 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +4 -3
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +2 -0
- package/pipeline/skills/shared/external/architecture/SKILL.md +106 -39
- package/pipeline/skills/shared/external/architecture/references/adr-and-review.md +76 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +2 -0
- package/pipeline/skills/shared/external/avkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/background-processing/SKILL.md +2 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +3 -2
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +2 -0
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +3 -2
- package/pipeline/skills/shared/external/clean-code/SKILL.md +192 -90
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +2 -1
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/compose-components/SKILL.md +4 -4
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +22 -22
- package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +10 -10
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +13 -6
- package/pipeline/skills/shared/external/compose-testing/references/patterns.md +59 -59
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +2 -0
- package/pipeline/skills/shared/external/context-compression/SKILL.md +118 -250
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-motion/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +2 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +2 -1
- package/pipeline/skills/shared/external/council/SKILL.md +2 -1
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +3 -2
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +2 -0
- package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +130 -20
- package/pipeline/skills/shared/external/debugging-strategies/references/hard-cases.md +54 -0
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +2 -0
- package/pipeline/skills/shared/external/docker-expert/SKILL.md +137 -381
- package/pipeline/skills/shared/external/docker-expert/references/patterns.md +98 -0
- package/pipeline/skills/shared/external/energykit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +3 -1
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +116 -185
- package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +235 -0
- package/pipeline/skills/shared/external/fastapi-pro/references/testing-and-deployment.md +69 -0
- package/pipeline/skills/shared/external/firebase/SKILL.md +4 -3
- package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +151 -293
- package/pipeline/skills/shared/external/github-actions-templates/references/patterns.md +96 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/hig-components-content/SKILL.md +69 -72
- package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +111 -0
- package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +77 -86
- package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +98 -0
- package/pipeline/skills/shared/external/hig-components-status/SKILL.md +150 -78
- package/pipeline/skills/shared/external/hig-components-system/SKILL.md +76 -97
- package/pipeline/skills/shared/external/hig-components-system/references/surfaces.md +98 -0
- package/pipeline/skills/shared/external/hig-foundations/SKILL.md +48 -76
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +132 -0
- package/pipeline/skills/shared/external/hig-inputs/SKILL.md +147 -106
- package/pipeline/skills/shared/external/hig-patterns/SKILL.md +61 -77
- package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +92 -0
- package/pipeline/skills/shared/external/hig-platforms/SKILL.md +147 -77
- package/pipeline/skills/shared/external/hig-technologies/SKILL.md +61 -121
- package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +108 -0
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +2 -0
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +3 -2
- package/pipeline/skills/shared/external/humanizer/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +14 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +4 -3
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +8 -6
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +11 -11
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +2 -0
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +102 -193
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +103 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -2
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +7 -9
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +2 -2
- package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +4 -3
- package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +5 -3
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +2 -0
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +2 -0
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +149 -46
- package/pipeline/skills/shared/external/monorepo-architect/references/patterns.md +70 -0
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +2 -0
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/natural-language/SKILL.md +2 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +3 -2
- package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +91 -21
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +247 -0
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/testing-and-frameworks.md +57 -0
- package/pipeline/skills/shared/external/observability-engineer/SKILL.md +155 -232
- package/pipeline/skills/shared/external/observability-engineer/references/patterns.md +75 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +2 -0
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +18 -18
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +2 -1
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +2 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +3 -2
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +2 -2
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +3 -2
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +7 -7
- package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +69 -69
- package/pipeline/skills/shared/external/room-database/references/patterns.md +126 -126
- package/pipeline/skills/shared/external/search-first/SKILL.md +2 -1
- package/pipeline/skills/shared/external/security-review/SKILL.md +4 -3
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +2 -2
- package/pipeline/skills/shared/external/signal-community/SKILL.md +1 -1
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +2 -1
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +2 -1
- package/pipeline/skills/shared/external/spm-build-analysis/SKILL.md +2 -0
- package/pipeline/skills/shared/external/storekit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swift-concurrency-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swift-security/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +10 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftdata-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swiftui-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +4 -3
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +3 -2
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +3 -2
- package/pipeline/skills/shared/external/tipkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +3 -2
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +3 -2
- package/pipeline/skills/shared/external/web-performance/SKILL.md +3 -2
- package/pipeline/skills/shared/external/web-testing/SKILL.md +3 -2
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +1 -1
- package/pipeline/skills/shared/external/xcode-build-benchmark/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-build-orchestrator/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/SKILL.md +2 -0
- package/pipeline/skills/skills-index.md +40 -41
- package/docs/token-budget-history.md +0 -24
- package/pipeline/skills/shared/external/agentflow/SKILL.md +0 -199
- package/pipeline/skills/shared/external/android-ui-verification/SKILL.md +0 -66
- package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +0 -299
- package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +0 -255
- package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +0 -167
- package/pipeline/skills/shared/external/closed-loop-delivery/SKILL.md +0 -116
- package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -216
|
@@ -2,18 +2,41 @@
|
|
|
2
2
|
"""Decision core for agent-guard.sh (see that file for the hook contract).
|
|
3
3
|
|
|
4
4
|
Reads the PreToolUse payload JSON on stdin and prints exactly one decision:
|
|
5
|
-
BLOCK_ATTRIB | BLOCK_FORCE | OK
|
|
5
|
+
BLOCK_ATTRIB | BLOCK_FORCE | BLOCK_UNATTENDED<TAB>reason | BLOCK_PARSE | OK
|
|
6
6
|
|
|
7
7
|
Never executes the command. shlex.split only tokenizes shell-like text; a
|
|
8
|
-
`$(...)` in the command is a literal token, never a subshell.
|
|
9
|
-
|
|
8
|
+
`$(...)` in the command is a literal token, never a subshell. The processes it
|
|
9
|
+
does start are read-only `git rev-parse` / `git diff --name-only` calls on a
|
|
10
|
+
path, with argv and no shell.
|
|
11
|
+
|
|
12
|
+
Attended (MULTI_AGENT_UNATTENDED unset): the attribution and force-push rules
|
|
13
|
+
only, and any error prints OK (fail-open) so a guard bug cannot break a
|
|
14
|
+
legitimate tool call. Unattended: unattended_policy.py is consulted as well,
|
|
15
|
+
and an error prints BLOCK_PARSE (fail-closed).
|
|
10
16
|
"""
|
|
11
17
|
import json
|
|
12
18
|
import os
|
|
13
19
|
import re
|
|
14
20
|
import shlex
|
|
21
|
+
import signal
|
|
15
22
|
import sys
|
|
16
23
|
|
|
24
|
+
# An internal deadline well under the hook timeout: if the guard cannot decide
|
|
25
|
+
# in this long, it prints the blocking verdict when unattended (fail-closed) and
|
|
26
|
+
# the allow verdict when attended (a guard bug must not break legitimate work).
|
|
27
|
+
# MA_GUARD_DEADLINE_SECONDS may lower it, never raise it: a longer deadline
|
|
28
|
+
# would let the hook timeout decide instead of the guard.
|
|
29
|
+
DEADLINE_SECONDS = 5
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def deadline_seconds(env=None) -> float:
|
|
33
|
+
env = os.environ if env is None else env
|
|
34
|
+
try:
|
|
35
|
+
v = float(env.get("MA_GUARD_DEADLINE_SECONDS", ""))
|
|
36
|
+
except ValueError:
|
|
37
|
+
return DEADLINE_SECONDS
|
|
38
|
+
return v if 0 < v < DEADLINE_SECONDS else DEADLINE_SECONDS
|
|
39
|
+
|
|
17
40
|
# --- Rule data (extend here; this is the hookify-style rule surface) ----------
|
|
18
41
|
PROTECTED = {"main", "master", "develop"}
|
|
19
42
|
REMOTES = {"origin", "upstream"}
|
|
@@ -126,9 +149,80 @@ def segments(cmd: str) -> list:
|
|
|
126
149
|
return [(seg, tokenize(seg)) for seg in SEPARATORS.split(cmd)]
|
|
127
150
|
|
|
128
151
|
|
|
129
|
-
|
|
152
|
+
# `git push` options that take the NEXT token as their value. Without this list
|
|
153
|
+
# `git push -f -o ci.skip origin` reads `ci.skip` as the repository and
|
|
154
|
+
# `origin` as a branch name, and the bare-push check never runs.
|
|
155
|
+
PUSH_VALUE_OPTIONS = {"-o", "--push-option", "--repo", "--receive-pack", "--exec"}
|
|
156
|
+
# Pushes that rewrite or remove every ref, protected ones included.
|
|
157
|
+
PUSH_ALL_REFS = {"--all", "--mirror", "--branches"}
|
|
158
|
+
PUSH_DELETE = {"-d", "--delete", "--prune"}
|
|
159
|
+
# Pushes that can remove or rewrite a protected ref with no force flag: a
|
|
160
|
+
# mirror (force + prune over every ref), a prune, a delete, and a refspec with
|
|
161
|
+
# an empty source (`:main`), which deletes its destination.
|
|
162
|
+
PUSH_GATE = re.compile(r"(^|\s)(-d|--delete|--mirror|--prune)(\s|=|$)|(^|\s)\+?:\S")
|
|
163
|
+
# A destination that names the branch the checkout is on.
|
|
164
|
+
CURRENT_BRANCH_REFS = {"HEAD", "@"}
|
|
165
|
+
SHORT_FORCE = re.compile(r"^-[A-Za-z]*f[A-Za-z]*$")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
SHELLS = {"sh", "bash", "zsh", "dash", "ksh", "fish"}
|
|
169
|
+
MAX_DEPTH = 3
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def flatten(cmd: str) -> str:
|
|
173
|
+
"""cmd with every unquoted `(`, `)` and backtick turned into a separator,
|
|
174
|
+
so a command inside a subshell or a substitution is a segment of its own.
|
|
175
|
+
Like the separator split, this can only add segments."""
|
|
176
|
+
out, q, i = [], None, 0
|
|
177
|
+
while i < len(cmd):
|
|
178
|
+
c = cmd[i]
|
|
179
|
+
if q == "'":
|
|
180
|
+
q = None if c == "'" else q
|
|
181
|
+
elif c == "\\" and i + 1 < len(cmd):
|
|
182
|
+
out.append(cmd[i : i + 2])
|
|
183
|
+
i += 2
|
|
184
|
+
continue
|
|
185
|
+
elif q == '"':
|
|
186
|
+
q = None if c == '"' else q
|
|
187
|
+
elif c in ("'", '"'):
|
|
188
|
+
q = c
|
|
189
|
+
elif c in "()`":
|
|
190
|
+
out.append(" ; ")
|
|
191
|
+
i += 1
|
|
192
|
+
continue
|
|
193
|
+
out.append(c)
|
|
194
|
+
i += 1
|
|
195
|
+
return "".join(out)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _nested_payload(toks: list):
|
|
199
|
+
"""The command line a segment hands to another shell: the `-c` argument
|
|
200
|
+
of sh/bash/zsh (after any env assignments or wrappers), or the words of
|
|
201
|
+
`eval`. None when the segment runs no nested line."""
|
|
202
|
+
i = 0
|
|
203
|
+
while i < len(toks) and (re.match(r"^[A-Za-z_][A-Za-z0-9_]*=", toks[i]) or os.path.basename(toks[i]) in ("env", "nohup", "command", "builtin", "exec", "nice", "time")):
|
|
204
|
+
i += 1
|
|
205
|
+
if i >= len(toks):
|
|
206
|
+
return None
|
|
207
|
+
base = os.path.basename(toks[i]).lower()
|
|
208
|
+
if base == "eval":
|
|
209
|
+
return " ".join(toks[i + 1 :])
|
|
210
|
+
if base not in SHELLS:
|
|
211
|
+
return None
|
|
212
|
+
has_c = False
|
|
213
|
+
for t in toks[i + 1 :]:
|
|
214
|
+
if t.startswith("-") and not t.startswith("--"):
|
|
215
|
+
has_c = has_c or "c" in t[1:]
|
|
216
|
+
continue
|
|
217
|
+
if t.startswith("--"):
|
|
218
|
+
continue
|
|
219
|
+
return t if has_c else None
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def decide(cmd: str, depth: int = 0) -> str:
|
|
130
224
|
low = cmd.lower()
|
|
131
|
-
segs = segments(cmd)
|
|
225
|
+
segs = segments(flatten(cmd))
|
|
132
226
|
|
|
133
227
|
# Rule 1: AI attribution inside a git commit.
|
|
134
228
|
commits = re.search(r"git\s+commit", low) or any(
|
|
@@ -138,22 +232,124 @@ def decide(cmd: str) -> str:
|
|
|
138
232
|
return "BLOCK_ATTRIB"
|
|
139
233
|
|
|
140
234
|
# Rule 2: force-push to a protected branch. Evaluate each push segment on its
|
|
141
|
-
# own so a neighbouring command's flags cannot implicate it.
|
|
235
|
+
# own so a neighbouring command's flags cannot implicate it. A `cd` earlier
|
|
236
|
+
# on the line moves the push into another checkout, whose branch is the one
|
|
237
|
+
# that counts.
|
|
238
|
+
cwd = None
|
|
142
239
|
for seg, toks in segs:
|
|
240
|
+
if toks and toks[0] in ("{", "}"):
|
|
241
|
+
toks = toks[1:]
|
|
242
|
+
if toks and toks[0] in ("cd", "pushd"):
|
|
243
|
+
cwd = _cd_target(toks, cwd)
|
|
244
|
+
continue
|
|
245
|
+
payload = _nested_payload(toks)
|
|
246
|
+
if payload and depth < MAX_DEPTH:
|
|
247
|
+
verdict = decide(payload, depth + 1)
|
|
248
|
+
if verdict != "OK":
|
|
249
|
+
return verdict
|
|
250
|
+
continue
|
|
143
251
|
if git_subcommand(toks) != "push" and not re.search(r"git\s+push", seg.lower()):
|
|
144
252
|
continue
|
|
145
|
-
verdict = _decide_push(seg)
|
|
253
|
+
verdict = _decide_push(seg, toks, cwd)
|
|
146
254
|
if verdict != "OK":
|
|
147
255
|
return verdict
|
|
148
256
|
|
|
149
257
|
return "OK"
|
|
150
258
|
|
|
151
259
|
|
|
260
|
+
def _cd_target(toks: list, cwd):
|
|
261
|
+
"""The directory a `cd` moves to, or "" when it cannot be read literally
|
|
262
|
+
(`cd`, `cd -`, `cd ~`, `cd "$X"`): a push after it is on an unknown branch.
|
|
263
|
+
Once unknown, a relative cd stays unknown; only an absolute one resolves."""
|
|
264
|
+
args = [t for t in toks[1:] if not t.startswith("-")]
|
|
265
|
+
if not args or any(ch in args[0] for ch in "$`~*?"):
|
|
266
|
+
return ""
|
|
267
|
+
if os.path.isabs(args[0]):
|
|
268
|
+
return os.path.normpath(args[0])
|
|
269
|
+
if cwd == "":
|
|
270
|
+
return ""
|
|
271
|
+
base = cwd if cwd else os.getcwd()
|
|
272
|
+
return os.path.normpath(os.path.join(base, args[0]))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _branch_of(directory: str) -> str:
|
|
276
|
+
"""The branch checked out in `directory`, read with the same read-only
|
|
277
|
+
`git rev-parse` the wrapper runs for the cwd. "" when it cannot be read."""
|
|
278
|
+
if not directory:
|
|
279
|
+
return ""
|
|
280
|
+
try:
|
|
281
|
+
import subprocess
|
|
282
|
+
|
|
283
|
+
out = subprocess.run(
|
|
284
|
+
["git", "-C", directory, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
285
|
+
capture_output=True,
|
|
286
|
+
text=True,
|
|
287
|
+
timeout=3,
|
|
288
|
+
)
|
|
289
|
+
return out.stdout.strip() if out.returncode == 0 else ""
|
|
290
|
+
except Exception:
|
|
291
|
+
return ""
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _parse_push(toks: list):
|
|
295
|
+
"""(git_dir, options, positionals) for a tokenized `git ... push ...`.
|
|
296
|
+
|
|
297
|
+
git_dir is the directory named by `-C` / `--work-tree` / `--git-dir`, or
|
|
298
|
+
None. Values of options that take one are consumed, never read as a
|
|
299
|
+
repository or refspec.
|
|
300
|
+
"""
|
|
301
|
+
git_dir, i = None, 0
|
|
302
|
+
while i < len(toks) and os.path.basename(toks[i]).lower() != "git":
|
|
303
|
+
i += 1
|
|
304
|
+
i += 1
|
|
305
|
+
while i < len(toks):
|
|
306
|
+
t = toks[i]
|
|
307
|
+
if t in ("-C", "--work-tree", "--git-dir"):
|
|
308
|
+
if i + 1 < len(toks):
|
|
309
|
+
val = toks[i + 1]
|
|
310
|
+
if t == "--git-dir":
|
|
311
|
+
val = os.path.dirname(val.rstrip("/")) or "."
|
|
312
|
+
git_dir = val if git_dir is None or os.path.isabs(val) else os.path.join(git_dir, val)
|
|
313
|
+
i += 2
|
|
314
|
+
continue
|
|
315
|
+
if t.startswith("--work-tree=") or t.startswith("--git-dir="):
|
|
316
|
+
key, _, val = t.partition("=")
|
|
317
|
+
if key == "--git-dir":
|
|
318
|
+
val = os.path.dirname(val.rstrip("/")) or "."
|
|
319
|
+
git_dir = val
|
|
320
|
+
i += 1
|
|
321
|
+
continue
|
|
322
|
+
if t in GIT_VALUE_OPTIONS:
|
|
323
|
+
i += 2
|
|
324
|
+
continue
|
|
325
|
+
if t.startswith("-"):
|
|
326
|
+
i += 1
|
|
327
|
+
continue
|
|
328
|
+
break
|
|
329
|
+
# toks[i] is the subcommand
|
|
330
|
+
opts, positionals = [], []
|
|
331
|
+
j = i + 1
|
|
332
|
+
while j < len(toks):
|
|
333
|
+
t = toks[j]
|
|
334
|
+
if t in PUSH_VALUE_OPTIONS:
|
|
335
|
+
opts.append((t, toks[j + 1] if j + 1 < len(toks) else ""))
|
|
336
|
+
j += 2
|
|
337
|
+
continue
|
|
338
|
+
if t.startswith("-"):
|
|
339
|
+
key, _, val = t.partition("=")
|
|
340
|
+
opts.append((key, val))
|
|
341
|
+
else:
|
|
342
|
+
positionals.append(t)
|
|
343
|
+
j += 1
|
|
344
|
+
return git_dir, opts, positionals
|
|
345
|
+
|
|
346
|
+
|
|
152
347
|
def _ref_target(arg: str) -> tuple:
|
|
153
|
-
"""(forced, branch) for one
|
|
348
|
+
"""(forced, branch) for one refspec.
|
|
154
349
|
|
|
155
350
|
The destination is the part after the last `:`; a leading `+` on it is a
|
|
156
|
-
force-push; `refs/heads/main` names the same branch as `main`.
|
|
351
|
+
force-push; `refs/heads/main` names the same branch as `main`. A refspec
|
|
352
|
+
with no `:` pushes to the branch of the same name.
|
|
157
353
|
"""
|
|
158
354
|
dst = arg.rsplit(":", 1)[-1]
|
|
159
355
|
forced = dst.startswith("+") or arg.startswith("+")
|
|
@@ -163,51 +359,166 @@ def _ref_target(arg: str) -> tuple:
|
|
|
163
359
|
return forced, name
|
|
164
360
|
|
|
165
361
|
|
|
166
|
-
def _decide_push(cmd: str) -> str:
|
|
167
|
-
"""Verdict for a single `git push ...` command.
|
|
362
|
+
def _decide_push(cmd: str, toks=None, cwd=None) -> str:
|
|
363
|
+
"""Verdict for a single `git push ...` command.
|
|
364
|
+
|
|
365
|
+
`git push [options] [<repository> [<refspec>...]]`: the first positional is
|
|
366
|
+
always the repository, whatever it is called; everything after it is a
|
|
367
|
+
refspec. A refspec of HEAD or @, or none at all, targets the branch that is
|
|
368
|
+
checked out in the directory the push runs in.
|
|
369
|
+
"""
|
|
370
|
+
if toks is None:
|
|
371
|
+
toks = tokenize(cmd)
|
|
168
372
|
force_flag = bool(FORCE.search(cmd))
|
|
169
|
-
if not force_flag and not FORCED_REFSPEC.search(cmd):
|
|
373
|
+
if not force_flag and not FORCED_REFSPEC.search(cmd) and not PUSH_GATE.search(cmd):
|
|
170
374
|
return "OK"
|
|
171
375
|
try:
|
|
172
|
-
|
|
376
|
+
shlex.split(cmd)
|
|
173
377
|
except Exception:
|
|
174
378
|
# A force-push we can't tokenize -> can't prove it's safe -> block.
|
|
175
379
|
return "BLOCK_FORCE"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
380
|
+
git_dir, opts, positionals = _parse_push(toks)
|
|
381
|
+
keys = {k for k, _ in opts}
|
|
382
|
+
force_flag = any(
|
|
383
|
+
k in ("--force", "--force-with-lease", "--force-if-includes") or SHORT_FORCE.match(k) for k in keys
|
|
384
|
+
)
|
|
385
|
+
repo_opt = any(k == "--repo" for k in keys)
|
|
386
|
+
refspecs = positionals if repo_opt else positionals[1:]
|
|
387
|
+
if "--mirror" in keys:
|
|
388
|
+
return "BLOCK_FORCE"
|
|
389
|
+
deleting = bool(keys & PUSH_DELETE) or any(r.lstrip("+").startswith(":") for r in refspecs)
|
|
390
|
+
|
|
391
|
+
if git_dir is not None:
|
|
392
|
+
base = cwd if cwd else os.getcwd()
|
|
393
|
+
current = _branch_of(git_dir if os.path.isabs(git_dir) else os.path.join(base, git_dir))
|
|
394
|
+
elif cwd is not None:
|
|
395
|
+
current = _branch_of(cwd)
|
|
396
|
+
else:
|
|
397
|
+
current = os.environ.get("CUR_BRANCH", "")
|
|
398
|
+
|
|
399
|
+
targets = []
|
|
400
|
+
for ref in refspecs:
|
|
401
|
+
forced, name = _ref_target(ref)
|
|
402
|
+
if name in CURRENT_BRANCH_REFS:
|
|
403
|
+
name = current or "HEAD"
|
|
404
|
+
targets.append((forced, name))
|
|
405
|
+
|
|
185
406
|
if any(forced and name in PROTECTED for forced, name in targets):
|
|
186
407
|
return "BLOCK_FORCE"
|
|
187
|
-
if force_flag:
|
|
188
|
-
|
|
408
|
+
if not (force_flag or deleting):
|
|
409
|
+
return "OK"
|
|
410
|
+
if keys & PUSH_ALL_REFS:
|
|
411
|
+
return "BLOCK_FORCE"
|
|
412
|
+
if any(name in PROTECTED for _, name in targets):
|
|
413
|
+
return "BLOCK_FORCE"
|
|
414
|
+
if "--prune" in keys and (not targets or any("*" in name for _, name in targets)):
|
|
415
|
+
return "BLOCK_FORCE"
|
|
416
|
+
unresolved = any(name == "HEAD" for _, name in targets)
|
|
417
|
+
if force_flag and (not targets or unresolved):
|
|
418
|
+
# The target is the current branch. Block if it's protected OR
|
|
419
|
+
# undeterminable (fail-closed for a data-loss guard).
|
|
420
|
+
if current in PROTECTED or current in ("", "HEAD"):
|
|
189
421
|
return "BLOCK_FORCE"
|
|
190
|
-
nonremote = [a for a in args if a not in REMOTES]
|
|
191
|
-
if not nonremote:
|
|
192
|
-
# Bare force-push: the target is the current branch. Block if it's
|
|
193
|
-
# protected OR undeterminable (fail-closed for a data-loss guard).
|
|
194
|
-
cur = os.environ.get("CUR_BRANCH", "")
|
|
195
|
-
if cur in PROTECTED or cur == "":
|
|
196
|
-
return "BLOCK_FORCE"
|
|
197
|
-
|
|
198
422
|
return "OK"
|
|
199
423
|
|
|
200
424
|
|
|
425
|
+
def unattended() -> bool:
|
|
426
|
+
"""MULTI_AGENT_UNATTENDED=1 in the hook's own environment - the launcher's,
|
|
427
|
+
never anything the run wrote (lib/unattended.sh ma_unattended)."""
|
|
428
|
+
return os.environ.get("MULTI_AGENT_UNATTENDED", "") == "1"
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def git_subcommand_mode() -> None:
|
|
432
|
+
"""`agent-guard.py --git-subcommand`: read a Bash payload (or a raw command
|
|
433
|
+
on argv) and print each git subcommand it runs, one per line, using the same
|
|
434
|
+
tokenizer the guard uses. pre-commit-check.sh reads this to detect a commit
|
|
435
|
+
without a regex. Never executes anything."""
|
|
436
|
+
if len(sys.argv) > 2:
|
|
437
|
+
cmd = sys.argv[2]
|
|
438
|
+
else:
|
|
439
|
+
try:
|
|
440
|
+
data = json.load(sys.stdin)
|
|
441
|
+
cmd = (data.get("tool_input") or {}).get("command", "")
|
|
442
|
+
except Exception:
|
|
443
|
+
cmd = ""
|
|
444
|
+
if not isinstance(cmd, str) or not cmd:
|
|
445
|
+
return
|
|
446
|
+
try:
|
|
447
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
448
|
+
import unattended_policy
|
|
449
|
+
|
|
450
|
+
for sub in unattended_policy.git_subcommands(cmd):
|
|
451
|
+
print(sub)
|
|
452
|
+
except Exception:
|
|
453
|
+
# Fall back to the git subcommand of the whole token list; a caller that
|
|
454
|
+
# gets nothing scans anyway, so silence here only costs an extra scan.
|
|
455
|
+
subs = git_subcommand(tokenize(cmd))
|
|
456
|
+
if subs:
|
|
457
|
+
print(subs)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def commit_dir_mode() -> None:
|
|
461
|
+
"""`agent-guard.py --commit-dir`: read a Bash payload and print the
|
|
462
|
+
directory the first commit-writing git command on the line runs in, after
|
|
463
|
+
any `cd` / `git -C`, resolved from the payload's cwd. Prints nothing when
|
|
464
|
+
there is no commit or the directory cannot be read literally, so the caller
|
|
465
|
+
keeps its own cwd. Never executes anything."""
|
|
466
|
+
try:
|
|
467
|
+
data = json.load(sys.stdin)
|
|
468
|
+
cmd = (data.get("tool_input") or {}).get("command", "")
|
|
469
|
+
base = data.get("cwd") or os.getcwd()
|
|
470
|
+
except Exception:
|
|
471
|
+
return
|
|
472
|
+
if not isinstance(cmd, str) or not cmd:
|
|
473
|
+
return
|
|
474
|
+
try:
|
|
475
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
476
|
+
import unattended_policy
|
|
477
|
+
|
|
478
|
+
dirs = unattended_policy.git_commit_dirs(cmd, base)
|
|
479
|
+
except Exception:
|
|
480
|
+
return
|
|
481
|
+
if dirs and dirs[0] and os.path.isdir(dirs[0]):
|
|
482
|
+
print(dirs[0])
|
|
483
|
+
|
|
484
|
+
|
|
201
485
|
def main() -> None:
|
|
486
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--git-subcommand":
|
|
487
|
+
git_subcommand_mode()
|
|
488
|
+
return
|
|
489
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--commit-dir":
|
|
490
|
+
commit_dir_mode()
|
|
491
|
+
return
|
|
492
|
+
strict = unattended()
|
|
493
|
+
|
|
494
|
+
def _deadline(_signum, _frame):
|
|
495
|
+
# os._exit skips the stdio flush, so the verdict is flushed first or
|
|
496
|
+
# the wrapper reads nothing.
|
|
497
|
+
print("BLOCK_UNATTENDED\tthe guard hit its internal deadline" if strict else "OK", flush=True)
|
|
498
|
+
os._exit(0)
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
signal.signal(signal.SIGALRM, _deadline)
|
|
502
|
+
signal.setitimer(signal.ITIMER_REAL, deadline_seconds())
|
|
503
|
+
except (ValueError, AttributeError, OSError):
|
|
504
|
+
pass
|
|
202
505
|
try:
|
|
203
506
|
data = json.load(sys.stdin)
|
|
204
507
|
cmd = (data.get("tool_input") or {}).get("command", "")
|
|
205
|
-
if
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
508
|
+
verdict = decide(cmd) if isinstance(cmd, str) and cmd else "OK"
|
|
509
|
+
if verdict == "OK" and strict:
|
|
510
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
511
|
+
import unattended_policy
|
|
512
|
+
|
|
513
|
+
reason = unattended_policy.check(data)
|
|
514
|
+
if reason:
|
|
515
|
+
verdict = "BLOCK_UNATTENDED\t" + reason.replace("\n", " ")
|
|
516
|
+
print(verdict)
|
|
209
517
|
except Exception:
|
|
210
|
-
|
|
518
|
+
# Attended: a guard bug must not break a legitimate tool call.
|
|
519
|
+
# Unattended: nobody is there to notice a gap, so an input the guard
|
|
520
|
+
# cannot judge is refused.
|
|
521
|
+
print("BLOCK_PARSE" if strict else "OK")
|
|
211
522
|
|
|
212
523
|
|
|
213
524
|
if __name__ == "__main__":
|
|
@@ -1,33 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# agent-guard.sh - PreToolUse
|
|
2
|
+
# agent-guard.sh - PreToolUse guard for the multi-agent pipeline.
|
|
3
3
|
#
|
|
4
|
-
# Turns
|
|
4
|
+
# Turns prompt-level rules into deterministic, OS-enforced gates:
|
|
5
5
|
# 1. No AI/assistant attribution in git commit messages (Co-Authored-By: Claude,
|
|
6
6
|
# "Generated with Claude Code", robot emoji, Anthropic no-reply address).
|
|
7
|
-
# 2. No force-push to a protected branch (main / master / develop)
|
|
7
|
+
# 2. No force-push to a protected branch (main / master / develop), whichever
|
|
8
|
+
# remote, push options or `git -C` directory the push names.
|
|
9
|
+
# 3. With MULTI_AGENT_UNATTENDED=1 in the hook's environment only: no outward
|
|
10
|
+
# writes (push, PR, issue, tracker publishers, mutating API calls), no
|
|
11
|
+
# Keychain reads, no fetch outside the network allowlist, no package
|
|
12
|
+
# installs or manifest edits, no writes into protected paths
|
|
13
|
+
# (scripts/unattended_policy.py, schemas/unattended-policy.json).
|
|
8
14
|
#
|
|
9
|
-
# Contract (Claude Code PreToolUse hook):
|
|
10
|
-
# - Reads the tool-call JSON on stdin: {"tool_input":{
|
|
15
|
+
# Contract (Claude Code PreToolUse hook, matcher Bash and Edit|Write|NotebookEdit):
|
|
16
|
+
# - Reads the tool-call JSON on stdin: {"tool_name":..., "tool_input":{...}, "cwd":...}.
|
|
11
17
|
# - Exit 2 -> BLOCK the tool call (reason on stderr, shown to the model).
|
|
12
18
|
# - Exit 0 -> allow.
|
|
13
19
|
#
|
|
14
|
-
# Safety design
|
|
20
|
+
# Safety design:
|
|
15
21
|
# - The command string is ONLY parsed/pattern-matched, NEVER executed or eval'd
|
|
16
22
|
# (the decision core is agent-guard.py; shlex tokenizes without running).
|
|
17
|
-
# -
|
|
18
|
-
# A guard bug must never break a legitimate tool call.
|
|
23
|
+
# - Attended: any internal error (bad JSON, missing python3/helper, empty
|
|
24
|
+
# input) -> exit 0. A guard bug must never break a legitimate tool call.
|
|
25
|
+
# - Unattended: the same errors -> exit 2. Nobody is watching to notice a gap.
|
|
19
26
|
# - No network, no file writes, no secret values printed.
|
|
20
|
-
# - The
|
|
27
|
+
# - The execs are read-only `git rev-parse` / `git diff --name-only` calls.
|
|
21
28
|
|
|
22
29
|
set -u
|
|
23
30
|
|
|
24
31
|
HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || true)"
|
|
25
32
|
HELPER="$HERE/agent-guard.py"
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
|
|
34
|
+
STRICT=0
|
|
35
|
+
[ "${MULTI_AGENT_UNATTENDED:-}" = "1" ] && STRICT=1
|
|
36
|
+
|
|
37
|
+
refuse_unjudged() {
|
|
38
|
+
if [ "$STRICT" = "1" ]; then
|
|
39
|
+
echo "BLOCKED by agent-guard (unattended): $1, so this tool call cannot be judged and is refused." >&2
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
42
|
+
exit 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[ -f "$HELPER" ] || refuse_unjudged "agent-guard.py is missing"
|
|
46
|
+
command -v python3 >/dev/null 2>&1 || refuse_unjudged "python3 is not on PATH"
|
|
28
47
|
|
|
29
48
|
PAYLOAD="$(cat 2>/dev/null || true)"
|
|
30
|
-
[ -z "$PAYLOAD" ] &&
|
|
49
|
+
[ -z "$PAYLOAD" ] && refuse_unjudged "the hook received no payload"
|
|
31
50
|
|
|
32
51
|
CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
33
52
|
|
|
@@ -43,6 +62,15 @@ case "$DECISION" in
|
|
|
43
62
|
echo "BLOCKED by agent-guard: force-push to a protected branch (main/master/develop) is not allowed." >&2
|
|
44
63
|
echo "Rewriting shared history is a data-loss risk. Push a normal commit, or force-push a feature branch." >&2
|
|
45
64
|
exit 2 ;;
|
|
46
|
-
*)
|
|
65
|
+
BLOCK_UNATTENDED*)
|
|
66
|
+
echo "BLOCKED by agent-guard (unattended policy): ${DECISION#BLOCK_UNATTENDED?}." >&2
|
|
67
|
+
echo "This run has nobody watching it. Outward writes go through pr-request.json and the runner;" >&2
|
|
68
|
+
echo "see multi-agent-refs/features/unattended-security.md. Record what is needed and continue." >&2
|
|
69
|
+
exit 2 ;;
|
|
70
|
+
BLOCK_PARSE)
|
|
71
|
+
refuse_unjudged "the guard could not parse the tool call" ;;
|
|
72
|
+
OK)
|
|
47
73
|
exit 0 ;;
|
|
74
|
+
*)
|
|
75
|
+
refuse_unjudged "the guard returned no decision" ;;
|
|
48
76
|
esac
|
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
* after `~/.claude` is deleted, and for a SECOND ANALYST - who is precisely the
|
|
39
39
|
* person positioned to open a duplicate tree.
|
|
40
40
|
*
|
|
41
|
+
* THE SPEC HAS TO BE CONSISTENT, NOT ONLY CLOSED
|
|
42
|
+
*
|
|
43
|
+
* After planning, `spec-consistency-gate.mjs` checks the document against the
|
|
44
|
+
* planned stories: every requirement in a story and in a Test Plan row, and no
|
|
45
|
+
* row that waives a binding constitution rule. With the quality gates active
|
|
46
|
+
* (lib/unattended.mjs, gatesActive) a gap is exit 4, the same refusal as an
|
|
47
|
+
* open placeholder, and the verdict is recorded in the gate ledger. Attended,
|
|
48
|
+
* it is an advisory `consistency:` line in the preview and a `consistency`
|
|
49
|
+
* field in the JSON, and the exit code is what it was.
|
|
50
|
+
*
|
|
41
51
|
* Usage:
|
|
42
52
|
* analysis-story-tree.mjs <analysis.md> [--json] [--prefs FILE]
|
|
43
53
|
*
|
|
@@ -47,7 +57,18 @@
|
|
|
47
57
|
import { readFileSync, existsSync } from "node:fs";
|
|
48
58
|
import { createHash } from "node:crypto";
|
|
49
59
|
import { homedir } from "node:os";
|
|
50
|
-
import { join } from "node:path";
|
|
60
|
+
import { dirname, join } from "node:path";
|
|
61
|
+
import { hostCommand } from "../lib/user-facing.mjs";
|
|
62
|
+
import { resolveConstitution } from "./constitution.mjs";
|
|
63
|
+
import { gateContext, recordQuietly } from "./gate-ledger.mjs";
|
|
64
|
+
import {
|
|
65
|
+
describe,
|
|
66
|
+
detail as consistencyDetail,
|
|
67
|
+
evaluate,
|
|
68
|
+
LEDGER,
|
|
69
|
+
storyTasks,
|
|
70
|
+
summary,
|
|
71
|
+
} from "./spec-consistency-gate.mjs";
|
|
51
72
|
|
|
52
73
|
const argv = process.argv.slice(2);
|
|
53
74
|
const JSON_OUT = argv.includes("--json");
|
|
@@ -364,7 +385,28 @@ export function plan(text, prefs) {
|
|
|
364
385
|
};
|
|
365
386
|
}
|
|
366
387
|
|
|
367
|
-
|
|
388
|
+
/**
|
|
389
|
+
* The consistency verdict for this document and its planned stories.
|
|
390
|
+
*
|
|
391
|
+
* @returns {object} a spec-consistency-gate result
|
|
392
|
+
*/
|
|
393
|
+
function consistencyOf(text, nodes) {
|
|
394
|
+
const constitution = resolveConstitution({
|
|
395
|
+
repo: dirname(file),
|
|
396
|
+
feature: frontMatter(text).feature || null,
|
|
397
|
+
});
|
|
398
|
+
if (constitution.error) {
|
|
399
|
+
return { gate: "spec-consistency", verdict: "unreadable", reason: constitution.error };
|
|
400
|
+
}
|
|
401
|
+
return evaluate({
|
|
402
|
+
docs: [{ path: file, text }],
|
|
403
|
+
tasks: storyTasks(nodes),
|
|
404
|
+
taskSource: "story-tree",
|
|
405
|
+
constitution,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function main() {
|
|
368
410
|
if (!file) return die("usage: analysis-story-tree.mjs <analysis.md> [--json] [--prefs FILE]", 3);
|
|
369
411
|
if (!existsSync(file)) return die(`analysis-story-tree: no such file: ${file}`, 3);
|
|
370
412
|
const text = readFileSync(file, "utf8");
|
|
@@ -378,7 +420,9 @@ function main() {
|
|
|
378
420
|
`analysis-story-tree: the document is not issue-ready (${open.length} open item(s))\n`,
|
|
379
421
|
);
|
|
380
422
|
for (const m of open.slice(0, 5)) process.stderr.write(` ${m}\n`);
|
|
381
|
-
process.stderr.write(
|
|
423
|
+
process.stderr.write(
|
|
424
|
+
` run ${hostCommand("analysis-resolve", import.meta.dirname)} to close them first\n`,
|
|
425
|
+
);
|
|
382
426
|
process.exitCode = 4;
|
|
383
427
|
return;
|
|
384
428
|
}
|
|
@@ -401,6 +445,33 @@ function main() {
|
|
|
401
445
|
return;
|
|
402
446
|
}
|
|
403
447
|
|
|
448
|
+
const ctx = gateContext({ repo: process.cwd() });
|
|
449
|
+
const consistency = consistencyOf(text, result.nodes);
|
|
450
|
+
if (ctx.active) {
|
|
451
|
+
await recordQuietly(
|
|
452
|
+
"spec-consistency",
|
|
453
|
+
LEDGER[consistency.verdict],
|
|
454
|
+
{
|
|
455
|
+
verdict: consistency.verdict,
|
|
456
|
+
source: "analysis-jira",
|
|
457
|
+
...(consistency.reason ? { reason: consistency.reason } : {}),
|
|
458
|
+
},
|
|
459
|
+
{ repo: process.cwd(), statePath: ctx.statePath },
|
|
460
|
+
);
|
|
461
|
+
if (consistency.verdict === "gap" || consistency.verdict === "unreadable") {
|
|
462
|
+
process.stderr.write(
|
|
463
|
+
`analysis-story-tree: the document is not issue-ready (spec-consistency: ${summary(consistency)})\n`,
|
|
464
|
+
);
|
|
465
|
+
for (const line of describe(consistency).slice(0, 10)) process.stderr.write(`${line}\n`);
|
|
466
|
+
process.stderr.write(
|
|
467
|
+
` add the missing rows to the document, or re-run ${hostCommand("analysis", import.meta.dirname)}, then plan again\n`,
|
|
468
|
+
);
|
|
469
|
+
process.exitCode = 4;
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
result.consistency = ctx.active ? consistency : { ...consistency, advisory: true };
|
|
474
|
+
|
|
404
475
|
if (JSON_OUT) {
|
|
405
476
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
406
477
|
return;
|
|
@@ -417,6 +488,11 @@ function main() {
|
|
|
417
488
|
);
|
|
418
489
|
if (c.uncovered.length) process.stdout.write(` not in any story: ${c.uncovered.join(", ")}\n`);
|
|
419
490
|
if (c.invented.length) process.stdout.write(` cited but undefined: ${c.invented.join(", ")}\n`);
|
|
491
|
+
const k = result.consistency;
|
|
492
|
+
process.stdout.write(
|
|
493
|
+
`consistency: ${k.verdict} (${k.advisory ? "advisory, " : ""}${consistencyDetail(k)})\n`,
|
|
494
|
+
);
|
|
495
|
+
for (const line of describe(k)) process.stdout.write(`${line}\n`);
|
|
420
496
|
process.stdout.write(`\n${result.writeCount} issue(s) would be created:\n`);
|
|
421
497
|
for (const s of result.nodes) {
|
|
422
498
|
process.stdout.write(` ${result.prefsUsed.storyIssueType} ${s.title}\n`);
|