@mmerterden/multi-agent-pipeline 20.2.0 → 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 +923 -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 -31
- 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 +82 -7
- 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 -63
- 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,747 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file launch-request.mjs - validate a launch request and turn it into the
|
|
4
|
+
* pickers Phase 0 may skip.
|
|
5
|
+
*
|
|
6
|
+
* A client that wants a run started without a terminal conversation writes a
|
|
7
|
+
* request file (launch-request.schema.json) and sets MA_LAUNCH_REQUEST to its
|
|
8
|
+
* path. Phase 0 Step 0.1 calls `resolve`; each answer then stands in for the
|
|
9
|
+
* picker run-questions.json names, and every other picker asks as it always did.
|
|
10
|
+
*
|
|
11
|
+
* With MA_LAUNCH_REQUEST unset the answer is `present: false` with an empty
|
|
12
|
+
* prefill and an empty state patch, so an interactive run writes nothing it did
|
|
13
|
+
* not write before. A variable naming a file that does not exist is the same
|
|
14
|
+
* answer plus a warning: the terminal path is the safe fallback, and a request
|
|
15
|
+
* nobody can read has no answers to apply.
|
|
16
|
+
*
|
|
17
|
+
* A request that IS present and wrong is rejected whole - exit 1, nothing
|
|
18
|
+
* pre-filled. Half-applying one would ask some questions and silently answer
|
|
19
|
+
* others with values nobody validated. The rules, beyond the schema:
|
|
20
|
+
*
|
|
21
|
+
* - every answered id exists in run-questions.json and is answerable;
|
|
22
|
+
* - a static question takes one of its option ids, a run-time-enumerated one
|
|
23
|
+
* a value matching its `answer.pattern`, multi-select an array of them;
|
|
24
|
+
* - `disabled-when` is enforced: `workspace: local` under `mode: autopilot`
|
|
25
|
+
* is rejected, and so is `workspace: local` when `--repo` names a working
|
|
26
|
+
* tree with uncommitted changes (Step 6b would check out a new branch over
|
|
27
|
+
* them). Without `--repo` the tree is reported `unchecked`, and Phase 0
|
|
28
|
+
* re-runs `resolve --repo "$PROJECT_ROOT"` at Step 5b before applying it.
|
|
29
|
+
*
|
|
30
|
+
* Answers are data. Nothing here or in Phase 0 reads an answer as an
|
|
31
|
+
* instruction: an answer selects an option, and a value that is not an option
|
|
32
|
+
* never reaches a picker.
|
|
33
|
+
*
|
|
34
|
+
* The input is data too, and it lands in the prompt of a session started with
|
|
35
|
+
* no permission prompts, so it is held to a shape instead of passed through:
|
|
36
|
+
*
|
|
37
|
+
* - a structured reference: a Jira key, a GitHub issue URL
|
|
38
|
+
* (https://github.com/<owner>/<repo>/issues/<n>), repo#N, #N, or a Jira
|
|
39
|
+
* URL on the configured Jira host (prefs global.hosts.jira). It reaches
|
|
40
|
+
* the prompt as it is;
|
|
41
|
+
* - free text, on the desktop channel only (the bearer route, the CLI, and a
|
|
42
|
+
* request file Phase 0 reads): one line of at most FREE_TEXT_MAX printable
|
|
43
|
+
* characters, no quote, backslash or backtick, reaching the prompt as one
|
|
44
|
+
* double-quoted argument so the dispatcher reads it as a task description.
|
|
45
|
+
* The phone channel takes structured references only.
|
|
46
|
+
*
|
|
47
|
+
* Either way a newline, a control or format character, a leading "-", a token
|
|
48
|
+
* that looks like a flag, and a first token that names a pipeline op or a mode
|
|
49
|
+
* keyword is refused. Only the first token is held to the reserved words: free
|
|
50
|
+
* text reaches the prompt as one quoted argument, so a later word cannot route,
|
|
51
|
+
* and a structured reference is a single token. The op names are the command
|
|
52
|
+
* tree's ids plus the words the dispatcher's routing table routes on; the mode
|
|
53
|
+
* keywords are the modes in phases.json and launch.json, the request modes and
|
|
54
|
+
* the workspace options. Both are read from that data, never listed here. A command tree
|
|
55
|
+
* that cannot be read refuses free text rather than guessing.
|
|
56
|
+
*
|
|
57
|
+
* A launch names its working directory, and only a configured autopilot repo
|
|
58
|
+
* (config.json repos[].localPath) or one the desktop client registered with
|
|
59
|
+
* `register-repo` (launch-repos.json under the autopilot root) is accepted,
|
|
60
|
+
* compared by real path.
|
|
61
|
+
*
|
|
62
|
+
* Zero runtime dependencies (ADR-0004); the schema checks are hand-written and
|
|
63
|
+
* test/launch-request.test.mjs holds them to launch-request.schema.json.
|
|
64
|
+
*
|
|
65
|
+
* `plan` is the other half, for a client that starts the run itself: the argv,
|
|
66
|
+
* environment and working directory launch.json declares for the request's
|
|
67
|
+
* mode, with every placeholder filled. It only prints; nothing here spawns a
|
|
68
|
+
* host. `questions` and `spec` print run-questions.json and launch.json as the
|
|
69
|
+
* contract carries them, so a client reads the same bytes from the CLI as from
|
|
70
|
+
* contract-server.mjs.
|
|
71
|
+
*
|
|
72
|
+
* Usage:
|
|
73
|
+
* node launch-request.mjs validate <file> [--repo <path>]
|
|
74
|
+
* node launch-request.mjs resolve [<file>] [--repo <path>] # file defaults to $MA_LAUNCH_REQUEST
|
|
75
|
+
* node launch-request.mjs plan <file> --repo <path> --session-id <uuid> [--channel desktop|phone] [--redact]
|
|
76
|
+
* node launch-request.mjs questions
|
|
77
|
+
* node launch-request.mjs spec
|
|
78
|
+
* node launch-request.mjs register-repo <absolute path>
|
|
79
|
+
*
|
|
80
|
+
* Output: one JSON object on stdout, always carrying contractVersion.
|
|
81
|
+
* Exit: 0 valid or absent, 1 invalid, 2 usage error, 3 register-repo from an
|
|
82
|
+
* unattended run.
|
|
83
|
+
*
|
|
84
|
+
* @module pipeline/scripts/launch-request
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
import {
|
|
88
|
+
chmodSync,
|
|
89
|
+
existsSync,
|
|
90
|
+
mkdirSync,
|
|
91
|
+
readFileSync,
|
|
92
|
+
realpathSync,
|
|
93
|
+
statSync,
|
|
94
|
+
writeFileSync,
|
|
95
|
+
} from "node:fs";
|
|
96
|
+
import { spawnSync } from "node:child_process";
|
|
97
|
+
import { homedir } from "node:os";
|
|
98
|
+
import { isAbsolute, join } from "node:path";
|
|
99
|
+
import { catalog, commandsDir } from "./commands.mjs";
|
|
100
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
101
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
102
|
+
import { autopilotRoot, unattendedLogsRoot } from "../lib/pr-request-location.mjs";
|
|
103
|
+
import { lockTimeoutFrom, updateJsonFileSync } from "../lib/json-file-lock.mjs";
|
|
104
|
+
import { unattendedSettingsPath } from "../lib/unattended-settings-location.mjs";
|
|
105
|
+
import { isUnattended } from "../lib/unattended.mjs";
|
|
106
|
+
|
|
107
|
+
export const CONTRACT_VERSION = "1.0.0";
|
|
108
|
+
export const MODES = ["interactive", "autopilot"];
|
|
109
|
+
const TOP_KEYS = ["contractVersion", "input", "mode", "answers"];
|
|
110
|
+
|
|
111
|
+
const readSchemaJson = (name) =>
|
|
112
|
+
JSON.parse(readFileSync(new URL(`../schemas/${name}`, import.meta.url), "utf8"));
|
|
113
|
+
|
|
114
|
+
/** run-questions.json, as the `questions` subcommand prints it. */
|
|
115
|
+
export function questionsDocument() {
|
|
116
|
+
return readSchemaJson("run-questions.json");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** launch.json, as the `spec` subcommand prints it. */
|
|
120
|
+
export function launchSpec() {
|
|
121
|
+
return readSchemaJson("launch.json");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function loadQuestions() {
|
|
125
|
+
return questionsDocument().questions;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* `clean`, `dirty`, or `unreadable` for a working tree. Unreadable counts as
|
|
130
|
+
* dirty wherever it matters: a tree git cannot describe is not one to check a
|
|
131
|
+
* branch out over.
|
|
132
|
+
*/
|
|
133
|
+
export function workingTreeState(repo) {
|
|
134
|
+
const r = spawnSync("git", ["-C", repo, "status", "--porcelain"], {
|
|
135
|
+
encoding: "utf8",
|
|
136
|
+
timeout: 10000,
|
|
137
|
+
});
|
|
138
|
+
if (r.status !== 0) return "unreadable";
|
|
139
|
+
return r.stdout.trim() === "" ? "clean" : "dirty";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const isString = (v, max) => typeof v === "string" && v.length >= 1 && v.length <= max;
|
|
143
|
+
|
|
144
|
+
export const CHANNELS = ["desktop", "phone"];
|
|
145
|
+
export const FREE_TEXT_MAX = 500;
|
|
146
|
+
const JIRA_KEY = /^[A-Z][A-Z0-9]+-[0-9]+$/;
|
|
147
|
+
const GITHUB_ISSUE_URL =
|
|
148
|
+
/^https:\/\/github\.com\/[A-Za-z0-9][A-Za-z0-9-]{0,38}\/([A-Za-z0-9._-]{1,100})\/issues\/[1-9][0-9]{0,9}$/;
|
|
149
|
+
const REPO_ISSUE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}#[1-9][0-9]{0,9}$/;
|
|
150
|
+
const ISSUE_NUMBER = /^#[1-9][0-9]{0,9}$/;
|
|
151
|
+
const JIRA_URL = /^https:\/\/([A-Za-z0-9.-]+(?::[0-9]{1,5})?)\/browse\/([A-Z][A-Z0-9]+-[0-9]+)$/;
|
|
152
|
+
const UNSAFE_CHAR = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}"\\`]/u;
|
|
153
|
+
const FLAG_TOKEN = /^-/;
|
|
154
|
+
|
|
155
|
+
const normalizeHost = (h) =>
|
|
156
|
+
String(h ?? "")
|
|
157
|
+
.trim()
|
|
158
|
+
.toLowerCase()
|
|
159
|
+
.replace(/^https?:\/\//, "")
|
|
160
|
+
.replace(/\/.*$/, "")
|
|
161
|
+
.replace(/:.*$/, "")
|
|
162
|
+
.replace(/\.$/, "");
|
|
163
|
+
|
|
164
|
+
/** The configured Jira host from prefs, normalized, or "" when there is none. */
|
|
165
|
+
export function configuredJiraHost(env = process.env) {
|
|
166
|
+
const file = env.MA_PREFS_FILE || join(homedir(), ".claude", "multi-agent-preferences.json");
|
|
167
|
+
try {
|
|
168
|
+
return normalizeHost(JSON.parse(readFileSync(file, "utf8"))?.global?.hosts?.jira);
|
|
169
|
+
} catch {
|
|
170
|
+
return "";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The words the dispatcher's routing table routes on: the first word of each row's pattern. */
|
|
175
|
+
function routingWords(dir) {
|
|
176
|
+
try {
|
|
177
|
+
const text = readFileSync(join(dir, "SKILL.md"), "utf8");
|
|
178
|
+
const start = text.indexOf("\n## Routing");
|
|
179
|
+
if (start === -1) return [];
|
|
180
|
+
const end = text.indexOf("\n## ", start + 1);
|
|
181
|
+
const section = text.slice(start, end === -1 ? undefined : end);
|
|
182
|
+
const words = [];
|
|
183
|
+
for (const line of section.split("\n")) {
|
|
184
|
+
const cell = /^\|\s*([^|]*)\|/.exec(line)?.[1] ?? "";
|
|
185
|
+
for (const m of cell.matchAll(/`([a-z][a-z0-9-]*)(?=[\s`[])/g)) words.push(m[1]);
|
|
186
|
+
}
|
|
187
|
+
return words;
|
|
188
|
+
} catch {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let reservedCache = null;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The op names and mode keywords an input may not contain, read from the
|
|
197
|
+
* command tree, the dispatcher, phases.json, launch.json and run-questions.json.
|
|
198
|
+
* `ops` is null when the command tree cannot be read.
|
|
199
|
+
*/
|
|
200
|
+
export function reservedWords({ questions, spec } = {}) {
|
|
201
|
+
if (!questions && !spec && reservedCache) return reservedCache;
|
|
202
|
+
const words = computeReservedWords(questions ?? loadQuestions(), spec ?? launchSpec());
|
|
203
|
+
if (!questions && !spec) reservedCache = words;
|
|
204
|
+
return words;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function computeReservedWords(questions, spec) {
|
|
208
|
+
const dir = commandsDir();
|
|
209
|
+
const cat = catalog();
|
|
210
|
+
const ops = cat && dir ? new Set([...cat.commands.map((c) => c.id), ...routingWords(dir)]) : null;
|
|
211
|
+
const modes = new Set([
|
|
212
|
+
...MODES,
|
|
213
|
+
...Object.keys(readSchemaJson("phases.json").modes ?? {}),
|
|
214
|
+
...Object.values(spec.hosts ?? {}).flatMap((h) => Object.keys(h.modes ?? {})),
|
|
215
|
+
...(questions.find((q) => q.id === "workspace")?.options ?? []).map((o) => o.id),
|
|
216
|
+
]);
|
|
217
|
+
return { ops, modes };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Which structured reference an input is, or null when it is none of them.
|
|
222
|
+
* A Jira URL counts only on `jiraHost`.
|
|
223
|
+
*/
|
|
224
|
+
export function structuredKind(input, jiraHost = "") {
|
|
225
|
+
if (typeof input !== "string") return null;
|
|
226
|
+
if (JIRA_KEY.test(input)) return "jira-id";
|
|
227
|
+
const gh = GITHUB_ISSUE_URL.exec(input);
|
|
228
|
+
if (gh) return /^\.+$/.test(gh[1]) ? null : "gh-url";
|
|
229
|
+
if (REPO_ISSUE.test(input)) return "gh-short";
|
|
230
|
+
if (ISSUE_NUMBER.test(input)) return "gh-num";
|
|
231
|
+
const jira = JIRA_URL.exec(input);
|
|
232
|
+
if (jira && jiraHost && normalizeHost(jira[1]) === normalizeHost(jiraHost)) return "jira-url";
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Every reason `input` may not start a run on `channel`. Pure apart from the
|
|
238
|
+
* reserved words, which are handed in.
|
|
239
|
+
*/
|
|
240
|
+
export function inputErrors(input, { channel = "desktop", jiraHost = "", reserved } = {}) {
|
|
241
|
+
if (!isString(input, 4000))
|
|
242
|
+
return ["input must be a non-empty string of at most 4000 characters"];
|
|
243
|
+
const errors = [];
|
|
244
|
+
if (UNSAFE_CHAR.test(input)) {
|
|
245
|
+
errors.push(
|
|
246
|
+
"input may not carry a newline, a control or format character, a quote, a backslash or a backtick",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (FLAG_TOKEN.test(input)) errors.push('input may not start with "-"');
|
|
250
|
+
const { ops, modes } = reserved ?? reservedWords();
|
|
251
|
+
const tokens = input.split(/\s+/).filter(Boolean);
|
|
252
|
+
for (const raw of tokens) {
|
|
253
|
+
if (FLAG_TOKEN.test(raw) && raw !== input) errors.push(`input may not carry a flag (${raw})`);
|
|
254
|
+
}
|
|
255
|
+
const first = (tokens[0] ?? "").toLowerCase().replace(/^[^a-z0-9-]+|[^a-z0-9-]+$/g, "");
|
|
256
|
+
if (ops?.has(first)) errors.push(`input may not start with the pipeline op "${first}"`);
|
|
257
|
+
if (modes.has(first)) errors.push(`input may not start with the mode keyword "${first}"`);
|
|
258
|
+
if (errors.length) return [...new Set(errors)];
|
|
259
|
+
if (structuredKind(input, jiraHost)) return [];
|
|
260
|
+
if (channel === "phone") {
|
|
261
|
+
return [
|
|
262
|
+
"the phone channel takes a structured input only: a Jira key, a GitHub issue URL, repo#N, #N, or a Jira URL on the configured host",
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
if (!ops) return ["free text cannot be checked without the command tree; use a structured input"];
|
|
266
|
+
if (input.length > FREE_TEXT_MAX) return [`free text is at most ${FREE_TEXT_MAX} characters`];
|
|
267
|
+
if (input.trim() !== input || /^[/#]/.test(input) || !/\p{L}/u.test(input)) {
|
|
268
|
+
return ["free text is a task description: no leading or trailing space, no leading / or #"];
|
|
269
|
+
}
|
|
270
|
+
if (!/\s/.test(input)) {
|
|
271
|
+
return ["a single word that is not a structured reference is refused; free text is a sentence"];
|
|
272
|
+
}
|
|
273
|
+
if (/[a-z][a-z0-9+.-]*:\/\//i.test(input)) {
|
|
274
|
+
return ["free text may not carry a URL; a GitHub or Jira link is sent as the whole input"];
|
|
275
|
+
}
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** The input as the prompt receives it: a structured reference as is, free text quoted. */
|
|
280
|
+
export function promptArgument(input, jiraHost = "") {
|
|
281
|
+
return structuredKind(input, jiraHost) ? input : `"${input}"`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Every reason a request is not acceptable. Pure: the working-tree state is
|
|
286
|
+
* handed in, so the rule is testable without a repository.
|
|
287
|
+
*
|
|
288
|
+
* @param {unknown} req
|
|
289
|
+
* @param {{questions?: object[], workingTree?: "clean"|"dirty"|"unreadable"|"unchecked",
|
|
290
|
+
* channel?: "desktop"|"phone", jiraHost?: string}} [opts]
|
|
291
|
+
* @returns {string[]}
|
|
292
|
+
*/
|
|
293
|
+
export function validateRequest(req, opts = {}) {
|
|
294
|
+
const questions = opts.questions ?? loadQuestions();
|
|
295
|
+
const workingTree = opts.workingTree ?? "unchecked";
|
|
296
|
+
const channel = opts.channel ?? "desktop";
|
|
297
|
+
const jiraHost = opts.jiraHost ?? configuredJiraHost();
|
|
298
|
+
const errors = [];
|
|
299
|
+
if (!req || typeof req !== "object" || Array.isArray(req))
|
|
300
|
+
return ["request is not a JSON object"];
|
|
301
|
+
|
|
302
|
+
for (const k of Object.keys(req)) {
|
|
303
|
+
if (!TOP_KEYS.includes(k)) errors.push(`unknown field ${k}`);
|
|
304
|
+
}
|
|
305
|
+
if (typeof req.contractVersion !== "string" || !/^1\.[0-9]+\.[0-9]+$/.test(req.contractVersion)) {
|
|
306
|
+
errors.push(`contractVersion must be 1.x.y (this pipeline speaks ${CONTRACT_VERSION})`);
|
|
307
|
+
}
|
|
308
|
+
if (!CHANNELS.includes(channel)) errors.push(`channel must be ${CHANNELS.join(" | ")}`);
|
|
309
|
+
else errors.push(...inputErrors(req.input, { channel, jiraHost, reserved: opts.reserved }));
|
|
310
|
+
if (!MODES.includes(req.mode)) errors.push(`mode must be ${MODES.join(" | ")}`);
|
|
311
|
+
const answers = req.answers;
|
|
312
|
+
if (!answers || typeof answers !== "object" || Array.isArray(answers)) {
|
|
313
|
+
errors.push("answers must be an object");
|
|
314
|
+
return errors;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const byId = new Map(questions.map((q) => [q.id, q]));
|
|
318
|
+
for (const [id, value] of Object.entries(answers)) {
|
|
319
|
+
const q = byId.get(id);
|
|
320
|
+
if (!q) {
|
|
321
|
+
errors.push(`answers.${id}: no such question in run-questions.json`);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!q.answerable) {
|
|
325
|
+
errors.push(`answers.${id}: this question is always asked and cannot be pre-filled`);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const multi = q.kind === "multi-select";
|
|
329
|
+
const values = multi ? value : [value];
|
|
330
|
+
if (multi && !Array.isArray(value)) {
|
|
331
|
+
errors.push(`answers.${id}: a multi-select answer is an array`);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (!multi && typeof value !== "string") {
|
|
335
|
+
errors.push(`answers.${id}: a single-select answer is one string`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
for (const v of values) {
|
|
339
|
+
if (!isString(v, 200)) {
|
|
340
|
+
errors.push(`answers.${id}: every value is a non-empty string of at most 200 characters`);
|
|
341
|
+
} else if (q.options && !q.options.some((o) => o.id === v)) {
|
|
342
|
+
errors.push(`answers.${id}: "${v}" is not one of ${q.options.map((o) => o.id).join(", ")}`);
|
|
343
|
+
} else if (!q.options && q.answer && !new RegExp(q.answer.pattern).test(v)) {
|
|
344
|
+
errors.push(`answers.${id}: "${v}" does not match ${q.answer.pattern}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for (const rule of q["disabled-when"] ?? []) {
|
|
348
|
+
if (!values.includes(rule.option)) continue;
|
|
349
|
+
if (rule.mode && rule.mode === req.mode) {
|
|
350
|
+
errors.push(`answers.${id}: "${rule.option}" is not allowed in ${rule.mode} mode`);
|
|
351
|
+
}
|
|
352
|
+
if (
|
|
353
|
+
rule.workingTree === "dirty" &&
|
|
354
|
+
(workingTree === "dirty" || workingTree === "unreadable")
|
|
355
|
+
) {
|
|
356
|
+
errors.push(
|
|
357
|
+
`answers.${id}: "${rule.option}" is not allowed on a working tree with uncommitted changes`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return errors;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* What Phase 0 applies: the pre-filled pickers and the one state field that
|
|
367
|
+
* records a request was used. Called only on a request validateRequest passed.
|
|
368
|
+
*/
|
|
369
|
+
export function prefillFor(req, questions = loadQuestions(), now = new Date()) {
|
|
370
|
+
const byId = new Map(questions.map((q) => [q.id, q]));
|
|
371
|
+
const prefill = {};
|
|
372
|
+
for (const [id, answer] of Object.entries(req.answers)) {
|
|
373
|
+
const q = byId.get(id);
|
|
374
|
+
prefill[id] = {
|
|
375
|
+
answer,
|
|
376
|
+
askedAt: q["asked-at"],
|
|
377
|
+
...(q.stateField ? { stateField: q.stateField } : {}),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
prefill,
|
|
382
|
+
statePatch: {
|
|
383
|
+
launchRequest: {
|
|
384
|
+
contractVersion: req.contractVersion,
|
|
385
|
+
mode: req.mode,
|
|
386
|
+
answered: Object.keys(req.answers).sort(),
|
|
387
|
+
resolvedAt: now.toISOString(),
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The whole resolve step as data, so the CLI and the tests read one answer.
|
|
395
|
+
*
|
|
396
|
+
* @param {{file?: string|null, repo?: string|null, env?: object, questions?: object[]}} args
|
|
397
|
+
*/
|
|
398
|
+
export function resolveRequest({ file = null, repo = null, env = process.env, questions } = {}) {
|
|
399
|
+
const qs = questions ?? loadQuestions();
|
|
400
|
+
const path = file || env.MA_LAUNCH_REQUEST || "";
|
|
401
|
+
const absent = { contractVersion: CONTRACT_VERSION, present: false, prefill: {}, statePatch: {} };
|
|
402
|
+
if (!path) return { code: 0, out: absent };
|
|
403
|
+
if (!existsSync(path)) {
|
|
404
|
+
return { code: 0, out: { ...absent, warning: "request-file-missing" } };
|
|
405
|
+
}
|
|
406
|
+
let req;
|
|
407
|
+
try {
|
|
408
|
+
req = JSON.parse(readFileSync(path, "utf8"));
|
|
409
|
+
} catch {
|
|
410
|
+
return {
|
|
411
|
+
code: 1,
|
|
412
|
+
out: {
|
|
413
|
+
contractVersion: CONTRACT_VERSION,
|
|
414
|
+
present: true,
|
|
415
|
+
valid: false,
|
|
416
|
+
errors: ["request is not valid JSON"],
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const workingTree = repo ? workingTreeState(repo) : "unchecked";
|
|
421
|
+
const errors = validateRequest(req, { questions: qs, workingTree });
|
|
422
|
+
if (errors.length) {
|
|
423
|
+
return {
|
|
424
|
+
code: 1,
|
|
425
|
+
out: { contractVersion: CONTRACT_VERSION, present: true, valid: false, errors },
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
code: 0,
|
|
430
|
+
out: {
|
|
431
|
+
contractVersion: CONTRACT_VERSION,
|
|
432
|
+
present: true,
|
|
433
|
+
valid: true,
|
|
434
|
+
input: req.input,
|
|
435
|
+
mode: req.mode,
|
|
436
|
+
workingTree,
|
|
437
|
+
...prefillFor(req, qs),
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
443
|
+
|
|
444
|
+
/** Where request files live, per launch.json requestFile.directory. */
|
|
445
|
+
export function requestsDir(env = process.env) {
|
|
446
|
+
return env.MA_AUTOPILOT_ROOT
|
|
447
|
+
? join(env.MA_AUTOPILOT_ROOT, "requests")
|
|
448
|
+
: join(homedir(), ".claude", "autopilot", "requests");
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Write a request file the way launch.json says a client does: named for the
|
|
453
|
+
* session, mode 0600, in a 0700 directory, never over an existing file.
|
|
454
|
+
* Returns the path. Called only with a request validateRequest passed.
|
|
455
|
+
*/
|
|
456
|
+
export function writeRequestFile(req, sessionId, env = process.env) {
|
|
457
|
+
if (!SESSION_ID.test(sessionId)) throw new Error("session id is not a UUID");
|
|
458
|
+
const dir = requestsDir(env);
|
|
459
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
460
|
+
chmodSync(dir, 0o700);
|
|
461
|
+
const path = join(dir, `${sessionId}.json`);
|
|
462
|
+
writeFileSync(path, JSON.stringify(req, null, 2) + "\n", { mode: 0o600, flag: "wx" });
|
|
463
|
+
return path;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Where the desktop client's registered launch repos live. */
|
|
467
|
+
export const launchReposPath = (env = process.env) => join(autopilotRoot(env), "launch-repos.json");
|
|
468
|
+
|
|
469
|
+
const realDir = (p) => {
|
|
470
|
+
try {
|
|
471
|
+
const real = realpathSync(p);
|
|
472
|
+
return statSync(real).isDirectory() ? real : null;
|
|
473
|
+
} catch {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const expandHome = (p) => String(p ?? "").replace(/^~(?=\/|$)/, homedir());
|
|
479
|
+
|
|
480
|
+
function readJsonOr(file, fallback) {
|
|
481
|
+
try {
|
|
482
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
483
|
+
} catch {
|
|
484
|
+
return fallback;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Real paths a launch may name: configured autopilot repos and registered ones. */
|
|
489
|
+
export function allowedLaunchRepos(env = process.env) {
|
|
490
|
+
const config = readJsonOr(join(autopilotRoot(env), "config.json"), {});
|
|
491
|
+
const registered = readJsonOr(launchReposPath(env), {});
|
|
492
|
+
const paths = [
|
|
493
|
+
...(Array.isArray(config?.repos) ? config.repos.map((r) => r?.localPath) : []),
|
|
494
|
+
...(Array.isArray(registered?.repos) ? registered.repos : []),
|
|
495
|
+
];
|
|
496
|
+
return new Set(
|
|
497
|
+
paths
|
|
498
|
+
.filter((p) => typeof p === "string" && p)
|
|
499
|
+
.map((p) => realDir(expandHome(p)))
|
|
500
|
+
.filter(Boolean),
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** Is `repoPath` a directory a launch may start in? Compared by real path. */
|
|
505
|
+
export function launchRepoAllowed(repoPath, env = process.env) {
|
|
506
|
+
const real = typeof repoPath === "string" && isAbsolute(repoPath) ? realDir(repoPath) : null;
|
|
507
|
+
return Boolean(real) && allowedLaunchRepos(env).has(real);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Add a checkout to launch-repos.json: an absolute directory that is not the
|
|
512
|
+
* home directory or the filesystem root, stored by real path in a 0600 file.
|
|
513
|
+
* The read-modify-write holds the file's lock and replaces it atomically, so
|
|
514
|
+
* two registrations at once both land. Returns the stored path.
|
|
515
|
+
*/
|
|
516
|
+
export function registerRepo(repoPath, env = process.env) {
|
|
517
|
+
if (typeof repoPath !== "string" || !isAbsolute(repoPath))
|
|
518
|
+
throw new Error("the path must be absolute");
|
|
519
|
+
const real = realDir(repoPath);
|
|
520
|
+
if (!real) throw new Error("the path is not a directory");
|
|
521
|
+
if (real === "/" || real === realDir(homedir())) {
|
|
522
|
+
throw new Error("the home directory or the root is not a repository");
|
|
523
|
+
}
|
|
524
|
+
const file = launchReposPath(env);
|
|
525
|
+
mkdirSync(autopilotRoot(env), { recursive: true, mode: 0o700 });
|
|
526
|
+
updateJsonFileSync(
|
|
527
|
+
file,
|
|
528
|
+
(current) => {
|
|
529
|
+
const repos = new Set(Array.isArray(current?.repos) ? current.repos : []);
|
|
530
|
+
repos.add(real);
|
|
531
|
+
return { repos: [...repos].sort() };
|
|
532
|
+
},
|
|
533
|
+
{ fallback: {}, mode: 0o600, timeoutMs: lockTimeoutFrom(env) },
|
|
534
|
+
);
|
|
535
|
+
return real;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** The launch-plan fields that name this machine, dropped under redact. */
|
|
539
|
+
export const LOCAL_PLAN_FIELDS = ["requestFile", "cwd", "argv", "env"];
|
|
540
|
+
|
|
541
|
+
const fill = (template, values) =>
|
|
542
|
+
template.replace(/\{([a-zA-Z]+)\}/g, (whole, k) => (k in values ? values[k] : whole));
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* What a client runs to start this request unattended, from launch.json. Pure
|
|
546
|
+
* apart from the working-tree state, which is handed in.
|
|
547
|
+
*
|
|
548
|
+
* @param {{request: object, requestFile: string, repoPath: string, sessionId: string,
|
|
549
|
+
* workingTree?: string, spec?: object, questions?: object[], redact?: boolean,
|
|
550
|
+
* channel?: "desktop"|"phone", jiraHost?: string, settingsFile?: string,
|
|
551
|
+
* runLogsRoot?: string}} args
|
|
552
|
+
* @returns {{code: number, reason?: string, out: object}}
|
|
553
|
+
*/
|
|
554
|
+
export function planLaunch({
|
|
555
|
+
request,
|
|
556
|
+
requestFile,
|
|
557
|
+
repoPath,
|
|
558
|
+
sessionId,
|
|
559
|
+
settingsFile = unattendedSettingsPath(),
|
|
560
|
+
runLogsRoot = unattendedLogsRoot(),
|
|
561
|
+
workingTree = "unchecked",
|
|
562
|
+
spec = launchSpec(),
|
|
563
|
+
questions,
|
|
564
|
+
redact = false,
|
|
565
|
+
channel = "desktop",
|
|
566
|
+
jiraHost = configuredJiraHost(),
|
|
567
|
+
}) {
|
|
568
|
+
const errors = validateRequest(request, { questions, workingTree, channel, jiraHost });
|
|
569
|
+
if (!SESSION_ID.test(String(sessionId ?? ""))) errors.push("session id must be a UUID");
|
|
570
|
+
if (typeof repoPath !== "string" || !isAbsolute(repoPath)) {
|
|
571
|
+
errors.push("the repository path must be absolute");
|
|
572
|
+
}
|
|
573
|
+
if (errors.length) {
|
|
574
|
+
return {
|
|
575
|
+
code: 1,
|
|
576
|
+
reason: "invalid-request",
|
|
577
|
+
out: { contractVersion: CONTRACT_VERSION, valid: false, errors },
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const host = spec.hosts.claude;
|
|
581
|
+
const shape = host.supported ? host.modes?.[request.mode] : undefined;
|
|
582
|
+
if (!shape) {
|
|
583
|
+
return {
|
|
584
|
+
code: 1,
|
|
585
|
+
reason: "no-launch-shape",
|
|
586
|
+
out: {
|
|
587
|
+
contractVersion: CONTRACT_VERSION,
|
|
588
|
+
valid: false,
|
|
589
|
+
errors: [`launch.json declares no unattended launch for mode ${request.mode}`],
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const values = {
|
|
594
|
+
input: promptArgument(request.input, jiraHost),
|
|
595
|
+
sessionId,
|
|
596
|
+
requestFile,
|
|
597
|
+
repoPath,
|
|
598
|
+
settingsFile,
|
|
599
|
+
runLogsRoot,
|
|
600
|
+
};
|
|
601
|
+
const plan = {
|
|
602
|
+
contractVersion: CONTRACT_VERSION,
|
|
603
|
+
valid: true,
|
|
604
|
+
verified: spec.verified === true,
|
|
605
|
+
host: "claude",
|
|
606
|
+
mode: request.mode,
|
|
607
|
+
sessionId,
|
|
608
|
+
requestFile,
|
|
609
|
+
bin: host.bin,
|
|
610
|
+
cwd: fill(host.cwd, values),
|
|
611
|
+
argv: shape.argv.map((a) => fill(a, values)),
|
|
612
|
+
env: Object.fromEntries(Object.entries(shape.env).map(([k, v]) => [k, fill(v, values)])),
|
|
613
|
+
shell: false,
|
|
614
|
+
};
|
|
615
|
+
if (redact) for (const k of LOCAL_PLAN_FIELDS) delete plan[k];
|
|
616
|
+
return { code: 0, out: plan };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function planMain(rest) {
|
|
620
|
+
let file = null;
|
|
621
|
+
let repo = null;
|
|
622
|
+
let sessionId = null;
|
|
623
|
+
let redact = false;
|
|
624
|
+
let channel = "desktop";
|
|
625
|
+
for (let i = 0; i < rest.length; i++) {
|
|
626
|
+
if (rest[i] === "--repo") repo = rest[++i] ?? null;
|
|
627
|
+
else if (rest[i] === "--session-id") sessionId = rest[++i] ?? null;
|
|
628
|
+
else if (rest[i] === "--redact") redact = true;
|
|
629
|
+
else if (rest[i] === "--channel") channel = rest[++i] ?? "";
|
|
630
|
+
else if (!file && !rest[i].startsWith("--")) file = rest[i];
|
|
631
|
+
else {
|
|
632
|
+
process.stderr.write(`launch-request: unknown argument ${rest[i]}\n`);
|
|
633
|
+
return 2;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!file || !repo || !sessionId || !CHANNELS.includes(channel)) {
|
|
637
|
+
process.stderr.write(
|
|
638
|
+
`usage: launch-request.mjs plan <file> --repo <path> --session-id <uuid> [--channel ${CHANNELS.join("|")}] [--redact]\n`,
|
|
639
|
+
);
|
|
640
|
+
return 2;
|
|
641
|
+
}
|
|
642
|
+
let request;
|
|
643
|
+
try {
|
|
644
|
+
request = JSON.parse(readFileSync(file, "utf8"));
|
|
645
|
+
} catch {
|
|
646
|
+
process.stderr.write("launch-request: the request file is missing or not JSON\n");
|
|
647
|
+
return 1;
|
|
648
|
+
}
|
|
649
|
+
if (!launchRepoAllowed(repo)) {
|
|
650
|
+
const out = {
|
|
651
|
+
contractVersion: CONTRACT_VERSION,
|
|
652
|
+
valid: false,
|
|
653
|
+
errors: ["the repository is neither a configured autopilot repo nor a registered one"],
|
|
654
|
+
};
|
|
655
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
656
|
+
return 1;
|
|
657
|
+
}
|
|
658
|
+
const { code, out } = planLaunch({
|
|
659
|
+
request,
|
|
660
|
+
requestFile: file,
|
|
661
|
+
repoPath: repo,
|
|
662
|
+
sessionId,
|
|
663
|
+
workingTree: workingTreeState(repo),
|
|
664
|
+
redact,
|
|
665
|
+
channel,
|
|
666
|
+
});
|
|
667
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
668
|
+
return code;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function registerMain(rest) {
|
|
672
|
+
if (rest.length !== 1) {
|
|
673
|
+
process.stderr.write("usage: launch-request.mjs register-repo <absolute path>\n");
|
|
674
|
+
return 2;
|
|
675
|
+
}
|
|
676
|
+
if (isUnattended()) {
|
|
677
|
+
process.stderr.write(
|
|
678
|
+
"launch-request: an unattended run may not register a launch repository\n",
|
|
679
|
+
);
|
|
680
|
+
return 3;
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
const stored = registerRepo(rest[0]);
|
|
684
|
+
process.stdout.write(
|
|
685
|
+
JSON.stringify({ contractVersion: CONTRACT_VERSION, registered: stored }) + "\n",
|
|
686
|
+
);
|
|
687
|
+
return 0;
|
|
688
|
+
} catch (err) {
|
|
689
|
+
process.stderr.write(`launch-request: ${err.message}\n`);
|
|
690
|
+
return 2;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function main(argv) {
|
|
695
|
+
const [cmd, ...rest] = argv;
|
|
696
|
+
if (cmd === "questions" || cmd === "spec") {
|
|
697
|
+
if (rest.length) {
|
|
698
|
+
process.stderr.write(`launch-request: ${cmd} takes no arguments\n`);
|
|
699
|
+
return 2;
|
|
700
|
+
}
|
|
701
|
+
const doc = cmd === "questions" ? questionsDocument() : launchSpec();
|
|
702
|
+
process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
|
|
703
|
+
return 0;
|
|
704
|
+
}
|
|
705
|
+
if (cmd === "plan") return planMain(rest);
|
|
706
|
+
if (cmd === "register-repo") return registerMain(rest);
|
|
707
|
+
let file = null;
|
|
708
|
+
let repo = null;
|
|
709
|
+
for (let i = 0; i < rest.length; i++) {
|
|
710
|
+
if (rest[i] === "--repo") repo = rest[++i] ?? null;
|
|
711
|
+
else if (!file && !rest[i].startsWith("--")) file = rest[i];
|
|
712
|
+
else {
|
|
713
|
+
process.stderr.write(`launch-request: unknown argument ${rest[i]}\n`);
|
|
714
|
+
return 2;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (cmd !== "validate" && cmd !== "resolve") {
|
|
718
|
+
process.stderr.write(
|
|
719
|
+
"usage: launch-request.mjs validate <file> | resolve [<file>] [--repo <path>] | plan <file> --repo <path> --session-id <uuid> [--channel desktop|phone] | questions | spec | register-repo <path>\n",
|
|
720
|
+
);
|
|
721
|
+
return 2;
|
|
722
|
+
}
|
|
723
|
+
if (cmd === "validate" && !file) {
|
|
724
|
+
process.stderr.write("launch-request: validate needs a file\n");
|
|
725
|
+
return 2;
|
|
726
|
+
}
|
|
727
|
+
if (cmd === "validate" && !existsSync(file)) {
|
|
728
|
+
const out = {
|
|
729
|
+
contractVersion: CONTRACT_VERSION,
|
|
730
|
+
present: false,
|
|
731
|
+
valid: false,
|
|
732
|
+
errors: ["no such file"],
|
|
733
|
+
};
|
|
734
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
735
|
+
return 1;
|
|
736
|
+
}
|
|
737
|
+
const { code, out } = resolveRequest({ file, repo });
|
|
738
|
+
if (out.warning) process.stderr.write(`launch-request: ${out.warning}, continuing without it\n`);
|
|
739
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
740
|
+
return code;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (invokedDirectly(import.meta.url)) {
|
|
744
|
+
runMain("launch-request", () => {
|
|
745
|
+
process.exitCode = main(process.argv.slice(2));
|
|
746
|
+
});
|
|
747
|
+
}
|