@mmerterden/multi-agent-pipeline 20.2.1 → 20.3.0
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 +912 -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 +616 -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/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 +185 -65
- package/pipeline/scripts/usage-report.mjs +230 -66
- 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
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
# Feature: Unattended Security
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [What turns it on](#what-turns-it-on)
|
|
5
|
+
- [The guard](#the-guard)
|
|
6
|
+
- [Phase 4: a PR request instead of a push](#phase-4-a-pr-request-instead-of-a-push)
|
|
7
|
+
- [The runner's launch](#the-runners-launch)
|
|
8
|
+
- [The publish step](#the-publish-step)
|
|
9
|
+
- [Phase 5 and channels](#phase-5-and-channels)
|
|
10
|
+
- [What leaves the machine is scanned](#what-leaves-the-machine-is-scanned)
|
|
11
|
+
- [Text a run did not write is data](#text-a-run-did-not-write-is-data)
|
|
12
|
+
- [The OS sandbox is the boundary](#the-os-sandbox-is-the-boundary)
|
|
13
|
+
- [Machine setup (applied by the operator)](#machine-setup-applied-by-the-operator)
|
|
14
|
+
- [Hook timeout and cost](#hook-timeout-and-cost)
|
|
15
|
+
- [The phone routes](#the-phone-routes)
|
|
16
|
+
- [Residual risk, accepted and stated](#residual-risk-accepted-and-stated)
|
|
17
|
+
- [Reference](#reference)
|
|
18
|
+
<!-- /toc -->
|
|
19
|
+
|
|
20
|
+
**Pattern**: a run nobody watches is also a run nobody can stop in time. Its
|
|
21
|
+
input is text other people wrote (a ticket, a page, a PR thread), so it has to be
|
|
22
|
+
assumed that some of that text will ask for something the task never did. This
|
|
23
|
+
feature keeps an unattended run's reach to its own worktree: it can build, test
|
|
24
|
+
and commit, and everything that leaves the machine goes through the autopilot
|
|
25
|
+
runner, after the runner checked it again outside the run. An attended run is unchanged.
|
|
26
|
+
|
|
27
|
+
## What turns it on
|
|
28
|
+
|
|
29
|
+
`MULTI_AGENT_UNATTENDED=1` in the hook's own environment, and nothing else. The
|
|
30
|
+
autopilot runner sets it on the `claude` process it spawns, and hooks inherit
|
|
31
|
+
it. It is never read from `agent-state.json` or any file the run writes: a rule
|
|
32
|
+
the run can switch off is not a rule. `lib/unattended.sh` (`ma_unattended`) and
|
|
33
|
+
`lib/unattended.mjs` (`isUnattended`) answer the question; `agent-guard.py` reads
|
|
34
|
+
the variable the same way.
|
|
35
|
+
|
|
36
|
+
With the variable unset every hook behaves as it did without this feature:
|
|
37
|
+
`smoke-unattended-redteam.sh` runs the same inputs both ways, and
|
|
38
|
+
`test/unattended-guard.test.mjs` holds the attended verdicts.
|
|
39
|
+
|
|
40
|
+
## The guard
|
|
41
|
+
|
|
42
|
+
`scripts/agent-guard.sh` is the PreToolUse hook on `Bash`, on
|
|
43
|
+
`Edit|Write|NotebookEdit`, and on `WebFetch|mcp__multi-agent-toolkit__.*`
|
|
44
|
+
(`install/claude.mjs` registers all three; a matcher is a regex over the tool
|
|
45
|
+
name, so the last one covers `WebFetch` and every toolkit tool, including
|
|
46
|
+
`agent_run_steps`, which dispatches the web tools, and the `ios_open_url` /
|
|
47
|
+
`android_open_url` tools, which open a URL in the device browser). The
|
|
48
|
+
"already present" check for the Bash guard requires the tool-name matcher
|
|
49
|
+
`Bash`, a guard entry written under a `Bash(...)` command filter - which a
|
|
50
|
+
hook never matches - is migrated to `Bash` in place, and the web-tools-only
|
|
51
|
+
matcher `WebFetch|mcp__multi-agent-toolkit__web_.*` an older install wrote is
|
|
52
|
+
migrated to the wider one. `install/templates/claude-hooks.json` carries the
|
|
53
|
+
same three registrations. Its decision core is
|
|
54
|
+
`agent-guard.py`; the unattended rules are `scripts/unattended_policy.py` and the
|
|
55
|
+
lists they read are `schemas/unattended-policy.json`.
|
|
56
|
+
|
|
57
|
+
In every mode it blocks AI attribution in commit messages and a force-push to
|
|
58
|
+
main, master or develop, including `git push -f origin HEAD`, a remote with any
|
|
59
|
+
name, `-o` / `--push-option` values, and a push that names another checkout
|
|
60
|
+
with `git -C <dir>` or follows a `cd`. A push that removes a protected branch
|
|
61
|
+
without a force flag is blocked the same way: `--delete` / `-d`, a refspec with
|
|
62
|
+
an empty source (`git push origin :main`), `--mirror` (always), and `--prune`
|
|
63
|
+
with a glob refspec or none. The rule reads through a subshell or group
|
|
64
|
+
(`(git push -f origin main)`), a `sh -c` / `bash -c` / `zsh -lc` payload and an
|
|
65
|
+
`eval`, and a `cd` it cannot read literally (`cd "$W"`) leaves the directory
|
|
66
|
+
unknown for every relative `cd` after it, so a bare force-push there is
|
|
67
|
+
blocked.
|
|
68
|
+
|
|
69
|
+
### The model under the variable: refuse what it cannot parse
|
|
70
|
+
|
|
71
|
+
A list of bad commands cannot be complete. Under the variable the guard
|
|
72
|
+
inverts the default: a Bash command is judged only when it reduces to
|
|
73
|
+
**simple commands joined by `;`, `&&`, `||` and plain pipes between
|
|
74
|
+
non-interpreter commands**. Anything that hides a command from a line reader is
|
|
75
|
+
refused outright, before any family check runs:
|
|
76
|
+
|
|
77
|
+
- a subshell or group - `(`, `)`, `{`, `}` (outside quotes)
|
|
78
|
+
- a backtick, `<(...)` or `>(...)` substitution, and `$((...))` arithmetic
|
|
79
|
+
- a `$(...)` the guard cannot analyse: one nested inside another, an
|
|
80
|
+
unterminated one, or one in command position (`$(echo git) push`)
|
|
81
|
+
- a heredoc with an unquoted delimiter (its body expands `$(...)`)
|
|
82
|
+
- a `${...}` other than a plain `${NAME}`
|
|
83
|
+
- a background job - a bare `&` (not `&&`, `&>`, `>&` or `2>&1`)
|
|
84
|
+
- a shell keyword in command position - `if`, `then`, `for`, `while`, `case`,
|
|
85
|
+
`function`, `eval`, `exec`, `source`, `.`, `export`, `trap`, `set`, `unset`,
|
|
86
|
+
`alias`, and the rest
|
|
87
|
+
- an interpreter reading its program from stdin - `echo ... | bash`,
|
|
88
|
+
`python3 -`, `bash <<< '...'` (a pipe into `node script.mjs` only passes
|
|
89
|
+
data and is judged like running the script), or `xargs` with a command
|
|
90
|
+
- a `-c` cluster on a shell or Python - `bash -lc "..."`, `sh -c ...`,
|
|
91
|
+
`python3 -c ...`
|
|
92
|
+
- an inline program on any other interpreter - `node -e/-p/--eval/--print`,
|
|
93
|
+
`perl -e/-E` (in a cluster too, `-lne`), `ruby -e`, `osascript -e`,
|
|
94
|
+
`php -r/-R/-B/-E`, `lua -e`, `Rscript -e`, `swift -e`, `bun -e/--eval/--print`,
|
|
95
|
+
`deno eval` / `deno repl`
|
|
96
|
+
- a program file the run could have written. An interpreter's script
|
|
97
|
+
(`bash x.sh`, `python3 x.py`, `node x.mjs`, `osascript x.scpt`, `tclsh`,
|
|
98
|
+
`swift x.swift`, `bun`/`deno run <file>`), a module `python3 -m` finds in the
|
|
99
|
+
cwd, a command named by path (`./x.sh`, also behind `nohup`, `env`,
|
|
100
|
+
`timeout`), an `awk -f` / `sed -f` program file and the Makefile `make` reads
|
|
101
|
+
must be one of: under `~/.claude/scripts` or `~/.claude/lib` (the installed
|
|
102
|
+
pipeline, itself write-protected), a file the user cannot write, a binary in
|
|
103
|
+
`node_modules/.bin`, or a file tracked by git and unmodified against HEAD
|
|
104
|
+
(staged, unstaged and untracked changes all count as modified)
|
|
105
|
+
- `make` with `--eval`, or with a `VAR=value` operand a recipe could expand
|
|
106
|
+
- an awk program with `system(`, a pipe (`|`, not `||`), `getline`, a `print >`
|
|
107
|
+
redirect, `@load` or `@include`, and awk's `-i` / `-l` / `-E` loaders
|
|
108
|
+
- a sed program with a `w` / `W` / `e` command or the `w` / `e` flag of `s///`
|
|
109
|
+
- `script` and `watch`, which run the rest of the line in a way the guard does
|
|
110
|
+
not model, and `env -S` / `env -C`, which re-split the command or move it
|
|
111
|
+
- a command name built at run time - a first token starting with `$`
|
|
112
|
+
- `sudo`, `doas`, and `find -exec` / `-execdir` / `-ok`
|
|
113
|
+
- more than 50 segments (refused unparsed, so a 300-`npm ci;` line ending in a
|
|
114
|
+
push is rejected in milliseconds, not after 300 git reads)
|
|
115
|
+
|
|
116
|
+
**`$(...)` is analysed, not refused.** Each top-level substitution is
|
|
117
|
+
extracted quote-aware and balanced, and its inner command goes through the same
|
|
118
|
+
parser and every policy check as if it were typed directly, in the cwd its
|
|
119
|
+
segment runs in; the outer command is then judged with the substitution replaced
|
|
120
|
+
by a placeholder. `SHA=$(git rev-parse HEAD)` passes; a substitution whose inner
|
|
121
|
+
command pushes, POSTs, reads a credential or writes a protected path is refused
|
|
122
|
+
for what that inner command does. A substitution-built value can never name a
|
|
123
|
+
command, a git or gh subcommand, a publisher verb or a write target. A heredoc
|
|
124
|
+
behind a quoted delimiter (`<<'EOF'`) is stripped as literal data before
|
|
125
|
+
parsing, so `git commit -m "$(cat <<'EOF' ... EOF)"` works. Segments and
|
|
126
|
+
redirections are split quote-aware, so a `|` or `;` inside a quoted sed, jq or
|
|
127
|
+
awk program is text. Variables assigned earlier in the same command
|
|
128
|
+
(`D=build; ... > "$D/x"`) and `$HOME` are expanded; a write target or `cd` whose
|
|
129
|
+
variable cannot be resolved is refused, and so is a relative write after an
|
|
130
|
+
unresolvable `cd`.
|
|
131
|
+
|
|
132
|
+
**The documented steps are written to pass.** The guard judges each Bash call
|
|
133
|
+
alone, so every command in the phase docs is one the guard accepts as written
|
|
134
|
+
once its placeholders are filled.
|
|
135
|
+
Shell control flow lives in installed scripts (`worktree-prepare.sh`,
|
|
136
|
+
`build-lock.sh`, `probe-evidence-capability.sh --platform auto`,
|
|
137
|
+
`triage-memory.mjs prior-art`, `memory-save.sh --from-json`, and the `--out`,
|
|
138
|
+
`--json-out`, `--append`, `--stack-from` and `--analysis-from-state` options of
|
|
139
|
+
the gates) and the doc calls each as one command. A write target is spelled
|
|
140
|
+
`{worktreePath}/...`, the literal path, rather than `$WORKTREE/...`, because the
|
|
141
|
+
guard checks only a target it can read from the command itself; LLM-written JSON
|
|
142
|
+
(analysis, plan, reviewer and triage output) is written with the Write tool. The
|
|
143
|
+
few steps a run does not take carry a `# unattended: skipped` comment on the
|
|
144
|
+
line before them. `test/unattended-doc-lines.test.mjs` runs every command line
|
|
145
|
+
of the bash blocks in `phases/*.md` and `features/unattended-gates.md` through
|
|
146
|
+
the guard (a `NAME=value` line such as `S="$HOME/.claude/scripts"` sets NAME for
|
|
147
|
+
the later lines of the same document), allows only the refusals recorded in
|
|
148
|
+
`test/fixtures/unattended-doc-lines-refused.json`, and fails on any other.
|
|
149
|
+
|
|
150
|
+
Wrappers are stripped WITH their option values before the command is read, so
|
|
151
|
+
`env -u FOO`, `nice -n N`, `timeout -s SIG N`, `nohup`, `caffeinate`,
|
|
152
|
+
`stdbuf`, `command`, `builtin`, `xcrun [--sdk X]`, `arch -arm64` and
|
|
153
|
+
`sandbox-exec -p ...` do not smuggle the wrapped command past the check
|
|
154
|
+
(`xcrun git push` is judged as `git push`; `xcrun --find` runs nothing).
|
|
155
|
+
Assignments the wrapped command receives - leading ones, those after `env`, and
|
|
156
|
+
`arch -e` values - are judged like any other. Command basenames are casefolded
|
|
157
|
+
(`GIT push`), and an absolute path is reduced to its basename (`/usr/bin/git`)
|
|
158
|
+
once the file itself passed the program-file check above.
|
|
159
|
+
|
|
160
|
+
Configuration that makes a later program run code is refused wherever it is
|
|
161
|
+
set. One key list (`GIT_EXEC_CONFIG`) covers `git -c`, `git --config-env`,
|
|
162
|
+
`git clone -c` and `git config`: `alias.*`, `core.hooksPath`,
|
|
163
|
+
`core.sshCommand`, `core.fsmonitor`, `core.editor`, `core.pager`,
|
|
164
|
+
`core.askPass`, `core.gitProxy`, `sequence.editor`, `diff.external`,
|
|
165
|
+
`diff.*.command` / `textconv`, `difftool.*`, `mergetool.*`, `merge.*.driver`,
|
|
166
|
+
`filter.*`, `credential.*`, `gpg.*`, `url.*.insteadOf` / `pushInsteadOf`,
|
|
167
|
+
`include*`, `http.*`, `remote.*`, `branch.*.remote` / `pushRemote`,
|
|
168
|
+
`protocol.*`, `pager.*`, `hook.*`, `submodule.*`, `trailer.*` and the rest.
|
|
169
|
+
`git config` writes to `--global` / `--system` and `git config --edit` are
|
|
170
|
+
refused too. The environment: every `GIT_*` variable except the commit
|
|
171
|
+
identity, date and prompt ones (so `GIT_CONFIG_COUNT` / `KEY_n` / `VALUE_n`,
|
|
172
|
+
`GIT_CONFIG_GLOBAL`, `GIT_EXTERNAL_DIFF`, `GIT_SSH(_COMMAND)`, `GIT_ASKPASS`,
|
|
173
|
+
`GIT_EXEC_PATH`, `GIT_DIR`), `EDITOR` / `VISUAL` / `PAGER` / `GIT_EDITOR` /
|
|
174
|
+
`GIT_PAGER` other than a no-op (`cat`, `true`, `:`, `less`), `BASH_ENV`,
|
|
175
|
+
`ENV`, `PROMPT_COMMAND`, `CDPATH`, `PATH`, `HOME`, `XDG_CONFIG_HOME`,
|
|
176
|
+
`NODE_OPTIONS`, `PYTHONPATH`, `PERL5OPT`, `RUBYOPT`, `LD_*`, `DYLD_*`,
|
|
177
|
+
`npm_config_*` and `MULTI_AGENT*`. Git subcommands that run a command line are
|
|
178
|
+
refused: `rebase --exec` / `-x`, `bisect run`, `submodule foreach`,
|
|
179
|
+
`difftool -x` / `--extcmd`, `filter-branch`, `grep -O`, `--upload-pack`,
|
|
180
|
+
`--receive-pack`, `--exec`, `--template`, and any `ext::` or `fd::`
|
|
181
|
+
transport.
|
|
182
|
+
|
|
183
|
+
Under the variable it also blocks:
|
|
184
|
+
|
|
185
|
+
| Family | Blocked | Why |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| Outward writes | `git push` in any form; `gh pr create/merge/ready/edit/comment/review/close`; `gh issue create/close/edit/comment/...`; `gh release`, `repo`, `workflow`, `secret`, `variable`, `label`, `alias`, `ssh-key`, `gpg-key`, `extension`, `codespace` writes; `gh api` with a mutating method, with `-f`/`-F` (attached forms too) / `--input` and no explicit GET, a GraphQL `mutation`, or a graphql `@file` field; `gh auth` (except `auth status`); `curl`/`wget` with a body or a non-GET method (`-x`, `--proxy`, `--connect-to`, `--resolve`, `-K`, `--unix-socket`, `--doh-url` are refused outright) | the runner is the only writer |
|
|
188
|
+
| Publisher scripts | `jira-publish.sh`, `post-pr-review.sh`, `update-issue-progress.sh`, `analysis-jira-write.sh`, `jira-attach.sh`, `md2confluence-v3.py create/update`, `multi-repo-pipeline.sh push/pr`, `keychain-save.sh`, `credential-store.sh get/set/delete`, `keychain.py get/set/delete`, `gate-ledger.mjs append`, `phone-devices.mjs add/revoke/launch` | the same, a credential never lands in the transcript, gates record verdicts through their own in-process calls (`park` stays allowed: it only stops the run, so there is nothing to forge), and a run never enrols a device that could then command this machine |
|
|
189
|
+
| Git redirection | `git remote add/set-url/rename/remove`; the configuration, environment and subcommands above | each changes what a later command runs or where the runner's push goes |
|
|
190
|
+
| Keychain | `security find-*`, `dump-keychain`, `export`, `add-*password`, `delete-*password`, also behind leading flags (`security -q find-generic-password`) | a run needs no credential it did not get from a fetcher |
|
|
191
|
+
| Network | `curl`/`wget`/`WebFetch`/the toolkit's `web_*` tools to a host outside the allowlist, or to a URL built at run time; `web_eval` outright; every step of `agent_run_steps` judged as if called directly (a nested `agent_*` step or an unreadable step list refused); `ios_open_url` / `android_open_url`, `xcrun simctl openurl` and `open` with a web URL outside the allowlist (an app's own deep-link scheme passes; `open` of anything but an allowed URL is refused); the `research_*` tools outright | an injected "fetch this script" is the first step of most attacks, and a browser opened on a crafted URL sends data out in the query |
|
|
192
|
+
| Packages | `npm/pnpm/yarn/bun install|add <pkg>`, `npm install-test/it <pkg>`, `update`, global installs (`yarn global` too), `npx` / `npm exec` / `npm x` of a binary not already in `node_modules/.bin` (with no TTY npm installs it without asking), `npx --yes/--package/--call`, `pnpm dlx`, `pnpx`, `yarn dlx`, `bunx`, `bun x`, `uvx`, `uv tool run/install`, `pipx run/install`, `pip install <pkg>`, `uv/poetry add`, `gem install`, `bundle add`, `brew install`, `cargo add/install`, `go get`, `go install pkg@v`, `swift package add-dependency`, `pod update`; `npm ci`, `pod install` and `pip install -r` once the run changed the manifest they restore from | slopsquatting: a model asked to add a dependency can name one that does not exist yet, and someone can register it |
|
|
193
|
+
| Host changes | `crontab` (except `-l`), `launchctl` (except the read verbs), `defaults` writes, `altool`, `notarytool` | a job or preference outlives the run and runs as the operator; an upload is outward |
|
|
194
|
+
| Manifests | Edit, Write or a shell write to `Package.swift`, `Package.resolved`, `package.json`, lockfiles, `Podfile(.lock)`, `build.gradle(.kts)`, `settings.gradle(.kts)`, `libs.versions.toml`, `requirements*.txt`, `pyproject.toml`, `go.mod`/`go.sum`, `Cargo.toml`/`Cargo.lock`, `Gemfile(.lock)` | the same decision, made through the file instead of the command |
|
|
195
|
+
| Protected paths | `.github/workflows/`, `CODEOWNERS`, any `.claude/` in a repo, `.mcp.json`, `.git/` (all casefolded), a directory that is an ancestor of one of these (`mv evil .github`), `~/.gitconfig`, `~/.config/git/`, `~/.config/gh/`, `~/.ssh/`, `~/.gnupg/`, `~/.npmrc`, `~/.netrc`, `~/.git-credentials`, the shell startup files (`~/.zshrc`, `~/.zshenv`, `~/.zprofile`, `~/.zlogin`, `~/.zlogout`, `~/.bashrc`, `~/.bash_profile`, `~/.bash_login`, `~/.profile`), `~/Library/LaunchAgents/`, `~/.claude.json`, the whole host roots `~/.claude`, `~/.copilot` and `~/.codex` (every path under them, `config.toml` and all, not an enumerated list - an unattended run writes under `~/.multi-agent-unattended` instead), everything under the runner's root | each one changes what runs next, with more rights than the run has |
|
|
196
|
+
|
|
197
|
+
Shell write targets are read from redirections (`>`, `>>`, `&>`), `tee`,
|
|
198
|
+
`sed -i`, `perl -pi`, and the targets of `cp`/`mv`/`ln`/`install`/`rsync`/`ditto`
|
|
199
|
+
(including `-t DIR`, `--target-directory=DIR`, `-tDIR`), `rm`, `touch`,
|
|
200
|
+
`truncate`, `chmod`, `dd of=`, `sort -o`, `find -fprint/-fprint0/-fprintf/-fls`
|
|
201
|
+
and the starting points of `find -delete`, `curl -o`/`--output`,
|
|
202
|
+
`tar -C`/`--directory`, `unzip -d`, `patch`, `git checkout -- <file>`,
|
|
203
|
+
`git restore <file>`, `git diff/log/show --output` and `git format-patch -o`,
|
|
204
|
+
with `--opt=value` and attached short options (`-tDIR`, `-oFILE`) parsed. A
|
|
205
|
+
write target with an unquoted glob character (`cp x .gi?/hooks/`) is refused:
|
|
206
|
+
the shell expands it at run time to paths the guard cannot see. Paths are
|
|
207
|
+
casefolded on darwin, resolved through `cd` / `pushd` state, and a directory
|
|
208
|
+
that is a prefix of a protected path is protected too. A `cd` into a glob is
|
|
209
|
+
refused, and with `CDPATH` in the environment a relative `cd` that does not
|
|
210
|
+
start with `./` or `../` leaves the directory unresolved (a `CDPATH=`
|
|
211
|
+
assignment is refused outright). Every Bash write target,
|
|
212
|
+
and every Write, Edit or NotebookEdit `file_path`, goes through the same check.
|
|
213
|
+
|
|
214
|
+
**Fail-closed.** An attended guard allows a call it cannot judge, so a guard bug
|
|
215
|
+
cannot break legitimate work. Under the variable the same case is refused: an
|
|
216
|
+
unparseable payload, a missing `agent-guard.py`, or no `python3`.
|
|
217
|
+
|
|
218
|
+
**No package allowlist.** A restore from the committed lockfile is allowed
|
|
219
|
+
(`npm ci`, `pod install`, `pip install -r` while the manifest is unchanged).
|
|
220
|
+
Adding a package is refused without exception. An allowlist the run can reach
|
|
221
|
+
would be edited by the same text that asks for the package, and one it cannot
|
|
222
|
+
reach is a person's decision made in advance, which is what the refusal already
|
|
223
|
+
asks for. A task that needs a new dependency ends with the need recorded in the
|
|
224
|
+
PR body under follow-ups.
|
|
225
|
+
|
|
226
|
+
**The network allowlist is data.** `network.staticHosts` in
|
|
227
|
+
`schemas/unattended-policy.json` (GitHub's hosts, localhost), plus the hosts
|
|
228
|
+
named by `network.prefsHostKeys` in `prefs.global.hosts` (Jira, Confluence,
|
|
229
|
+
Bitbucket, Fortify, Graylog, Jenkins), read at hook time. A subdomain of an
|
|
230
|
+
allowed host is allowed; `corpDomain` is not used, because it is an email domain
|
|
231
|
+
and would allow every host under it. The host is parsed with
|
|
232
|
+
`urllib.parse.urlsplit`, so `http://evil.com#@github.com/` resolves to `evil.com`
|
|
233
|
+
and is refused. The same allowlist governs `WebFetch`, the toolkit's
|
|
234
|
+
`web_goto` / `web_crawl` (any URL argument), each step of `agent_run_steps`,
|
|
235
|
+
and a web URL given to `ios_open_url` / `android_open_url`; `web_eval` is
|
|
236
|
+
refused outright because inline JavaScript can reach any host. Reads only: a request body or
|
|
237
|
+
upload - `-d`/`--data*`, `-F`/`--form`, `-T`/`--upload-file`, and their attached
|
|
238
|
+
and clustered short forms (`-d@file`, `-sd`, `-Tfile`) - is **refused outright**,
|
|
239
|
+
whatever the host, rather than allowed to an allowlisted one; a run has nothing
|
|
240
|
+
to POST that is not the runner's to send.
|
|
241
|
+
|
|
242
|
+
If the allowlist must be pinned at launch instead of re-read from prefs on every
|
|
243
|
+
call, the runner may set `MA_UNATTENDED_HOSTS` (a comma-separated snapshot); when
|
|
244
|
+
it is absent the guard reads `prefs.global.hosts` as before, and protecting
|
|
245
|
+
`~/.claude/multi-agent-preferences.json` from the run is what keeps that read
|
|
246
|
+
trustworthy.
|
|
247
|
+
|
|
248
|
+
## Phase 4: a PR request instead of a push
|
|
249
|
+
|
|
250
|
+
Under the variable Phase 4 commits as usual (the commit hook still needs a
|
|
251
|
+
passing gate ledger) and then writes a request instead of pushing:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
node "$HOME/.claude/scripts/pr-request.mjs" write \
|
|
255
|
+
--branch "$BRANCH" --base "$BASE_BRANCH" --repo "$OWNER/$REPO" \
|
|
256
|
+
--title "$PR_TITLE" --body-file "$WORKTREE/.pipeline/pr-body.md"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The body is the one Step 3 builds for a PR (the `channels/pr.md` section set,
|
|
260
|
+
humanizer pass, no auto-close keywords). The script writes
|
|
261
|
+
`<run root>/pr-requests/<MULTI_AGENT_SESSION_ID>.json` (the run root is
|
|
262
|
+
`~/.multi-agent-unattended`, the one place outside the worktree the sandbox lets
|
|
263
|
+
the run write; `lib/pr-request-location.mjs`), mode 0600, validated against
|
|
264
|
+
`schemas/pr-request.schema.json`: `{branch, base, title, body, draft: true, repo}`.
|
|
265
|
+
It refuses a protected branch, a branch equal to its base and an auto-close
|
|
266
|
+
keyword (exit 1), and a body or title carrying a token shape (exit 7, rule and
|
|
267
|
+
line reported, never the value). Nothing is written on a refusal. The guard
|
|
268
|
+
allows that one file and no other under the runner's root; the run never
|
|
269
|
+
hand-writes it.
|
|
270
|
+
|
|
271
|
+
Skipped under the variable, because each one writes outward: push (standard step
|
|
272
|
+
7), the PR prompt and creation (step 8), the Step 2.9 evidence push (`host:
|
|
273
|
+
none`, reason `unattended`), the issue body update (step 10), and the Jira
|
|
274
|
+
comment. Worktree finalize exits 3 on the unpushed branch and keeps the
|
|
275
|
+
worktree, which is what the runner needs to push from.
|
|
276
|
+
|
|
277
|
+
The runner publishes it after the session ends ("The publish step" below).
|
|
278
|
+
Attended runs never call `pr-request.mjs`.
|
|
279
|
+
|
|
280
|
+
## The runner's launch
|
|
281
|
+
|
|
282
|
+
Before every launch - the dev run, a research pass, a resume - the runner:
|
|
283
|
+
|
|
284
|
+
- refuses to launch unless `agent-guard.sh` is registered as a PreToolUse hook
|
|
285
|
+
on `Bash`, `Edit|Write|NotebookEdit` and `WebFetch|mcp__multi-agent-toolkit__.*`
|
|
286
|
+
in the user settings (`CLAUDE_CONFIG_DIR` or `~/.claude`) or macOS managed
|
|
287
|
+
settings, read the way `install/claude.mjs` writes it: a matcher is a regex
|
|
288
|
+
over the tool name (the legacy `Bash(git push:*)` covers nothing, and the web
|
|
289
|
+
entry has to cover `agent_run_steps` and both `open_url` tools), the hook
|
|
290
|
+
command is exactly the installer's `bash $HOME/.claude/scripts/agent-guard.sh`
|
|
291
|
+
(or the same path with `$HOME` expanded) and that script exists. The
|
|
292
|
+
checkout's `.claude/` settings never count toward the registration - the repo
|
|
293
|
+
under work is not where the guard may come from - but `disableAllHooks` in
|
|
294
|
+
any of these files, the checkout's included, turns every hook off. It also
|
|
295
|
+
refuses when `~/.claude/multi-agent-unattended.settings.json`, the permission
|
|
296
|
+
profile, is missing. A refused first launch is recorded as
|
|
297
|
+
`blocked-guard-missing` (nothing ran, no attempt counted) and the item stays
|
|
298
|
+
queued; a refused research or resume launch leaves the run parked.
|
|
299
|
+
`guardRegistration` in the runner.
|
|
300
|
+
- launches the child with `--settings ~/.claude/multi-agent-unattended.settings.json`
|
|
301
|
+
(`launchArgv`, `researchArgv`, `resumeArgv`; `schemas/launch.json` carries it
|
|
302
|
+
as `{settingsFile}` for a client that launches itself).
|
|
303
|
+
- creates `<autopilot root>/pr-requests/` at mode 0700 and removes any request,
|
|
304
|
+
verdict or summary left under the new session's id (`prepareLaunch`).
|
|
305
|
+
- passes `MULTI_AGENT_UNATTENDED=1`, `MULTI_AGENT_SESSION_ID`,
|
|
306
|
+
`MCP_TOOLKIT_URL_POLICY=strict` (forced, never loosened by the runner's own
|
|
307
|
+
environment) and `MCP_TOOLKIT_INDEX_DENY` (the profile's list plus any entries
|
|
308
|
+
the runner's environment adds), values read from
|
|
309
|
+
`schemas/unattended-profile.json` (`launchEnv`).
|
|
310
|
+
|
|
311
|
+
## The publish step
|
|
312
|
+
|
|
313
|
+
`scripts/autopilot-publish.mjs`, called by the runner when a session ended
|
|
314
|
+
(gone, finished, or idle waiting) with `currentPhase >= 4` and a request at
|
|
315
|
+
`prRequestPath(sessionId)`; again from the next tick's recovery when supervision
|
|
316
|
+
stopped before the session did, and when a parked session ends. Everything the
|
|
317
|
+
run wrote is a claim to check:
|
|
318
|
+
|
|
319
|
+
Files the run could have written are read only as bounded regular files
|
|
320
|
+
(`lib/regular-file.mjs`): the path is resolved, opened non-blocking without
|
|
321
|
+
following a final symlink, and the open descriptor must be a regular file under
|
|
322
|
+
a size cap. A FIFO, a device or an oversized file is refused under its gate
|
|
323
|
+
without being read, so nothing the run leaves behind can block the runner's
|
|
324
|
+
thread. The runner reads run states and trackers the same way.
|
|
325
|
+
|
|
326
|
+
| Step | Refused as (`verificationFailed.gate`) |
|
|
327
|
+
|---|---|
|
|
328
|
+
| the run's state is a regular JSON file (16 MB cap); a refused state cannot be parked, so only the verdict records it | `publish/state` |
|
|
329
|
+
| the request is read once (1 MB cap) and `validatePrRequest` from `pr-request.mjs` runs in process on those bytes: schema, protected branch, auto-close keywords, and the outbound gate | `publish/pr-request` |
|
|
330
|
+
| `repo` must equal the queue item's `nameWithOwner` from the runner's own config; the request never chooses the repository | `publish/repo` |
|
|
331
|
+
| `host` from the config: `github` (default) or `bitbucket`; anything else | `publish/host` |
|
|
332
|
+
| the request's base must be the configured `baseBranch`, else the remote's default branch (`ls-remote --symref <url> HEAD`), or a branch listed in `allowedBases`; a remote that names no default with no `baseBranch` set is refused | `publish/base` |
|
|
333
|
+
| the worktree shares its git common dir with the configured `localPath`, and has no uncommitted change to a tracked file, so the tree verified below is the tree pushed | `publish/worktree` |
|
|
334
|
+
| the branch is not main, master, develop, `release/*`, the configured base or an `allowedBases` entry; it equals the `branch` the run recorded in its state at Phase 0; it exists and is the worktree's HEAD | `publish/branch` |
|
|
335
|
+
| the push URL is the config's `pushUrl`, else `gh repo view <repo> --json url`; never `.git/config`. A URL with credentials in it is refused | `publish/remote` |
|
|
336
|
+
| the branch is fetched from the worktree into a fresh staging repository, and the base from the push URL; the branch must have commits not on the base | `publish/base`, `publish/branch` |
|
|
337
|
+
| a branch already on the remote must have been pushed by this session (its earlier verdict says `pushed: true`) and its tip must be an ancestor of the run's HEAD | `publish/remote-branch` |
|
|
338
|
+
| the gate ledger passes for every commit's parent in the range. The ledger is written by the run, so this is a consistency check, not evidence | `publish/gate-ledger` |
|
|
339
|
+
| the ledger's latest `test-summary` entry names a test log (`detail.source`), a regular file of at most 64 MB inside the worktree or the run's directory; no log, or failed or zero executed, is a failure | `publish/test-log`, `publish/test-summary` |
|
|
340
|
+
| `pre-commit-check.sh --scan-range <merge-base>..<head>`: the commit hook's detectors over every commit in the range on its own, read from the commit objects, so a secret added in one commit and removed in a later one is still found in the history that would be pushed | `publish/secret-scan` |
|
|
341
|
+
| the outbound gate on the title and body | `publish/outbound-gate` |
|
|
342
|
+
| independent verification, below | `publish/verify-stack`, `publish/verify-scripts`, `publish/verify-build`, `publish/verify-test`, `publish/verify-credentials` |
|
|
343
|
+
|
|
344
|
+
### Independent verification
|
|
345
|
+
|
|
346
|
+
The gate ledger is agent-writable: a run can append `not-applicable` for every
|
|
347
|
+
mandatory gate and `gate-ledger.mjs check` passes. So the publish step does not
|
|
348
|
+
take the run's word that it built and tested. It resolves the worktree's stack
|
|
349
|
+
with `_stack-adapter.mjs` and renders the adapter's `build` and `test` commands
|
|
350
|
+
from the **base commit's** manifests (`package.json`, the lock files and the
|
|
351
|
+
adapter's `when` files, copied out of the merge-base into a private directory).
|
|
352
|
+
The run does not choose how it is verified: a range that changes a command
|
|
353
|
+
line, a `build`/`test` package script or its `pre`/`post` hook, the declared
|
|
354
|
+
`packageManager`, or a repository executable a command starts with (`./gradlew`)
|
|
355
|
+
is refused as `publish/verify-scripts`. The commands then run in the worktree:
|
|
356
|
+
its own process group killed at `verify.timeoutSec` (default 1800) and again
|
|
357
|
+
when the command exits, output to a private file. Every process started for the
|
|
358
|
+
command carries a per-run marker variable, and any process still carrying it
|
|
359
|
+
after the group is killed is killed too, which covers a child that left the
|
|
360
|
+
group with `setsid` or a double fork (a process that also clears its
|
|
361
|
+
environment is not reached). The environment is the runner's with `MULTI_AGENT_UNATTENDED`,
|
|
362
|
+
`MULTI_AGENT_SESSION_ID`, `SSH_AUTH_SOCK`, the askpass helpers and every
|
|
363
|
+
variable whose name ends in `_TOKEN`, `_KEY`, `_SECRET`, `_PASSWORD` or
|
|
364
|
+
`_CREDENTIALS` removed (`verifyEnv`). A non-zero exit refuses; then
|
|
365
|
+
`evidence-gate.mjs --claim <kind> --status passed --stack <id>` judges the
|
|
366
|
+
fresh output, and for the test run `test-summary.mjs` counts it: a failure or
|
|
367
|
+
zero tests executed refuses. A stack the adapters cannot build and test (the
|
|
368
|
+
unknown adapter, a package with no `build` or `test` script) refuses as
|
|
369
|
+
`publish/verify-stack`, unless the config sets `verify.allowUnverifiedStack:
|
|
370
|
+
true`, which publishes with the ledger and the log check alone and records
|
|
371
|
+
`verified: false` in the verdict.
|
|
372
|
+
|
|
373
|
+
The operator's credential settings (`credentialConfig`) are read before the
|
|
374
|
+
build and tests run, and the push uses that snapshot. Afterwards
|
|
375
|
+
`~/.gitconfig`, `$XDG_CONFIG_HOME/git/config` (default `~/.config/git/config`)
|
|
376
|
+
and any `GIT_CONFIG_GLOBAL` file must hash to what they did before, and the
|
|
377
|
+
credential settings read again must equal the snapshot; otherwise nothing is
|
|
378
|
+
pushed (`publish/verify-credentials`).
|
|
379
|
+
|
|
380
|
+
Running the run's tests executes code the agent wrote, as the runner's user,
|
|
381
|
+
outside every hook. Scrubbing the environment keeps token-shaped variables out
|
|
382
|
+
of that process; it does not keep credentials away from it. That code can do
|
|
383
|
+
anything the runner's user can do without a prompt, including:
|
|
384
|
+
|
|
385
|
+
- read login-Keychain items whose access list lets the user's own tools read
|
|
386
|
+
them silently, which includes items the pipeline's credential store wrote
|
|
387
|
+
with `security` and anything readable by `security find-generic-password`
|
|
388
|
+
under that user;
|
|
389
|
+
- run `gh auth token` and get the `gh` login's token, whether `gh` keeps it in
|
|
390
|
+
the Keychain or in `~/.config/gh/hosts.yml`;
|
|
391
|
+
- read any file the user can read (SSH keys, `~/.git-credentials`, cloud CLI
|
|
392
|
+
profiles) and open a network connection.
|
|
393
|
+
|
|
394
|
+
Nothing in the publish step can take those away from a process running as the
|
|
395
|
+
same user. The recommended setup is the separate, non-admin macOS user below,
|
|
396
|
+
whose Keychain and home hold only the narrow tokens the runner needs; without
|
|
397
|
+
it this step is the widest opening in the design.
|
|
398
|
+
|
|
399
|
+
### The push, from a staging repository
|
|
400
|
+
|
|
401
|
+
Nothing that touches the network runs in the worktree. The worktree's git
|
|
402
|
+
config is the run's to write, and a denylist of keys that could move or
|
|
403
|
+
observe a push (`url.*.insteadOf`, `http.proxy`, `http.curloptResolve`,
|
|
404
|
+
`http.sslVerify`, `http.extraHeader`, `credential.*`, `core.sshCommand`,
|
|
405
|
+
`include*`, and the ones git adds next) is never complete. So the publish step
|
|
406
|
+
makes a fresh bare repository in a private temp directory and runs every
|
|
407
|
+
network operation there, with `GIT_CONFIG_NOSYSTEM=1`, `GIT_CONFIG_GLOBAL`
|
|
408
|
+
pointing at a file it writes, `-c core.hooksPath=/dev/null` and
|
|
409
|
+
`GIT_TERMINAL_PROMPT=0`. That file holds only the operator's `credential.*`
|
|
410
|
+
settings, read from the system and global scopes outside any repository
|
|
411
|
+
(`credentialConfig`), so the helper the operator already uses still answers and
|
|
412
|
+
nothing else from any config reaches the push.
|
|
413
|
+
|
|
414
|
+
The branch enters the staging repository by a fetch the staging repository
|
|
415
|
+
runs, naming the worktree as a local path: its config, not the worktree's,
|
|
416
|
+
decides where that fetch reads. The base fetch and the remote-branch check run
|
|
417
|
+
from one staging repository, which is deleted before independent verification
|
|
418
|
+
starts; the push (`push --no-verify --porcelain <url>
|
|
419
|
+
refs/heads/<branch>:refs/heads/<branch>`, never forced, submodules not
|
|
420
|
+
recursed) runs from a second one, holding the credential snapshot taken
|
|
421
|
+
before verification, made after the run's build and tests have exited, once the worktree is confirmed to be still on the checked commit with
|
|
422
|
+
no tracked file changed (`publish/verify-test` otherwise). Code the tests
|
|
423
|
+
started could otherwise have written into the staging repository's own config
|
|
424
|
+
while it waited. The worktree is
|
|
425
|
+
still read locally - `rev-parse`, a status with `core.fsmonitor=false`, the
|
|
426
|
+
secret scan's `diff --no-ext-diff --no-textconv` - with the scrubbed
|
|
427
|
+
environment. On GitHub, `gh pr create --draft --repo <repo> --base <base>
|
|
428
|
+
--head <branch> --title ... --body-file <tmp>` runs from a private temp
|
|
429
|
+
directory, not the worktree; the URL goes into `state.pr` through the
|
|
430
|
+
write-state lock and the attempt is `pr-opened`. A push or `gh` failure is
|
|
431
|
+
`publish/push` or `publish/pr-create`.
|
|
432
|
+
|
|
433
|
+
Bitbucket has no draft pull request, and opening a ready one would ask for
|
|
434
|
+
review of work nobody has looked at. The runner pushes the verified branch,
|
|
435
|
+
writes `<session id>.summary.md` (0600) beside the request with the branch,
|
|
436
|
+
base, title and body, records `state.publish`, and the attempt is
|
|
437
|
+
`pushed-awaiting-pr`: terminal, because the work is delivered and a retry would
|
|
438
|
+
push it again, and not parked, because the step left is a person's and needs
|
|
439
|
+
neither the worktree nor the repo's queue slot.
|
|
440
|
+
|
|
441
|
+
Every process the publish step starts runs with `MULTI_AGENT_UNATTENDED` and
|
|
442
|
+
`MULTI_AGENT_SESSION_ID` removed and `GIT_TERMINAL_PROMPT=0`. The runner is not
|
|
443
|
+
the agent: these are spawns, not tool calls, so no hook sees them, and the
|
|
444
|
+
variable would only make the scripts behave as if the agent had called them.
|
|
445
|
+
The runner's own credentials (the `gh` login, the git credential helper) are
|
|
446
|
+
used; none reaches argv or a log.
|
|
447
|
+
|
|
448
|
+
The verdict is written to `<session id>.verdict.json` (0600): the outcome, the
|
|
449
|
+
URL or the gate and reason, whether the branch was pushed, and what the
|
|
450
|
+
independent verification found, never a matched value. A refusal parks the run
|
|
451
|
+
through `gate-ledger.mjs park`; once the cause is fixed a person re-runs the one
|
|
452
|
+
step with `autopilot-publish.mjs --session <id> --state <agent-state.json>
|
|
453
|
+
--repo <owner/name>`.
|
|
454
|
+
|
|
455
|
+
## Phase 5 and channels
|
|
456
|
+
|
|
457
|
+
Under the variable Phase 5 skips external delivery (`deferred-to-runner`) and
|
|
458
|
+
`channels` posts nothing. After the PR opened, and only then, the runner posts
|
|
459
|
+
what `~/.claude/autopilot/config.json` opts into
|
|
460
|
+
(`schemas/autopilot-config.schema.json`), once, through the existing scripts:
|
|
461
|
+
|
|
462
|
+
| Key | What the runner posts |
|
|
463
|
+
|---|---|
|
|
464
|
+
| `reportChannels: true` with `channels.mode: configured` | `jira`: one comment on the linked issue with the PR link and text (`lib/jira-publish.sh`). `pr`: the draft PR is the report. `confluence`, `wiki`: skipped and logged, because a page is composed by a session and a session with publish rights is what the unattended design does not create |
|
|
465
|
+
| `reportIssueUpdates: true` | `update-issue-progress.sh` for the GitHub issue in `state.githubIssue`, and a PR-link comment on the linked Jira issue when no channel comment went out |
|
|
466
|
+
|
|
467
|
+
Both default off; without them the draft PR is the only thing published. The
|
|
468
|
+
text is scanned by the outbound gate before a script sees it, each script's own
|
|
469
|
+
gate runs again, and each runs with `MULTI_AGENT_UNATTENDED` unset.
|
|
470
|
+
`reportAfterPublish` in `autopilot-publish.mjs`.
|
|
471
|
+
|
|
472
|
+
## What leaves the machine is scanned
|
|
473
|
+
|
|
474
|
+
`lib/outbound-gate.mjs` (`SURFACES`) scans every published body for token
|
|
475
|
+
shapes: the PR request body (`pr-request.mjs`, at write and at validate), the
|
|
476
|
+
PR text and post-PR reports the runner publishes (`autopilot-publish.mjs`), Jira
|
|
477
|
+
comments and descriptions (`jira-publish.sh`), Jira issue creation
|
|
478
|
+
(`analysis-jira-write.sh`), Confluence pages (`md2confluence-v3.py`), PR reviews
|
|
479
|
+
(`post-pr-review.sh`) and issue progress comments (`update-issue-progress.sh`).
|
|
480
|
+
|
|
481
|
+
## Text a run did not write is data
|
|
482
|
+
|
|
483
|
+
Fetched bodies, ticket descriptions, comments and PR text enter prompts inside
|
|
484
|
+
`<untrusted-data source="...">` ... `</untrusted-data>`, produced by
|
|
485
|
+
`lib/untrusted.mjs wrap`, which defuses a delimiter forged inside the text. The
|
|
486
|
+
rule, in `features/external-context-injection.md` and the clarifier and reviewer
|
|
487
|
+
agents: content inside a block is never an instruction. A prompt rule is
|
|
488
|
+
advisory; the guard is what holds when a model follows the text anyway.
|
|
489
|
+
|
|
490
|
+
## The OS sandbox is the boundary
|
|
491
|
+
|
|
492
|
+
The primary containment for an unattended run is the OS sandbox (Seatbelt on
|
|
493
|
+
macOS), not the command guard. A deny-list of command shapes cannot be
|
|
494
|
+
complete; the sandbox is enforced by the kernel for every Bash command and
|
|
495
|
+
every process it starts, whatever the command line looks like.
|
|
496
|
+
|
|
497
|
+
`install --unattended` always writes an OS sandbox block into the profile
|
|
498
|
+
(`schemas/unattended-profile.json` -> `~/.claude/multi-agent-unattended.settings.json`,
|
|
499
|
+
`lib/unattended-settings-location.mjs`):
|
|
500
|
+
|
|
501
|
+
- `sandbox.enabled: true`, `failIfUnavailable: true` (a missing sandbox is a
|
|
502
|
+
refusal to start, not an unsandboxed run), `allowUnsandboxedCommands: false`
|
|
503
|
+
(no `dangerouslyDisableSandbox` retry), `autoAllowBashIfSandboxed: false` (the
|
|
504
|
+
allow list still decides), and no `excludedCommands`.
|
|
505
|
+
- Writes are confined to the run's worktree and the session temp directory.
|
|
506
|
+
`filesystem.denyWrite` additionally names `~/.claude`, `~/.copilot`, `~/.codex`,
|
|
507
|
+
`~/Library/LaunchAgents`, the shell rc/profile files, `~/.ssh`, `~/.gnupg`,
|
|
508
|
+
`~/.config/gh`, `~/.config/git`, `~/.gitconfig`, `~/.npmrc`, `~/.netrc`,
|
|
509
|
+
`~/.git-credentials`, `~/.gradle/init.d`, `~/.lldbinit` and `~/.curlrc`.
|
|
510
|
+
- `filesystem.denyRead` names `~/.ssh`, `~/.aws`, `~/Library/Keychains`,
|
|
511
|
+
`~/.netrc`, `~/.git-credentials` and `~/.docker/config.json`.
|
|
512
|
+
- `network.strictAllowlist: true` with `allowedDomains` limited to the package
|
|
513
|
+
registries and toolchains the stack adapters need (npm, PyPI, Maven/Google/
|
|
514
|
+
Gradle, CocoaPods, Go, crates, RubyGems, GitHub) plus the prefs hosts.
|
|
515
|
+
`enableWeakerNetworkIsolation: true` lets Go tools such as `gh` reach the
|
|
516
|
+
macOS trust service to verify TLS; `allowLocalBinding` and
|
|
517
|
+
`allowMachLookup: ["com.apple.coresimulator.*"]` let a dev server and the
|
|
518
|
+
simulator work.
|
|
519
|
+
|
|
520
|
+
An unattended run therefore writes what the runner reads back not under
|
|
521
|
+
`~/.claude` (the sandbox denies it) but under `~/.multi-agent-unattended`
|
|
522
|
+
(`lib/pr-request-location.mjs`): `LOGS_ROOT` points the run's state there and
|
|
523
|
+
the PR request file lives there; verdicts stay in the runner's own
|
|
524
|
+
`~/.claude/autopilot` directory, which the run cannot write.
|
|
525
|
+
|
|
526
|
+
### Verified behaviour (Claude Code 2.1.282)
|
|
527
|
+
|
|
528
|
+
Measured with a probe: `claude -p --model <a Haiku probe model> --settings
|
|
529
|
+
<profile> --permission-mode dontAsk --allowedTools Bash` in a temp git repo,
|
|
530
|
+
asked to run each command, against HOME-safe decoys (`$HOME/.ma-sandbox-decoy`
|
|
531
|
+
in `denyWrite`, never the real `~/.claude`). Results:
|
|
532
|
+
|
|
533
|
+
| Command | Result |
|
|
534
|
+
|---|---|
|
|
535
|
+
| `touch ~/.claude/sandbox-probe-x` | blocked (Operation not permitted) |
|
|
536
|
+
| `touch ./ok` (in the worktree) | allowed |
|
|
537
|
+
| `touch ~/.ma-sandbox-outside-file` (outside the worktree, not listed) | blocked |
|
|
538
|
+
| `mv <denyWrite dir> /tmp`, `rm -rf <denyWrite dir>`, `cat <denyRead file>` | blocked |
|
|
539
|
+
| `echo x > .git/hooks/pre-push`, `git config core.hooksPath /tmp` | blocked (protected path) |
|
|
540
|
+
| `mkdir -p .github/workflows` with `denyWrite **/.github/workflows` | blocked |
|
|
541
|
+
| Python/Node writing to a `denyWrite` path | blocked (EPERM) |
|
|
542
|
+
| `curl https://example.com` (not allowlisted) | blocked (`network-outbound ... not on the allow list`) |
|
|
543
|
+
| `curl https://registry.npmjs.org/` | allowed |
|
|
544
|
+
| `gh api rate_limit`, `git ls-remote https://github.com/...` | allowed (with `enableWeakerNetworkIsolation`) |
|
|
545
|
+
| `touch ~/... dangerouslyDisableSandbox:true` under `allowUnsandboxedCommands:false` | blocked (the escape hatch is ignored) |
|
|
546
|
+
|
|
547
|
+
A relative `denyWrite`/`Edit(...)` entry in a `--settings` file does not resolve
|
|
548
|
+
to the repository, so in-repo protection (`.git/hooks`, `.github/workflows`) is
|
|
549
|
+
carried by `Edit(**/.github/workflows/**)` and `Edit(**/.git/hooks/**)` deny
|
|
550
|
+
rules, which Claude Code adds to the sandbox `denyWrite` list, plus the sandbox's
|
|
551
|
+
own built-in protected paths. `**/.github/workflows` as a bare `denyWrite`
|
|
552
|
+
matched both the top-level and a nested `.github/workflows`; the `Edit(...)` deny
|
|
553
|
+
form is used because it also covers the file tools.
|
|
554
|
+
|
|
555
|
+
### The permission layer (second line, for the tools the sandbox does not cover)
|
|
556
|
+
|
|
557
|
+
The same profile carries `permissions.defaultMode: "dontAsk"`, a narrow allow
|
|
558
|
+
list (`xcrun` only as `simctl`, `xcresulttool`, `xctrace` and `--find`), deny
|
|
559
|
+
rules for merge, push, and - since Edit/Write/NotebookEdit and MCP tools run
|
|
560
|
+
outside the OS sandbox - `Edit(...)`/`Read(...)` deny rules on the same
|
|
561
|
+
protected paths (`~/.claude/**`, `~/.copilot/**`, `~/.codex/**`,
|
|
562
|
+
`~/Library/LaunchAgents/**`, the shell rc files, `~/.ssh`, `~/.gnupg`,
|
|
563
|
+
`~/.config/gh`, `~/.config/git`, `~/.gitconfig`, `~/.npmrc`, `~/.netrc`,
|
|
564
|
+
`~/.git-credentials`, `.git/hooks/**`, `.git/config`, `**/.github/workflows/**`),
|
|
565
|
+
and the toolkit env `MCP_TOOLKIT_URL_POLICY=strict` and
|
|
566
|
+
`MCP_TOOLKIT_INDEX_DENY=~/.ssh,~/.aws,~/.gnupg,~/Library/Keychains`.
|
|
567
|
+
|
|
568
|
+
The runner hands the file to each child with `claude --settings`, so only the
|
|
569
|
+
unattended session loads it. `~/.claude/settings.json` is the attended posture
|
|
570
|
+
and is not changed. Permission lists merge across settings files, so a
|
|
571
|
+
whole-Bash allow (`Bash`, `Bash(*)`, `Bash(:*)`, `Bash(**)`) in `settings.json`
|
|
572
|
+
still reaches the child: the installer reports it and doctor's
|
|
573
|
+
`unattended-permissions` check warns, and neither removes it. `defaultMode` in
|
|
574
|
+
the profile is forced to `dontAsk`; `bypassPermissions`, `acceptEdits` or any
|
|
575
|
+
other value already there is replaced, never kept. When `settings.json` carries
|
|
576
|
+
the v20.2.1 profile (that release wrote it there), the installer prints the move
|
|
577
|
+
and removes exactly that release's entries (frozen in
|
|
578
|
+
`install/unattended-profile-legacy.json`), including rules the current profile no
|
|
579
|
+
longer has. `--unattended-sandbox` remains a synonym for `--unattended`; the
|
|
580
|
+
sandbox is written either way. `lib/unattended-profile-check.mjs` is the one rule
|
|
581
|
+
the installer, doctor and the runner's launch precondition apply.
|
|
582
|
+
|
|
583
|
+
The runner refuses to launch (`blocked-sandbox-unavailable`) unless the profile
|
|
584
|
+
file's content still holds the sandbox block, `dontAsk` and the deny rules, and
|
|
585
|
+
the checkout's own `.claude/settings.json` adds no sandbox block, additional
|
|
586
|
+
directory, path allow rule or WebFetch domain. It checks before every launch, so
|
|
587
|
+
a run that rewrote the profile cannot widen the next one (R3 finding 14), and it
|
|
588
|
+
hashes `~/.claude/{scripts,lib}`, the settings files, the profile and
|
|
589
|
+
`~/Library/LaunchAgents` before and after publish verification (`hostFingerprint`
|
|
590
|
+
in `autopilot-publish.mjs`) and refuses to push if any changed.
|
|
591
|
+
|
|
592
|
+
The profile's `env` reaches the session and the commands it runs. The Claude
|
|
593
|
+
Code docs pass environment to a stdio MCP server through the server entry, so
|
|
594
|
+
pin the two toolkit variables there as well: `claude mcp add --env
|
|
595
|
+
MCP_TOOLKIT_URL_POLICY=strict ...` when the server is registered.
|
|
596
|
+
|
|
597
|
+
Details: `unattended-contract.md`, "The permission posture".
|
|
598
|
+
|
|
599
|
+
## Machine setup (applied by the operator)
|
|
600
|
+
|
|
601
|
+
These are outside the pipeline and nothing here applies them.
|
|
602
|
+
|
|
603
|
+
1. **A separate, non-admin macOS user** for the runner, with its own login
|
|
604
|
+
Keychain holding only the tokens below. The run then cannot read the
|
|
605
|
+
operator's Keychain, SSH keys or browser profiles even through a path the
|
|
606
|
+
hook does not see, and cannot `sudo`. The LaunchAgent runs in that user's
|
|
607
|
+
session (`docs/server-readiness.md`).
|
|
608
|
+
2. **GitHub credentials per repo**: a fine-grained PAT limited to the queued
|
|
609
|
+
repositories with `Contents: write` and `Pull requests: write` and nothing
|
|
610
|
+
else, or a GitHub App installed on those repositories with the same two
|
|
611
|
+
permissions. No `Administration`, no `Workflows`, no org scope.
|
|
612
|
+
3. **Jira**: a token for an account that can read the queued projects and add
|
|
613
|
+
comments, and nothing more (no transition, no delete, no admin).
|
|
614
|
+
4. **GitHub rulesets** on every default and release branch: no force-push, no
|
|
615
|
+
deletion, pull request required, code-owner review required, approval of the
|
|
616
|
+
most recent push required, required status checks, and the bot account or
|
|
617
|
+
App in no bypass list.
|
|
618
|
+
5. **Optional: a Tart VM.** Running the runner's user inside a Tart macOS VM
|
|
619
|
+
separates the whole filesystem and network stack, and a snapshot makes each
|
|
620
|
+
run start clean. The steps above still apply inside the VM.
|
|
621
|
+
|
|
622
|
+
## Hook timeout and cost
|
|
623
|
+
|
|
624
|
+
A PreToolUse hook that times out does not block the tool call (Claude Code
|
|
625
|
+
hooks reference), so a guard that ran out of time would fail open on the one
|
|
626
|
+
path that must fail closed. `agent-guard.py` therefore arms its own `SIGALRM`
|
|
627
|
+
deadline of 5 seconds: on expiry it prints and flushes the blocking verdict when
|
|
628
|
+
unattended (and the allow verdict when attended, where a guard bug must not
|
|
629
|
+
break legitimate work). `MA_GUARD_DEADLINE_SECONDS` may lower the deadline,
|
|
630
|
+
never raise it; the tests use it with a slow `git` on `PATH`. The hook is registered with `timeout: 10`, above that deadline,
|
|
631
|
+
so the guard's own verdict is what the harness sees. The work is bounded: no
|
|
632
|
+
network, one prefs read, and read-only git calls memoized per invocation, each
|
|
633
|
+
capped at 3 seconds; a command above 50 segments is refused unparsed rather than
|
|
634
|
+
walked. Measured over a 40-command corpus the guard's median is about 0.13 s and
|
|
635
|
+
its p99 about 0.17 s.
|
|
636
|
+
|
|
637
|
+
## The phone routes
|
|
638
|
+
|
|
639
|
+
`contract-server.mjs` serves a small signed subset to an enrolled phone
|
|
640
|
+
(`features/phone-api.md`): read the redacted runs, answer a parked question
|
|
641
|
+
with an offered option id, and queue a launch when the operator enabled it.
|
|
642
|
+
Three things tie it to this page. The device registry and the phone audit log
|
|
643
|
+
live under the autopilot root, which the guard forbids a run to write.
|
|
644
|
+
`phone-devices.mjs add`, `revoke` and `launch` refuse under the variable and are
|
|
645
|
+
blocked by name above. An answer from a phone goes through the same
|
|
646
|
+
`answer-question.mjs` refusals as a desktop answer, and the step that asked
|
|
647
|
+
re-runs its gate on resume, so a phone cannot answer past the maturity or
|
|
648
|
+
open-questions check.
|
|
649
|
+
|
|
650
|
+
## Residual risk, accepted and stated
|
|
651
|
+
|
|
652
|
+
- **Bash runs code the hook cannot read, so it is refused, not inspected.** An
|
|
653
|
+
inline program (`python3 -c`, `node -e`, `perl -e`, `ruby -e`, `osascript -e`,
|
|
654
|
+
`php -r`, `deno eval`, `bash -c`), a nested or command-position substitution,
|
|
655
|
+
an unquoted heredoc, or a script, Makefile or awk/sed program file the run
|
|
656
|
+
wrote or modified hands the guard a program it cannot parse into simple
|
|
657
|
+
commands. Under the variable the guard refuses every one of those rather than
|
|
658
|
+
guessing; the cost is that a legitimate one-liner using those forms is
|
|
659
|
+
rejected and has to be written as separate simple commands or committed first.
|
|
660
|
+
What the guard cannot bound at all is a **committed, unmodified** script,
|
|
661
|
+
Makefile target, test runner or build plugin, and the code a test runner or
|
|
662
|
+
build loads from the worktree: `npm test`, `swift test`, `pytest` or a build
|
|
663
|
+
phase in the project file run code the run wrote, because running the run's
|
|
664
|
+
tests is the point. The separate macOS user bounds that, and the sandbox
|
|
665
|
+
narrows it further. A binary in `node_modules/.bin` is accepted the way `npx`
|
|
666
|
+
accepts it, and a run can write there.
|
|
667
|
+
- **An archive's members are not read.** `tar -x` and `unzip` are judged by
|
|
668
|
+
their `-C` / `-d` target; a member path inside the archive
|
|
669
|
+
(`.git/hooks/pre-commit`) is not listed before extraction. `tar -P`, `patch`
|
|
670
|
+
and `git apply`/`am` are refused outright because their targets are not
|
|
671
|
+
enumerable. Extraction into the worktree with no `-C` is allowed, and the
|
|
672
|
+
sandbox confines the written paths to the worktree.
|
|
673
|
+
- **Egress through an allowed host is not prevented.** A command that reads a
|
|
674
|
+
worktree file and sends it to an allowlisted host in a header or query
|
|
675
|
+
(`curl ... -H "X: $(cat .env)"` to `api.github.com`) passes both the guard
|
|
676
|
+
and the sandbox: the host is allowed and the file is the repo's own. The
|
|
677
|
+
network allowlist bounds *where* data can go, not *what*; the code-owner
|
|
678
|
+
review of the PR and the separate macOS user are the remaining checks.
|
|
679
|
+
- **Build tools run code the run named.** `xcodebuild CC=./x.sh`,
|
|
680
|
+
`swift package plugin`, `swift run` and similar run a program the run could
|
|
681
|
+
run directly; the sandbox confines that program to the worktree and the
|
|
682
|
+
allowed network the same as any other command. `ssh`/`scp`/`nc`/`dig` and
|
|
683
|
+
rsync-to-remote are refused; other outbound tools are bounded by the network
|
|
684
|
+
allowlist.
|
|
685
|
+
- **Shell control flow is refused, not parsed.** `if`/`for`/`while`/`case`,
|
|
686
|
+
functions, arrays, brace groups, backticks, process substitution, arithmetic
|
|
687
|
+
and non-trivial `${...}` are rejected under the variable even when harmless.
|
|
688
|
+
A documented step written that way has to become simple commands or a
|
|
689
|
+
checked-in script (`test/unattended-doc-lines.test.mjs` records every
|
|
690
|
+
phase-doc line in that state). Plain `$VAR`, `${NAME}` and analysable
|
|
691
|
+
`$(...)` are allowed.
|
|
692
|
+
- **Permission rules match the command as written.** Claude Code's docs state a
|
|
693
|
+
Bash deny rule does not stop `sh -c` or an absolute path. The profile is a
|
|
694
|
+
second line for that reason.
|
|
695
|
+
- **Reads are not blocked by the hook.** The deny rules and the sandbox cover the
|
|
696
|
+
credential directories; the rest of the user's files are readable, which is
|
|
697
|
+
why that user holds nothing else.
|
|
698
|
+
- **The publish step runs the run's code.** Independent verification executes
|
|
699
|
+
the agent's build and tests as the runner's user, with token-shaped variables
|
|
700
|
+
removed from the environment but not from the disk or the Keychain: that code
|
|
701
|
+
can read Keychain items the user's own tools read without a prompt, run `gh
|
|
702
|
+
auth token`, and read the user's files. Its process group and every process
|
|
703
|
+
carrying the run's marker are killed when it exits; a process that moved into
|
|
704
|
+
a new session and cleared its environment outlives that. A changed user git
|
|
705
|
+
config refuses the push, but a credential helper program the user can write
|
|
706
|
+
is not checked. The separate macOS user is what bounds all of this.
|
|
707
|
+
- **The PR body is reviewed text, not trusted text.** The outbound gate catches
|
|
708
|
+
token shapes, not every sensitive sentence; the draft PR and code-owner
|
|
709
|
+
review are where a person reads it.
|
|
710
|
+
|
|
711
|
+
## Reference
|
|
712
|
+
|
|
713
|
+
Scripts: `agent-guard.sh`, `agent-guard.py`, `unattended_policy.py`,
|
|
714
|
+
`pr-request.mjs`, `autopilot-publish.mjs`, the continuous-mode runner
|
|
715
|
+
(`guardRegistration`, `prepareLaunch`, `launchEnv`), `pre-commit-check.sh
|
|
716
|
+
--scan-range`, `lib/pr-request-location.mjs`, `lib/untrusted.mjs`,
|
|
717
|
+
`lib/outbound-gate.mjs`, `lib/unattended-settings-location.mjs`,
|
|
718
|
+
`install/_unattended-profile.mjs`. Data:
|
|
719
|
+
`schemas/unattended-policy.json`, `schemas/unattended-profile.json`,
|
|
720
|
+
`schemas/pr-request.schema.json`. Tests: `test/unattended-guard.test.mjs`,
|
|
721
|
+
`test/pr-request.test.mjs`, `test/autopilot-publish.test.mjs`,
|
|
722
|
+
the runner's tests, `test/outbound-gate.test.mjs`,
|
|
723
|
+
`test/unattended-profile.test.mjs`, `test/agent-guard.test.mjs`; smokes
|
|
724
|
+
`smoke-agent-guard.sh`, `smoke-unattended-redteam.sh` (including a request that names another repo), `smoke-pre-commit.sh`,
|
|
725
|
+
`smoke-untrusted-delimiters.sh`, `smoke-unattended-install-profile.sh`,
|
|
726
|
+
`smoke-gates-interactive-noop.sh`. The phone routes: `features/phone-api.md`,
|
|
727
|
+
`test/phone-api.test.mjs`.
|
|
728
|
+
|
|
729
|
+
The runner's operations around a run - the sleep lock, the credential probe at
|
|
730
|
+
arming, the parallel cap, the cleanup report and the daily digest - are in
|
|
731
|
+
`features/autopilot-operations.md`.
|