@mmerterden/multi-agent-pipeline 20.2.1 → 20.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +919 -1
- package/README.md +104 -81
- package/README.tr.md +103 -62
- package/docs/FIGMA_PIPELINE.md +35 -35
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0007-multi-tool-adapter-framework.md +6 -0
- package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +7 -0
- package/docs/architecture.md +50 -14
- package/docs/best-practices.md +1 -1
- package/docs/ecosystem.md +56 -32
- package/docs/facts.json +10 -10
- package/docs/features.md +97 -5
- package/docs/recovery-guide.md +7 -14
- package/docs/server-readiness.md +31 -24
- package/index.js +1 -1
- package/install/_common.mjs +3 -5
- package/install/_platform-filter.mjs +23 -1
- package/install/_unattended-profile.mjs +321 -75
- package/install/claude.mjs +51 -10
- package/install/codex.mjs +2 -0
- package/install/copilot.mjs +2 -0
- package/install/index.mjs +30 -17
- package/install/templates/claude-hooks.json +16 -5
- package/install/templates/copilot-instructions.md +1 -1
- package/install/templates/multi-agent-autopilot-awake.plist.template +48 -0
- package/install/templates/multi-agent-autopilot.plist.template +12 -5
- package/install/unattended-profile-legacy.json +80 -0
- package/manifest.json +617 -483
- package/package.json +8 -3
- package/pipeline/agents/code-reviewer.md +10 -0
- package/pipeline/agents/plan-critic.md +98 -0
- package/pipeline/agents/security-auditor.md +10 -0
- package/pipeline/agents/task-clarifier.md +10 -0
- package/pipeline/commands/multi-agent/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +12 -1
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +36 -6
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +77 -12
- package/pipeline/commands/multi-agent/autopilot-status/SKILL.md +29 -8
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/design-check/SKILL.md +16 -11
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/doctor/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/feedback/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/forget/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/graph/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/help/SKILL.md +37 -39
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/issue/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/jira/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/kill/SKILL.md +9 -3
- package/pipeline/commands/multi-agent/language/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/log/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/purge/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/research/SKILL.md +49 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +29 -6
- package/pipeline/commands/multi-agent/review/SKILL.md +28 -9
- package/pipeline/commands/multi-agent/review-analysis/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/review-issue/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/review-jira/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-off/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-on/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-status/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/routines/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/save/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/scaffold/SKILL.md +47 -0
- package/pipeline/commands/multi-agent/scan/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/search/SKILL.md +12 -8
- package/pipeline/commands/multi-agent/security-review/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/serve/SKILL.md +62 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +27 -36
- package/pipeline/commands/multi-agent/stack/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +10 -9
- package/pipeline/commands/multi-agent/steer/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
- package/pipeline/commands/multi-agent/test/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-accessibility/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-dark-mode/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-dynamic-type/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-screenshots/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +4 -0
- package/pipeline/contract/CHANGELOG.md +74 -0
- package/pipeline/contract/README.md +126 -0
- package/pipeline/contract/build.mjs +427 -0
- package/pipeline/contract/fixtures/answer-result.json +11 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +8 -0
- package/pipeline/contract/fixtures/error-unauthorized.json +5 -0
- package/pipeline/contract/fixtures/error-unsigned.json +5 -0
- package/pipeline/contract/fixtures/issues-empty.json +18 -0
- package/pipeline/contract/fixtures/launch-plan.json +31 -0
- package/pipeline/contract/fixtures/runs-awaiting-question.json +70 -0
- package/pipeline/contract/fixtures/runs-empty.json +6 -0
- package/pipeline/contract/fixtures/runs-failed.json +84 -0
- package/pipeline/contract/fixtures/runs-old-schema.json +70 -0
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +101 -0
- package/pipeline/contract/fixtures/runs-pr-opened.json +106 -0
- package/pipeline/contract/fixtures/runs-running.json +84 -0
- package/pipeline/contract/fixtures/worktrees-empty.json +4 -0
- package/pipeline/contract/frozen/toolbox.json +107 -0
- package/pipeline/contract/manifest.json +263 -0
- package/pipeline/contract/types/index.d.ts +343 -0
- package/pipeline/lib/_jira-auth.sh +6 -2
- package/pipeline/lib/account-resolver.sh +1 -1
- package/pipeline/lib/autopilot-state.sh +19 -0
- package/pipeline/lib/context-link-extractor.sh +12 -5
- package/pipeline/lib/credential-inventory.sh +12 -5
- package/pipeline/lib/credential-store.sh +116 -185
- package/pipeline/lib/fetch-confluence.sh +44 -3
- package/pipeline/lib/fetch-document.sh +3 -4
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/figma-mcp-refresh.sh +2 -2
- package/pipeline/lib/figma-token.sh +5 -1
- package/pipeline/lib/issue-fetcher.sh +233 -16
- package/pipeline/lib/json-file-lock.mjs +172 -0
- package/pipeline/lib/model-dispatch.sh +21 -12
- package/pipeline/lib/model-rung.sh +6 -1
- package/pipeline/lib/multi-repo-pipeline.sh +1 -1
- package/pipeline/lib/outbound-gate.mjs +46 -16
- package/pipeline/lib/parse-complaints.sh +14 -7
- package/pipeline/lib/plan-todos.sh +3 -3
- package/pipeline/lib/post-pr-review.sh +9 -9
- package/pipeline/lib/pr-request-location.mjs +85 -0
- package/pipeline/lib/regular-file.mjs +153 -0
- package/pipeline/lib/repo-hygiene.sh +17 -0
- package/pipeline/lib/route-state.sh +5 -1
- package/pipeline/lib/run-paths.sh +3 -2
- package/pipeline/lib/stack-detect.sh +19 -1
- package/pipeline/lib/unattended-profile-check.mjs +178 -0
- package/pipeline/lib/unattended-settings-location.mjs +28 -0
- package/pipeline/lib/unattended.mjs +76 -0
- package/pipeline/lib/unattended.sh +32 -0
- package/pipeline/lib/untrusted.mjs +76 -0
- package/pipeline/lib/usage-endpoint.mjs +33 -0
- package/pipeline/lib/user-facing.mjs +82 -0
- package/pipeline/lib/user-facing.sh +58 -0
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +4 -0
- package/pipeline/multi-agent-refs/analysis/locked.md +2 -2
- package/pipeline/multi-agent-refs/analysis/render.md +4 -3
- package/pipeline/multi-agent-refs/analysis/resolve.md +3 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +4 -5
- package/pipeline/multi-agent-refs/analysis-template.md +10 -17
- package/pipeline/multi-agent-refs/channels/jira.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
- package/pipeline/multi-agent-refs/conventions-defaults.md +32 -32
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -17
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +37 -9
- package/pipeline/multi-agent-refs/features/autopilot-operations.md +277 -0
- package/pipeline/multi-agent-refs/features/constitution.md +196 -0
- package/pipeline/multi-agent-refs/features/doctor.md +6 -3
- package/pipeline/multi-agent-refs/features/external-context-injection.md +6 -1
- package/pipeline/multi-agent-refs/features/jira-context.md +1 -1
- package/pipeline/multi-agent-refs/features/maturity-followup.md +43 -23
- package/pipeline/multi-agent-refs/features/model-fallback.md +9 -0
- package/pipeline/multi-agent-refs/features/phone-api.md +306 -0
- package/pipeline/multi-agent-refs/features/plan-critic.md +159 -0
- package/pipeline/multi-agent-refs/features/research.md +150 -0
- package/pipeline/multi-agent-refs/features/review-decision.md +185 -0
- package/pipeline/multi-agent-refs/features/scaffold.md +160 -0
- package/pipeline/multi-agent-refs/features/security-audit.md +6 -2
- package/pipeline/multi-agent-refs/features/skill-conformance.md +4 -1
- package/pipeline/multi-agent-refs/features/stack-adapters.md +116 -0
- package/pipeline/multi-agent-refs/features/unattended-gates.md +316 -0
- package/pipeline/multi-agent-refs/features/unattended-security.md +731 -0
- package/pipeline/multi-agent-refs/features/url-enrichment.md +2 -2
- package/pipeline/multi-agent-refs/features/usage-reporting.md +127 -35
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/features/visual-evidence.md +7 -7
- package/pipeline/multi-agent-refs/keychain.md +6 -11
- package/pipeline/multi-agent-refs/payload-contracts.md +2 -2
- package/pipeline/multi-agent-refs/phases/log-format.md +2 -3
- package/pipeline/multi-agent-refs/phases/modes.md +10 -12
- package/pipeline/multi-agent-refs/phases/operations.md +11 -5
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +73 -81
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +22 -22
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +42 -66
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +45 -79
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +28 -38
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +27 -25
- package/pipeline/multi-agent-refs/phases.md +1 -1
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/progress-contract.md +13 -16
- package/pipeline/multi-agent-refs/readiness-review.md +1 -1
- package/pipeline/multi-agent-refs/research/engine.md +91 -0
- package/pipeline/multi-agent-refs/rules.md +6 -4
- package/pipeline/multi-agent-refs/setup/repo-discovery.md +2 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +2 -2
- package/pipeline/multi-agent-refs/unattended-contract.md +68 -16
- package/pipeline/rules/figma-pipeline.md +12 -12
- package/pipeline/schemas/agent-state.schema.json +480 -18
- package/pipeline/schemas/analysis-spec.schema.json +4 -4
- package/pipeline/schemas/answer-request.schema.json +24 -0
- package/pipeline/schemas/answer-result.schema.json +28 -0
- package/pipeline/schemas/autopilot-config.schema.json +111 -13
- package/pipeline/schemas/command-parameters.schema.json +99 -0
- package/pipeline/schemas/constitution.schema.json +56 -0
- package/pipeline/schemas/contract-error.schema.json +52 -0
- package/pipeline/schemas/design-check-config.schema.json +5 -1
- package/pipeline/schemas/issues.schema.json +61 -0
- package/pipeline/schemas/launch-plan.schema.json +46 -0
- package/pipeline/schemas/launch-request.schema.json +45 -0
- package/pipeline/schemas/launch.json +61 -0
- package/pipeline/schemas/launch.schema.json +84 -0
- package/pipeline/schemas/phases.json +2 -2
- package/pipeline/schemas/phases.schema.json +68 -0
- package/pipeline/schemas/phone-devices.schema.json +61 -0
- package/pipeline/schemas/phone-signed-request.schema.json +67 -0
- package/pipeline/schemas/plan-critique.schema.json +99 -0
- package/pipeline/schemas/plan-todos.schema.json +7 -7
- package/pipeline/schemas/planning-output.schema.json +5 -0
- package/pipeline/schemas/pr-request.schema.json +46 -0
- package/pipeline/schemas/prefs.schema.json +79 -8
- package/pipeline/schemas/research-output.schema.json +118 -0
- package/pipeline/schemas/review-file-exclusions.schema.json +25 -0
- package/pipeline/schemas/reviewer-output.schema.json +40 -4
- package/pipeline/schemas/run-questions.json +392 -0
- package/pipeline/schemas/run-questions.schema.json +118 -0
- package/pipeline/schemas/runs-index.schema.json +189 -0
- package/pipeline/schemas/scaffold-manifest.schema.json +43 -0
- package/pipeline/schemas/secret-patterns.schema.json +28 -0
- package/pipeline/schemas/stack-adapters.json +527 -0
- package/pipeline/schemas/stack-adapters.schema.json +184 -0
- package/pipeline/schemas/token-budget.json +1 -1
- package/pipeline/schemas/token-budget.schema.json +26 -0
- package/pipeline/schemas/triage-output.schema.json +64 -3
- package/pipeline/schemas/unattended-policy.json +139 -0
- package/pipeline/schemas/unattended-policy.schema.json +73 -0
- package/pipeline/schemas/unattended-profile.json +248 -0
- package/pipeline/schemas/unattended-profile.schema.json +198 -0
- package/pipeline/schemas/worktrees.schema.json +51 -0
- package/pipeline/scripts/README.md +1 -0
- package/pipeline/scripts/_autopilot-config.mjs +130 -0
- package/pipeline/scripts/_autopilot-ops.mjs +567 -0
- package/pipeline/scripts/_autopilot-outcomes.mjs +174 -0
- package/pipeline/scripts/_command-contract.mjs +384 -0
- package/pipeline/scripts/_cost.mjs +40 -0
- package/pipeline/scripts/_notices.mjs +160 -0
- package/pipeline/scripts/_phone-auth.mjs +485 -0
- package/pipeline/scripts/_pre-existing.mjs +294 -0
- package/pipeline/scripts/_redact.mjs +77 -0
- package/pipeline/scripts/_run-paths.mjs +4 -2
- package/pipeline/scripts/_stack-adapter.mjs +678 -0
- package/pipeline/scripts/_stack-routing.mjs +1 -1
- package/pipeline/scripts/agent-guard.py +348 -37
- package/pipeline/scripts/agent-guard.sh +41 -13
- package/pipeline/scripts/analysis-story-tree.mjs +79 -3
- package/pipeline/scripts/answer-question.mjs +181 -0
- package/pipeline/scripts/audit-log-rotate.sh +1 -4
- package/pipeline/scripts/audit-log.sh +4 -4
- package/pipeline/scripts/autopilot-arming.mjs +389 -21
- package/pipeline/scripts/autopilot-awake.mjs +255 -0
- package/pipeline/scripts/autopilot-intake.mjs +137 -36
- package/pipeline/scripts/autopilot-menubar.swift +156 -44
- package/pipeline/scripts/autopilot-publish.mjs +1625 -0
- package/pipeline/scripts/autopilot-runner.mjs +1678 -222
- package/pipeline/scripts/autopilot-status.sh +198 -33
- package/pipeline/scripts/build-lock.sh +120 -0
- package/pipeline/scripts/build-references.mjs +4 -1
- package/pipeline/scripts/build-stack-plugins.mjs +59 -22
- package/pipeline/scripts/capture-flush.sh +1 -1
- package/pipeline/scripts/capture-resume.sh +13 -9
- package/pipeline/scripts/check-derived-drift.mjs +52 -11
- package/pipeline/scripts/commands.mjs +88 -0
- package/pipeline/scripts/constitution.mjs +362 -0
- package/pipeline/scripts/contract-server.mjs +776 -0
- package/pipeline/scripts/cost-analyze.mjs +89 -39
- package/pipeline/scripts/diff-explain.mjs +12 -1
- package/pipeline/scripts/doctor.mjs +77 -28
- package/pipeline/scripts/evidence-gate.mjs +192 -12
- package/pipeline/scripts/feedback-send.mjs +4 -2
- package/pipeline/scripts/gate-ledger.mjs +449 -0
- package/pipeline/scripts/gc-abandoned.sh +132 -13
- package/pipeline/scripts/gen-facts.mjs +31 -15
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/github-ssh-setup.sh +140 -29
- package/pipeline/scripts/graph-mermaid.mjs +4 -1
- package/pipeline/scripts/issues.mjs +236 -0
- package/pipeline/scripts/jira-attach.sh +6 -2
- package/pipeline/scripts/jira-search.sh +4 -3
- package/pipeline/scripts/keychain-save.sh +125 -24
- package/pipeline/scripts/keychain.py +63 -93
- package/pipeline/scripts/launch-request.mjs +747 -0
- package/pipeline/scripts/localize-commands.mjs +4 -10
- package/pipeline/scripts/log-metric.sh +6 -5
- package/pipeline/scripts/maturity-followup.mjs +13 -4
- package/pipeline/scripts/memory-save.sh +25 -0
- package/pipeline/scripts/migrate-prefs.mjs +4 -3
- package/pipeline/scripts/open-questions-gate.mjs +276 -0
- package/pipeline/scripts/phase-tracker.sh +41 -27
- package/pipeline/scripts/phase0-exit-gate.mjs +22 -4
- package/pipeline/scripts/phone-devices.mjs +224 -0
- package/pipeline/scripts/plan-coverage-gate.mjs +200 -66
- package/pipeline/scripts/plan-critique-gate.mjs +591 -0
- package/pipeline/scripts/pr-request.mjs +188 -0
- package/pipeline/scripts/pre-commit-check.sh +115 -4
- package/pipeline/scripts/probe-evidence-capability.sh +44 -5
- package/pipeline/scripts/record-phase.mjs +71 -0
- package/pipeline/scripts/render-agent-log-cost.sh +17 -2
- package/pipeline/scripts/render-cost-summary.sh +1 -1
- package/pipeline/scripts/render-work-summary.sh +1 -1
- package/pipeline/scripts/require-supported-version.sh +4 -1
- package/pipeline/scripts/research-gate.mjs +704 -0
- package/pipeline/scripts/review-decision-gate.mjs +403 -0
- package/pipeline/scripts/routine-registry.mjs +5 -2
- package/pipeline/scripts/runs-index.mjs +135 -27
- package/pipeline/scripts/scaffold-gate.mjs +393 -0
- package/pipeline/scripts/skill-conformance.mjs +25 -8
- package/pipeline/scripts/skill-siblings.mjs +2 -1
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +32 -6
- package/pipeline/scripts/smoke-schema-validation.sh +6 -2
- package/pipeline/scripts/spec-consistency-gate.mjs +469 -0
- package/pipeline/scripts/symbol-existence-gate.mjs +450 -0
- package/pipeline/scripts/test-gap-scan.mjs +40 -2
- package/pipeline/scripts/test-integrity-gate.mjs +20 -4
- package/pipeline/scripts/test-strength.mjs +484 -0
- package/pipeline/scripts/test-summary.mjs +651 -0
- package/pipeline/scripts/triage-memory.mjs +49 -9
- package/pipeline/scripts/unattended_policy.py +2786 -0
- package/pipeline/scripts/uninstall.mjs +10 -10
- package/pipeline/scripts/update-issue-progress.sh +1 -1
- package/pipeline/scripts/usage-identity.mjs +288 -0
- package/pipeline/scripts/usage-register.mjs +187 -67
- package/pipeline/scripts/usage-report.mjs +232 -67
- package/pipeline/scripts/validate-complaint-doc.mjs +28 -10
- package/pipeline/scripts/validate-planning.mjs +6 -0
- package/pipeline/scripts/verify-citations.mjs +151 -38
- package/pipeline/scripts/verify.mjs +58 -18
- package/pipeline/scripts/worktree-prepare.sh +126 -0
- package/pipeline/scripts/worktrees.mjs +124 -0
- package/pipeline/scripts/write-state.mjs +48 -17
- package/pipeline/skills/.skill-manifest.json +222 -226
- package/pipeline/skills/.skills-index.json +77 -88
- package/pipeline/skills/shared/README.md +44 -45
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +3 -2
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -9
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -5
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +11 -3
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-autopilot-off/SKILL.md +32 -6
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +66 -8
- package/pipeline/skills/shared/core/multi-agent-autopilot-status/SKILL.md +27 -9
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -4
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +7 -4
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-research/SKILL.md +35 -0
- package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +7 -3
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-scaffold/SKILL.md +30 -0
- package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +60 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +7 -8
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +10 -10
- package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-accessibility/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-dark-mode/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-dynamic-type/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-screenshots/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -1
- package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +46 -0
- package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +9 -3
- package/pipeline/skills/shared/external/NOTICE-paul-hudson-skills.md +54 -0
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +9 -11
- package/pipeline/skills/shared/external/NOTICE-vibeship-spawner-skills.md +203 -0
- package/pipeline/skills/shared/external/NOTICE-xcode-build-skills.md +10 -3
- package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +131 -25
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -3
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +85 -76
- package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +51 -3
- package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +9 -0
- package/pipeline/skills/shared/external/android-datastore/SKILL.md +4 -3
- package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +4 -3
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +106 -181
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +147 -0
- package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +45 -4
- package/pipeline/skills/shared/external/android-performance/SKILL.md +27 -2
- package/pipeline/skills/shared/external/android-performance/references/patterns.md +37 -37
- package/pipeline/skills/shared/external/api-patterns/SKILL.md +111 -67
- package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +128 -0
- package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +136 -186
- package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +140 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +207 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/operations.md +66 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +175 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +4 -3
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +2 -0
- package/pipeline/skills/shared/external/architecture/SKILL.md +106 -39
- package/pipeline/skills/shared/external/architecture/references/adr-and-review.md +76 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +2 -0
- package/pipeline/skills/shared/external/avkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/background-processing/SKILL.md +2 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +3 -2
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +2 -0
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +3 -2
- package/pipeline/skills/shared/external/clean-code/SKILL.md +192 -90
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +2 -1
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/compose-components/SKILL.md +4 -4
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +22 -22
- package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +10 -10
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +13 -6
- package/pipeline/skills/shared/external/compose-testing/references/patterns.md +59 -59
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +2 -0
- package/pipeline/skills/shared/external/context-compression/SKILL.md +118 -250
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-motion/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +2 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +2 -1
- package/pipeline/skills/shared/external/council/SKILL.md +2 -1
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +3 -2
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +2 -0
- package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +130 -20
- package/pipeline/skills/shared/external/debugging-strategies/references/hard-cases.md +54 -0
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +2 -0
- package/pipeline/skills/shared/external/docker-expert/SKILL.md +137 -381
- package/pipeline/skills/shared/external/docker-expert/references/patterns.md +98 -0
- package/pipeline/skills/shared/external/energykit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +3 -1
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +116 -185
- package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +235 -0
- package/pipeline/skills/shared/external/fastapi-pro/references/testing-and-deployment.md +69 -0
- package/pipeline/skills/shared/external/firebase/SKILL.md +4 -3
- package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +151 -293
- package/pipeline/skills/shared/external/github-actions-templates/references/patterns.md +96 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/hig-components-content/SKILL.md +69 -72
- package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +111 -0
- package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +77 -86
- package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +98 -0
- package/pipeline/skills/shared/external/hig-components-status/SKILL.md +150 -78
- package/pipeline/skills/shared/external/hig-components-system/SKILL.md +76 -97
- package/pipeline/skills/shared/external/hig-components-system/references/surfaces.md +98 -0
- package/pipeline/skills/shared/external/hig-foundations/SKILL.md +48 -76
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +132 -0
- package/pipeline/skills/shared/external/hig-inputs/SKILL.md +147 -106
- package/pipeline/skills/shared/external/hig-patterns/SKILL.md +61 -77
- package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +92 -0
- package/pipeline/skills/shared/external/hig-platforms/SKILL.md +147 -77
- package/pipeline/skills/shared/external/hig-technologies/SKILL.md +61 -121
- package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +108 -0
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +2 -0
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +3 -2
- package/pipeline/skills/shared/external/humanizer/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +14 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +4 -3
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +8 -6
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +11 -11
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +2 -0
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +102 -193
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +103 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -2
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +7 -9
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +2 -2
- package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +4 -3
- package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +5 -3
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +2 -0
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +2 -0
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +149 -46
- package/pipeline/skills/shared/external/monorepo-architect/references/patterns.md +70 -0
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +2 -0
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/natural-language/SKILL.md +2 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +3 -2
- package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +91 -21
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +247 -0
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/testing-and-frameworks.md +57 -0
- package/pipeline/skills/shared/external/observability-engineer/SKILL.md +155 -232
- package/pipeline/skills/shared/external/observability-engineer/references/patterns.md +75 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +2 -0
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +18 -18
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +2 -1
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +2 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +3 -2
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +2 -2
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +3 -2
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +7 -7
- package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +69 -69
- package/pipeline/skills/shared/external/room-database/references/patterns.md +126 -126
- package/pipeline/skills/shared/external/search-first/SKILL.md +2 -1
- package/pipeline/skills/shared/external/security-review/SKILL.md +4 -3
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +2 -2
- package/pipeline/skills/shared/external/signal-community/SKILL.md +1 -1
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +2 -1
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +2 -1
- package/pipeline/skills/shared/external/spm-build-analysis/SKILL.md +2 -0
- package/pipeline/skills/shared/external/storekit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swift-concurrency-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swift-security/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +10 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftdata-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swiftui-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +4 -3
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +3 -2
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +3 -2
- package/pipeline/skills/shared/external/tipkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +3 -2
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +3 -2
- package/pipeline/skills/shared/external/web-performance/SKILL.md +3 -2
- package/pipeline/skills/shared/external/web-testing/SKILL.md +3 -2
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +1 -1
- package/pipeline/skills/shared/external/xcode-build-benchmark/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-build-orchestrator/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/SKILL.md +2 -0
- package/pipeline/skills/skills-index.md +40 -41
- package/docs/token-budget-history.md +0 -24
- package/pipeline/skills/shared/external/agentflow/SKILL.md +0 -199
- package/pipeline/skills/shared/external/android-ui-verification/SKILL.md +0 -66
- package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +0 -299
- package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +0 -255
- package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +0 -167
- package/pipeline/skills/shared/external/closed-loop-delivery/SKILL.md +0 -116
- package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -216
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* review-decision-gate.mjs - a blocking finding needs more than one model's
|
|
4
|
+
* opinion before it stops an autopilot run.
|
|
5
|
+
*
|
|
6
|
+
* Every reviewer on Claude Code is a Claude model, so a panel shares its blind
|
|
7
|
+
* spots and a single reviewer's blocker is a judgement nobody independent has
|
|
8
|
+
* checked. With the quality gates active (lib/unattended.mjs, gatesActive), an
|
|
9
|
+
* `accepted[]` finding of severity `blocking` keeps that severity only when:
|
|
10
|
+
*
|
|
11
|
+
* corroborated at least two independent reviewer outputs of the iteration
|
|
12
|
+
* carry a blocking finding that is the same finding (below);
|
|
13
|
+
* failing-test its `verification` (Step 3.7, verify-by-test) is
|
|
14
|
+
* `confirmed` and `evidencePath` is a non-empty log inside the
|
|
15
|
+
* checkout that shows a failing test and no pass;
|
|
16
|
+
* test-integrity it is the finding test-integrity-gate.mjs produced for that
|
|
17
|
+
* file, which is a command's output and not a model's.
|
|
18
|
+
*
|
|
19
|
+
* Anything else is lowered to `important`, stays in `accepted[]` (triage judged
|
|
20
|
+
* it real and in scope, so the rework still fixes it) and is listed with its
|
|
21
|
+
* reason in the report and in the ledger entry. `approved` is recomputed.
|
|
22
|
+
*
|
|
23
|
+
* The same finding: the same file (diff prefixes stripped) and either the same
|
|
24
|
+
* fingerprint (finding-fingerprint.mjs: file plus ruleId, else file plus the
|
|
25
|
+
* normalized issue text) or cited lines at most LINE_WINDOW apart, where a
|
|
26
|
+
* whole-file finding (line 0) matches by fingerprint only and two findings
|
|
27
|
+
* citing different ruleIds or different CWEs never match. Independence is one
|
|
28
|
+
* entry of `state.reviewIterations[i].reviewers[]` (or one `--source` file):
|
|
29
|
+
* a separate dispatch that saw the diff and not the other outputs.
|
|
30
|
+
*
|
|
31
|
+
* Step 2.5, the rebuttal round, shows each reviewer the others' findings and
|
|
32
|
+
* replaces its output, which is how reviewers converge on each other. With the
|
|
33
|
+
* gates active a reviewer entry with `roundCount` above 1 fails the gate (exit
|
|
34
|
+
* 1, nothing rewritten); `--rebuttal-allowed` is the check Step 2.5 runs before
|
|
35
|
+
* dispatching the round.
|
|
36
|
+
*
|
|
37
|
+
* The verdict is recorded as `review-decision` through gate-ledger.mjs. With
|
|
38
|
+
* the gates off the decisions are printed marked advisory, the exit is 0 and
|
|
39
|
+
* neither the triage file nor the state is written.
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* review-decision-gate.mjs <triage.json> [--state <agent-state.json>] [--iteration <n>]
|
|
43
|
+
* [--source <reviewer-output.json>]... [--integrity <test-integrity.json>]
|
|
44
|
+
* [--repo <dir>] [--dry-run] [--json]
|
|
45
|
+
* review-decision-gate.mjs --rebuttal-allowed [--state <agent-state.json>] [--json]
|
|
46
|
+
*
|
|
47
|
+
* Exit (gates active): 0 decided · 1 a rebuttal round ran, or --rebuttal-allowed refused ·
|
|
48
|
+
* 2 usage · 3 unreadable input, or an accepted blocker with no reviewer record (an empty
|
|
49
|
+
* `reviewers: []` is no record). A missing or empty --integrity file is no findings,
|
|
50
|
+
* and a missing or empty --source is an audit that did not run.
|
|
51
|
+
* Attended: 0, and 2 on a usage error.
|
|
52
|
+
*
|
|
53
|
+
* @module pipeline/scripts/review-decision-gate
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
import { existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
57
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
58
|
+
|
|
59
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
60
|
+
import { findingFingerprint, FINGERPRINT_RE, normalizeFile } from "./_fingerprint.mjs";
|
|
61
|
+
import { compileMarkers, getAdapter, resolveStack } from "./_stack-adapter.mjs";
|
|
62
|
+
import { gateContext, recordQuietly } from "./gate-ledger.mjs";
|
|
63
|
+
import { summarize } from "./test-summary.mjs";
|
|
64
|
+
|
|
65
|
+
export const GATE = "review-decision";
|
|
66
|
+
export const LINE_WINDOW = 3;
|
|
67
|
+
const FAILURE_MARKER = /\bFAILED\b|✗|\b[1-9][0-9]*\s+(?:failures?|failing|failed)\b/;
|
|
68
|
+
|
|
69
|
+
function fingerprintOf(f) {
|
|
70
|
+
return typeof f?.fingerprint === "string" && FINGERPRINT_RE.test(f.fingerprint)
|
|
71
|
+
? f.fingerprint
|
|
72
|
+
: findingFingerprint(f);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function category(f) {
|
|
76
|
+
if (typeof f?.ruleId === "string" && f.ruleId.trim()) return `rule:${f.ruleId.trim()}`;
|
|
77
|
+
if (typeof f?.security?.cwe === "string") return `cwe:${f.security.cwe}`;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {object} a
|
|
83
|
+
* @param {object} b
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
export function sameFinding(a, b) {
|
|
87
|
+
if (normalizeFile(a?.file) !== normalizeFile(b?.file)) return false;
|
|
88
|
+
if (fingerprintOf(a) === fingerprintOf(b)) return true;
|
|
89
|
+
const ca = category(a);
|
|
90
|
+
const cb = category(b);
|
|
91
|
+
if (ca && cb && ca !== cb) return false;
|
|
92
|
+
const la = Number.isInteger(a?.line) ? a.line : 0;
|
|
93
|
+
const lb = Number.isInteger(b?.line) ? b.line : 0;
|
|
94
|
+
return la > 0 && lb > 0 && Math.abs(la - lb) <= LINE_WINDOW;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function real(p) {
|
|
98
|
+
try {
|
|
99
|
+
return realpathSync(p);
|
|
100
|
+
} catch {
|
|
101
|
+
return resolve(p);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function failureMarkers(root) {
|
|
106
|
+
const list = [FAILURE_MARKER];
|
|
107
|
+
try {
|
|
108
|
+
for (const { id } of resolveStack(root).adapters) {
|
|
109
|
+
const m = compileMarkers(getAdapter(id), "test");
|
|
110
|
+
if (m) list.push(...m.failure);
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// An unreadable adapter file leaves the generic marker.
|
|
114
|
+
}
|
|
115
|
+
return list;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Does the log at `path` show a failing test?
|
|
120
|
+
*
|
|
121
|
+
* @param {string} path relative to `root`, or absolute inside it
|
|
122
|
+
* @param {string} root the checkout
|
|
123
|
+
* @returns {{ok: boolean, reason: string}}
|
|
124
|
+
*/
|
|
125
|
+
export function evidenceFromLog(path, root) {
|
|
126
|
+
if (typeof path !== "string" || !path.trim()) {
|
|
127
|
+
return { ok: false, reason: "no evidencePath recorded" };
|
|
128
|
+
}
|
|
129
|
+
const abs = isAbsolute(path) ? path : join(root, path);
|
|
130
|
+
if (!existsSync(abs)) return { ok: false, reason: `evidence log ${path} does not exist` };
|
|
131
|
+
const rel = relative(real(root), real(abs));
|
|
132
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
133
|
+
return { ok: false, reason: `evidence log ${path} is outside the checkout` };
|
|
134
|
+
}
|
|
135
|
+
let body;
|
|
136
|
+
try {
|
|
137
|
+
if (statSync(abs).size === 0) return { ok: false, reason: `evidence log ${path} is empty` };
|
|
138
|
+
body = readFileSync(abs, "utf8");
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return { ok: false, reason: `evidence log ${path} is unreadable: ${err.message}` };
|
|
141
|
+
}
|
|
142
|
+
if (!body.trim()) return { ok: false, reason: `evidence log ${path} is empty` };
|
|
143
|
+
const summary = summarize(body, { source: path });
|
|
144
|
+
if (summary.status === "failed") {
|
|
145
|
+
return { ok: true, reason: `${path}: ${summary.failed} failed of ${summary.executed}` };
|
|
146
|
+
}
|
|
147
|
+
if (summary.status === "passed") {
|
|
148
|
+
return { ok: false, reason: `evidence log ${path} shows a pass (${summary.executed} passed)` };
|
|
149
|
+
}
|
|
150
|
+
if (summary.status === "no-tests") {
|
|
151
|
+
return { ok: false, reason: `evidence log ${path} shows no test executed` };
|
|
152
|
+
}
|
|
153
|
+
if (failureMarkers(root).some((re) => re.test(body))) {
|
|
154
|
+
return { ok: true, reason: `${path}: failure marker, counts unparsed` };
|
|
155
|
+
}
|
|
156
|
+
return { ok: false, reason: `evidence log ${path} shows no failing test` };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function blockingOf(output) {
|
|
160
|
+
const list = Array.isArray(output?.findings) ? output.findings : [];
|
|
161
|
+
return list.filter((f) => f && typeof f === "object" && f.severity === "blocking");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Did a rebuttal round replace any reviewer output?
|
|
166
|
+
* @param {object[]} reviewers
|
|
167
|
+
*/
|
|
168
|
+
export function rebuttalRecorded(reviewers) {
|
|
169
|
+
return (Array.isArray(reviewers) ? reviewers : []).some(
|
|
170
|
+
(r) => Number.isInteger(r?.roundCount) && r.roundCount > 1,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Apply the decision rule. Pure: the input is not mutated.
|
|
176
|
+
*
|
|
177
|
+
* @param {{triage: object, reviewers: object[], integrity?: object[], root: string}} input
|
|
178
|
+
* @returns {{triage: object, kept: object[], downgraded: object[]}}
|
|
179
|
+
*/
|
|
180
|
+
export function decide({ triage, reviewers, integrity = [], root }) {
|
|
181
|
+
const next = structuredClone(triage);
|
|
182
|
+
const sources = (Array.isArray(reviewers) ? reviewers : []).map(blockingOf);
|
|
183
|
+
const gateFindings = (Array.isArray(integrity) ? integrity : []).filter(
|
|
184
|
+
(f) => f && f.tag === "test_integrity",
|
|
185
|
+
);
|
|
186
|
+
const kept = [];
|
|
187
|
+
const downgraded = [];
|
|
188
|
+
for (const f of Array.isArray(next.accepted) ? next.accepted : []) {
|
|
189
|
+
if (!f || f.severity !== "blocking") continue;
|
|
190
|
+
const id = {
|
|
191
|
+
fingerprint: fingerprintOf(f),
|
|
192
|
+
file: f.file,
|
|
193
|
+
line: f.line,
|
|
194
|
+
issue: f.issue,
|
|
195
|
+
};
|
|
196
|
+
const count = sources.filter((list) => list.some((raw) => sameFinding(f, raw))).length;
|
|
197
|
+
if (count >= 2) {
|
|
198
|
+
kept.push({ ...id, basis: "corroborated", sources: count });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (gateFindings.some((g) => normalizeFile(g.file) === normalizeFile(f.file))) {
|
|
202
|
+
kept.push({ ...id, basis: "test-integrity", sources: count });
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const v = f.verification;
|
|
206
|
+
let evidence = null;
|
|
207
|
+
if (v && v.result === "confirmed") {
|
|
208
|
+
evidence = evidenceFromLog(v.evidencePath, root);
|
|
209
|
+
if (evidence.ok) {
|
|
210
|
+
kept.push({ ...id, basis: "failing-test", sources: count, evidence: evidence.reason });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const why = [
|
|
215
|
+
`raised by ${count} reviewer${count === 1 ? "" : "s"}, two needed`,
|
|
216
|
+
evidence
|
|
217
|
+
? `verify-by-test evidence rejected: ${evidence.reason}`
|
|
218
|
+
: "no failing test recorded (verify-by-test)",
|
|
219
|
+
].join("; ");
|
|
220
|
+
f.severity = "important";
|
|
221
|
+
downgraded.push({ ...id, sources: count, reason: `lowered to important: ${why}` });
|
|
222
|
+
}
|
|
223
|
+
next.approved = !(next.accepted || []).some((f) => f?.severity === "blocking");
|
|
224
|
+
return { triage: next, kept, downgraded };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// CLI
|
|
229
|
+
|
|
230
|
+
function readJson(p, what) {
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
233
|
+
} catch (err) {
|
|
234
|
+
throw new Error(`cannot read ${what} ${p}: ${err.message}`, { cause: err });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Step 1.76's findings. No flag, no file or an empty file is the gate's own
|
|
240
|
+
* "no findings" output; a file with content that is not JSON is unreadable.
|
|
241
|
+
*/
|
|
242
|
+
function readIntegrity(p) {
|
|
243
|
+
if (!p || !existsSync(p)) return [];
|
|
244
|
+
let body;
|
|
245
|
+
try {
|
|
246
|
+
body = readFileSync(p, "utf8");
|
|
247
|
+
} catch (err) {
|
|
248
|
+
throw new Error(`cannot read test-integrity output ${p}: ${err.message}`, { cause: err });
|
|
249
|
+
}
|
|
250
|
+
if (!body.trim()) return [];
|
|
251
|
+
try {
|
|
252
|
+
const doc = JSON.parse(body);
|
|
253
|
+
return Array.isArray(doc?.findings) ? doc.findings : [];
|
|
254
|
+
} catch (err) {
|
|
255
|
+
throw new Error(`cannot read test-integrity output ${p}: ${err.message}`, { cause: err });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parse(argv) {
|
|
260
|
+
const opts = { source: [], json: false, dryRun: false, rebuttalAllowed: false, triage: null };
|
|
261
|
+
const single = new Set(["--state", "--iteration", "--integrity", "--repo"]);
|
|
262
|
+
for (let i = 0; i < argv.length; i++) {
|
|
263
|
+
const a = argv[i];
|
|
264
|
+
if (a === "--json") opts.json = true;
|
|
265
|
+
else if (a === "--dry-run") opts.dryRun = true;
|
|
266
|
+
else if (a === "--rebuttal-allowed") opts.rebuttalAllowed = true;
|
|
267
|
+
else if (a === "--source" || single.has(a)) {
|
|
268
|
+
const v = argv[++i];
|
|
269
|
+
if (v === undefined) throw new Error(`${a} needs a value`);
|
|
270
|
+
if (a === "--source") opts.source.push(v);
|
|
271
|
+
else opts[a.slice(2)] = v;
|
|
272
|
+
} else if (a.startsWith("--")) throw new Error(`unexpected argument ${a}`);
|
|
273
|
+
else if (opts.triage) throw new Error(`one triage file only, got ${a} too`);
|
|
274
|
+
else opts.triage = a;
|
|
275
|
+
}
|
|
276
|
+
if (!opts.rebuttalAllowed && !opts.triage) throw new Error("a triage file is required");
|
|
277
|
+
if (opts.iteration !== undefined && !/^[1-9][0-9]*$/.test(opts.iteration)) {
|
|
278
|
+
throw new Error("--iteration must be a positive integer");
|
|
279
|
+
}
|
|
280
|
+
return opts;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function reviewerRecord(opts, state) {
|
|
284
|
+
const iterations = Array.isArray(state?.reviewIterations) ? state.reviewIterations : [];
|
|
285
|
+
const at = opts.iteration ? Number(opts.iteration) - 1 : iterations.length - 1;
|
|
286
|
+
const fromState = Array.isArray(iterations[at]?.reviewers) ? iterations[at].reviewers : [];
|
|
287
|
+
const extra = opts.source
|
|
288
|
+
.filter((p) => existsSync(p) && readFileSync(p, "utf8").trim())
|
|
289
|
+
.map((p) => readJson(p, "reviewer output"));
|
|
290
|
+
const all = [...fromState, ...extra];
|
|
291
|
+
return all.length ? all : null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function emit(json, result, stream) {
|
|
295
|
+
if (json) {
|
|
296
|
+
writeFileSync(1, `${JSON.stringify(result, null, 2)}\n`);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const lines = [
|
|
300
|
+
`${GATE}${result.advisory ? " (advisory, the quality gates are off)" : ""}: ${result.summary}`,
|
|
301
|
+
];
|
|
302
|
+
for (const d of result.downgraded || []) lines.push(` ${d.file}:${d.line} ${d.reason}`);
|
|
303
|
+
writeFileSync(stream, `${lines.join("\n")}\n`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function main(argv) {
|
|
307
|
+
let opts;
|
|
308
|
+
try {
|
|
309
|
+
opts = parse(argv);
|
|
310
|
+
} catch (err) {
|
|
311
|
+
process.stderr.write(`review-decision-gate: ${err.message}\n`);
|
|
312
|
+
return 2;
|
|
313
|
+
}
|
|
314
|
+
const ctx = gateContext({ statePath: opts.state || null, repo: opts.repo || process.cwd() });
|
|
315
|
+
const advisory = !ctx.active;
|
|
316
|
+
|
|
317
|
+
if (opts.rebuttalAllowed) {
|
|
318
|
+
if (advisory) {
|
|
319
|
+
if (opts.json) writeFileSync(1, `${JSON.stringify({ rebuttalAllowed: true })}\n`);
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
const why =
|
|
323
|
+
"the quality gates are active: reviewers stay blind to each other, Step 2.5 does not run";
|
|
324
|
+
if (opts.json) writeFileSync(1, `${JSON.stringify({ rebuttalAllowed: false, reason: why })}\n`);
|
|
325
|
+
else writeFileSync(1, `rebuttal round: off (${why})\n`);
|
|
326
|
+
return 1;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const state = ctx.state;
|
|
330
|
+
const worktree =
|
|
331
|
+
typeof state?.worktreePath === "string" && existsSync(state.worktreePath)
|
|
332
|
+
? state.worktreePath
|
|
333
|
+
: null;
|
|
334
|
+
const root = opts.repo || worktree || process.cwd();
|
|
335
|
+
|
|
336
|
+
let result;
|
|
337
|
+
let decided = null;
|
|
338
|
+
try {
|
|
339
|
+
const triage = readJson(opts.triage, "the triage output");
|
|
340
|
+
if (!triage || typeof triage !== "object" || !Array.isArray(triage.accepted)) {
|
|
341
|
+
throw new Error(`${opts.triage} is not a triage output (no accepted[])`);
|
|
342
|
+
}
|
|
343
|
+
const reviewers = reviewerRecord(opts, state);
|
|
344
|
+
const integrity = readIntegrity(opts.integrity);
|
|
345
|
+
if (rebuttalRecorded(reviewers)) {
|
|
346
|
+
result = {
|
|
347
|
+
gate: GATE,
|
|
348
|
+
verdict: "rebuttal-round",
|
|
349
|
+
summary:
|
|
350
|
+
"a Step 2.5 rebuttal round replaced reviewer output; with the quality gates active reviewers stay blind to each other",
|
|
351
|
+
};
|
|
352
|
+
} else {
|
|
353
|
+
const blockers = triage.accepted.filter((f) => f?.severity === "blocking");
|
|
354
|
+
if (blockers.length && !reviewers) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
"no reviewer record for this iteration (state.reviewIterations[].reviewers or --source), so no blocker can be checked for corroboration",
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
decided = decide({ triage, reviewers: reviewers || [], integrity, root });
|
|
360
|
+
result = {
|
|
361
|
+
gate: GATE,
|
|
362
|
+
verdict: "pass",
|
|
363
|
+
summary: `${blockers.length} blocking finding(s): ${decided.kept.length} kept, ${decided.downgraded.length} lowered to important`,
|
|
364
|
+
approved: decided.triage.approved,
|
|
365
|
+
kept: decided.kept,
|
|
366
|
+
downgraded: decided.downgraded,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
result = { gate: GATE, verdict: "unreadable", summary: err.message };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (advisory) {
|
|
374
|
+
emit(opts.json, { ...result, advisory: true }, 1);
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (decided && !opts.dryRun)
|
|
379
|
+
writeFileSync(opts.triage, `${JSON.stringify(decided.triage, null, 2)}\n`);
|
|
380
|
+
const detail =
|
|
381
|
+
result.verdict === "pass"
|
|
382
|
+
? {
|
|
383
|
+
kept: result.kept.length,
|
|
384
|
+
downgraded: result.downgraded.map(({ fingerprint, file, line, reason }) => ({
|
|
385
|
+
fingerprint,
|
|
386
|
+
file,
|
|
387
|
+
line,
|
|
388
|
+
reason,
|
|
389
|
+
})),
|
|
390
|
+
}
|
|
391
|
+
: { verdict: result.verdict, reason: result.summary };
|
|
392
|
+
await recordQuietly(GATE, result.verdict === "pass" ? "pass" : "fail", detail, {
|
|
393
|
+
repo: existsSync(root) ? root : null,
|
|
394
|
+
statePath: ctx.statePath,
|
|
395
|
+
});
|
|
396
|
+
emit(opts.json, result, result.verdict === "pass" ? 1 : 2);
|
|
397
|
+
if (result.verdict === "pass") return 0;
|
|
398
|
+
return result.verdict === "rebuttal-round" ? 1 : 3;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (invokedDirectly(import.meta.url)) {
|
|
402
|
+
main(process.argv.slice(2)).then((code) => process.exit(code));
|
|
403
|
+
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, renameSync } from "node:fs";
|
|
29
29
|
import { join, dirname } from "node:path";
|
|
30
30
|
import { runMain } from "../lib/fatal.mjs";
|
|
31
|
+
import { hostCommand } from "../lib/user-facing.mjs";
|
|
31
32
|
|
|
32
33
|
const NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
33
34
|
const MAX_NAME = 40;
|
|
@@ -59,7 +60,7 @@ function fail(msg) {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
function homeDir(args) {
|
|
62
|
-
return args.home || process.env.HOME ||
|
|
63
|
+
return args.home || process.env.HOME || "";
|
|
63
64
|
}
|
|
64
65
|
function prefsPath(home) {
|
|
65
66
|
return join(home, ".claude", "multi-agent-preferences.json");
|
|
@@ -174,7 +175,9 @@ function cmdAdd(home, args) {
|
|
|
174
175
|
prefs.global.routines = routines;
|
|
175
176
|
writePrefs(home, prefs);
|
|
176
177
|
console.log(`routine-registry: saved "${name}" -> ${join(dir, "SKILL.md")}`);
|
|
177
|
-
console.log(
|
|
178
|
+
console.log(
|
|
179
|
+
`routine-registry: available as ${hostCommand(name, import.meta.dirname)} after a session reload`,
|
|
180
|
+
);
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
function cmdRemove(home, name) {
|
|
@@ -22,9 +22,16 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Read-only. Never writes, never migrates, never deletes.
|
|
24
24
|
*
|
|
25
|
+
* The JSON is a contract (pipeline/schemas/runs-index.schema.json). Fields are
|
|
26
|
+
* only ever added: `subs` stays the count a reader already decodes, and the
|
|
27
|
+
* sub-steps themselves are `subList`. `--redact` drops every field the schema
|
|
28
|
+
* marks `x-sensitivity: local` - paths and the PR URL - and scrubs the free
|
|
29
|
+
* text a phase wrote into `now`, sub-step names and a pending question.
|
|
30
|
+
*
|
|
25
31
|
* Usage:
|
|
26
32
|
* node runs-index.mjs # human table, grouped
|
|
27
33
|
* node runs-index.mjs --json # the same records as JSON
|
|
34
|
+
* node runs-index.mjs --json --redact # without the fields specific to this machine
|
|
28
35
|
* node runs-index.mjs --group waiting # one group only
|
|
29
36
|
* node runs-index.mjs --task-id <id> # one run
|
|
30
37
|
*
|
|
@@ -36,8 +43,10 @@
|
|
|
36
43
|
*/
|
|
37
44
|
|
|
38
45
|
import { existsSync, readFileSync } from "node:fs";
|
|
46
|
+
import { readRegularFile } from "../lib/regular-file.mjs";
|
|
39
47
|
import { join } from "node:path";
|
|
40
48
|
import { costUsd } from "./_cost.mjs";
|
|
49
|
+
import { omit, scrub } from "./_redact.mjs";
|
|
41
50
|
import { listRuns, logsRoot, resolveRunDir, taskIdVariants } from "./_run-paths.mjs";
|
|
42
51
|
import { runMain } from "../lib/fatal.mjs";
|
|
43
52
|
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
@@ -48,9 +57,24 @@ import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
|
48
57
|
* under an eight-phase contract, 4 under six), so a literal would survive a
|
|
49
58
|
* renumbering unchanged and silently regroup every run.
|
|
50
59
|
*/
|
|
51
|
-
const
|
|
60
|
+
const PHASE_CONTRACT = JSON.parse(
|
|
52
61
|
readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"),
|
|
53
|
-
)
|
|
62
|
+
);
|
|
63
|
+
const PHASE_WAITING_FROM = PHASE_CONTRACT.thresholds.waitingFromPhase;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The last phase id, so the Phase column reads "current/last" in the same
|
|
67
|
+
* numbering the run records use. Phases are numbered from 0, so this is one
|
|
68
|
+
* less than the phase count.
|
|
69
|
+
*/
|
|
70
|
+
const LAST_PHASE = PHASE_CONTRACT.phases[PHASE_CONTRACT.phases.length - 1].id;
|
|
71
|
+
|
|
72
|
+
export const CONTRACT_VERSION = "1.1.0";
|
|
73
|
+
|
|
74
|
+
const STATE_MAX_BYTES = 16 * 1024 * 1024;
|
|
75
|
+
|
|
76
|
+
/** The run fields runs-index.schema.json marks x-sensitivity local. */
|
|
77
|
+
export const LOCAL_RUN_FIELDS = ["dir", "worktreePath", "prUrl", "duplicateOf"];
|
|
54
78
|
|
|
55
79
|
const GROUPS = {
|
|
56
80
|
waiting: "Waiting on you",
|
|
@@ -60,10 +84,11 @@ const GROUPS = {
|
|
|
60
84
|
};
|
|
61
85
|
|
|
62
86
|
function parseArgs(argv) {
|
|
63
|
-
const flags = { json: false, group: null, taskId: null };
|
|
87
|
+
const flags = { json: false, redact: false, group: null, taskId: null };
|
|
64
88
|
for (let i = 0; i < argv.length; i++) {
|
|
65
89
|
const a = argv[i];
|
|
66
90
|
if (a === "--json") flags.json = true;
|
|
91
|
+
else if (a === "--redact") flags.redact = true;
|
|
67
92
|
else if (a === "--group") flags.group = argv[++i];
|
|
68
93
|
else if (a.startsWith("--group=")) flags.group = a.slice(8);
|
|
69
94
|
else if (a === "--task-id") flags.taskId = argv[++i];
|
|
@@ -84,9 +109,9 @@ function parseArgs(argv) {
|
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
function readJson(path) {
|
|
87
|
-
if (!path
|
|
112
|
+
if (!path) return null;
|
|
88
113
|
try {
|
|
89
|
-
return JSON.parse(
|
|
114
|
+
return JSON.parse(readRegularFile(path, { maxBytes: STATE_MAX_BYTES }));
|
|
90
115
|
} catch {
|
|
91
116
|
// A truncated state file is a fact about the run, not a reason to refuse
|
|
92
117
|
// the whole index. It surfaces as `stateReadable: false`.
|
|
@@ -134,10 +159,12 @@ function summarisePhases(tracker) {
|
|
|
134
159
|
let usd = 0;
|
|
135
160
|
const rows = phases.map((p) => {
|
|
136
161
|
// phase-tracker.sh writes flat `tokens_in` / `tokens_out` / `tokens_cached`
|
|
137
|
-
// on each phase, not a nested `tokens` object.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
162
|
+
// on each phase, not a nested `tokens` object. Token counts are whole
|
|
163
|
+
// numbers in the contract, so a fractional value a writer produced is
|
|
164
|
+
// rounded here rather than handed to a decoder that reads an integer.
|
|
165
|
+
const tin = Math.round(Number(p.tokens_in ?? 0) || 0);
|
|
166
|
+
const tout = Math.round(Number(p.tokens_out ?? 0) || 0);
|
|
167
|
+
const tcached = Math.round(Number(p.tokens_cached ?? 0) || 0);
|
|
141
168
|
tokensIn += tin;
|
|
142
169
|
tokensOut += tout;
|
|
143
170
|
tokensCached += tcached;
|
|
@@ -152,6 +179,13 @@ function summarisePhases(tracker) {
|
|
|
152
179
|
completedAt: p.completed_at ?? null,
|
|
153
180
|
now: p.now ?? null,
|
|
154
181
|
subs: Array.isArray(p.subs) ? p.subs.length : 0,
|
|
182
|
+
subList: Array.isArray(p.subs)
|
|
183
|
+
? p.subs.map((sub) => ({
|
|
184
|
+
id: String(sub?.id ?? ""),
|
|
185
|
+
name: sub?.name ?? "",
|
|
186
|
+
status: sub?.status ?? "pending",
|
|
187
|
+
}))
|
|
188
|
+
: [],
|
|
155
189
|
};
|
|
156
190
|
});
|
|
157
191
|
return {
|
|
@@ -175,12 +209,37 @@ function groupOf(state) {
|
|
|
175
209
|
const prUrl = state?.pr?.url ?? state?.prUrl ?? null;
|
|
176
210
|
if (status === "awaiting_input" || status === "awaiting-user-test-main-checkout")
|
|
177
211
|
return "waiting";
|
|
212
|
+
// A paused run names the step it re-enters. It is waiting on a person at any
|
|
213
|
+
// phase, and at phase 0 it must not read as a run left at intake, which the
|
|
214
|
+
// question group offers to garbage-collect.
|
|
215
|
+
if (state?.waitingFor) return "waiting";
|
|
178
216
|
if (prUrl) return "waiting";
|
|
179
217
|
if (Number.isFinite(phase) && phase >= PHASE_WAITING_FROM) return "waiting";
|
|
180
218
|
if (Number.isFinite(phase) && phase === 0) return "question";
|
|
181
219
|
return "stopped";
|
|
182
220
|
}
|
|
183
221
|
|
|
222
|
+
/**
|
|
223
|
+
* The question a run stopped on, in the shape answer-question.mjs accepts an
|
|
224
|
+
* answer to, or null. Only a run with `waitingFor: "question"` has one: a stale
|
|
225
|
+
* record left beside another pause is not a question anyone can answer.
|
|
226
|
+
*
|
|
227
|
+
* @param {object|null} state
|
|
228
|
+
*/
|
|
229
|
+
function questionOf(state) {
|
|
230
|
+
const q = state?.pendingQuestion;
|
|
231
|
+
if (state?.waitingFor !== "question" || !q || typeof q !== "object") return null;
|
|
232
|
+
return {
|
|
233
|
+
id: String(q.id ?? ""),
|
|
234
|
+
stepId: String(q.stepId ?? ""),
|
|
235
|
+
text: String(q.text ?? ""),
|
|
236
|
+
kind: q.kind === "multi-select" ? "multi-select" : "single-select",
|
|
237
|
+
options: Array.isArray(q.options)
|
|
238
|
+
? q.options.map((o) => ({ id: String(o?.id ?? ""), label: String(o?.label ?? "") }))
|
|
239
|
+
: [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
184
243
|
/**
|
|
185
244
|
* Every run, enriched, in one stable order.
|
|
186
245
|
*
|
|
@@ -229,8 +288,13 @@ export function buildIndex() {
|
|
|
229
288
|
worktreePath: state?.worktreePath ?? null,
|
|
230
289
|
prUrl,
|
|
231
290
|
autopilot: Boolean(state?.autopilot),
|
|
232
|
-
|
|
291
|
+
// agent-state.schema.json declares a string; a number a hand-edited
|
|
292
|
+
// state carries is printed as its string so the field keeps one type.
|
|
293
|
+
schemaVersion: state?.schemaVersion == null ? null : String(state.schemaVersion),
|
|
233
294
|
rev: Number.isInteger(state?.rev) ? state.rev : null,
|
|
295
|
+
sessionId: typeof state?.sessionId === "string" ? state.sessionId : null,
|
|
296
|
+
waitingFor: typeof state?.waitingFor === "string" ? state.waitingFor : null,
|
|
297
|
+
pendingQuestion: questionOf(state),
|
|
234
298
|
group: groupOf(state),
|
|
235
299
|
phases: phases.phases,
|
|
236
300
|
tokens: phases.tokens,
|
|
@@ -278,7 +342,7 @@ function renderHuman(records) {
|
|
|
278
342
|
" ID Phase Status Branch Age ~USD",
|
|
279
343
|
);
|
|
280
344
|
for (const r of rows) {
|
|
281
|
-
const phase = r.currentPhase === null ? " -" : `${r.currentPhase}
|
|
345
|
+
const phase = r.currentPhase === null ? " -" : `${r.currentPhase}/${LAST_PHASE}`;
|
|
282
346
|
out.push(
|
|
283
347
|
" " +
|
|
284
348
|
[
|
|
@@ -306,34 +370,78 @@ function renderHuman(records) {
|
|
|
306
370
|
return out.join("\n");
|
|
307
371
|
}
|
|
308
372
|
|
|
373
|
+
/**
|
|
374
|
+
* The --json envelope. Redaction is applied here, after grouping and
|
|
375
|
+
* filtering, so a redacted index and a full one list the same runs.
|
|
376
|
+
*/
|
|
377
|
+
export function envelope(records, { redact = false } = {}) {
|
|
378
|
+
const runs = redact
|
|
379
|
+
? records.map((r) => ({
|
|
380
|
+
...omit(r, LOCAL_RUN_FIELDS),
|
|
381
|
+
pendingQuestion: r.pendingQuestion && {
|
|
382
|
+
...r.pendingQuestion,
|
|
383
|
+
text: scrub(r.pendingQuestion.text),
|
|
384
|
+
options: r.pendingQuestion.options.map((o) => ({ ...o, label: scrub(o.label) })),
|
|
385
|
+
},
|
|
386
|
+
phases: r.phases.map((p) => ({
|
|
387
|
+
...p,
|
|
388
|
+
name: scrub(p.name),
|
|
389
|
+
now: p.now === null ? null : scrub(p.now),
|
|
390
|
+
subList: p.subList.map((sub) => ({ ...sub, name: scrub(sub.name) })),
|
|
391
|
+
})),
|
|
392
|
+
}))
|
|
393
|
+
: records;
|
|
394
|
+
const out = { contractVersion: CONTRACT_VERSION };
|
|
395
|
+
if (!redact) out.logsRoot = logsRoot();
|
|
396
|
+
out.count = runs.length;
|
|
397
|
+
out.runs = runs;
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** The group names --group accepts. */
|
|
402
|
+
export const GROUP_NAMES = Object.keys(GROUPS);
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* The records one invocation selects: every run, one task id, or one group.
|
|
406
|
+
* `found` is false only when a task id was asked for and no run carries it.
|
|
407
|
+
* The CLI and contract-server.mjs both call this, so a filter means the same
|
|
408
|
+
* thing on either carrier.
|
|
409
|
+
*
|
|
410
|
+
* @param {{taskId?: string|null, group?: string|null}} [opts]
|
|
411
|
+
* @returns {{found: boolean, records: object[]}}
|
|
412
|
+
*/
|
|
413
|
+
export function selectRuns({ taskId = null, group = null } = {}) {
|
|
414
|
+
let records = buildIndex();
|
|
415
|
+
if (taskId) {
|
|
416
|
+
const wanted = new Set(taskIdVariants(taskId));
|
|
417
|
+
records = records.filter((r) => wanted.has(r.taskId));
|
|
418
|
+
if (!records.length) return { found: false, records };
|
|
419
|
+
}
|
|
420
|
+
if (group) records = records.filter((r) => r.group === group);
|
|
421
|
+
return { found: true, records };
|
|
422
|
+
}
|
|
423
|
+
|
|
309
424
|
function main() {
|
|
310
425
|
const flags = parseArgs(process.argv.slice(2));
|
|
311
426
|
if (flags.help) {
|
|
312
427
|
process.stdout.write(
|
|
313
|
-
"Usage: runs-index.mjs [--json] [--group waiting|stopped|question|unknown] [--task-id <id>]\n",
|
|
428
|
+
"Usage: runs-index.mjs [--json [--redact]] [--group waiting|stopped|question|unknown] [--task-id <id>]\n",
|
|
314
429
|
);
|
|
315
430
|
return 0;
|
|
316
431
|
}
|
|
317
432
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
process.stderr.write(
|
|
326
|
-
`runs-index: no run for ${flags.taskId}${dir ? ` (directory ${dir} has no markers)` : ""}\n`,
|
|
327
|
-
);
|
|
328
|
-
return 0;
|
|
329
|
-
}
|
|
433
|
+
const { found, records } = selectRuns(flags);
|
|
434
|
+
if (!found) {
|
|
435
|
+
const dir = resolveRunDir(flags.taskId);
|
|
436
|
+
process.stderr.write(
|
|
437
|
+
`runs-index: no run for ${flags.taskId}${dir ? ` (directory ${dir} has no markers)` : ""}\n`,
|
|
438
|
+
);
|
|
439
|
+
return 0;
|
|
330
440
|
}
|
|
331
|
-
if (flags.group) records = records.filter((r) => r.group === flags.group);
|
|
332
441
|
|
|
333
442
|
if (flags.json) {
|
|
334
443
|
process.stdout.write(
|
|
335
|
-
JSON.stringify(
|
|
336
|
-
"\n",
|
|
444
|
+
JSON.stringify(envelope(records, { redact: flags.redact }), null, 2) + "\n",
|
|
337
445
|
);
|
|
338
446
|
} else {
|
|
339
447
|
process.stdout.write(renderHuman(records) + "\n");
|