@mmerterden/multi-agent-pipeline 17.6.0 → 19.0.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 +310 -0
- package/README.md +76 -18
- package/README.tr.md +55 -16
- package/docs/adr/0002-instruction-driven-flag.md +1 -0
- package/docs/adr/0005-lazy-phase-docs.md +11 -1
- package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +1 -0
- package/docs/adr/0010-own-code-graph.md +1 -0
- package/docs/adr/0011-dormant-ci.md +25 -1
- package/docs/adr/0014-six-phase-consolidation.md +134 -0
- package/docs/adr/README.md +2 -1
- package/docs/architecture.md +37 -38
- package/docs/best-practices.md +1 -1
- package/docs/ecosystem.md +37 -26
- package/docs/engineering.md +1 -1
- package/docs/facts.json +45 -0
- package/docs/features.md +54 -53
- package/docs/performance.md +5 -5
- package/docs/recovery-guide.md +9 -9
- package/docs/server-readiness.md +188 -0
- package/docs/token-budget-history.md +3 -1
- package/index.js +18 -3
- package/install/_codex-agents.mjs +1 -1
- package/install/_common.mjs +42 -17
- package/install/_dev-only-files.mjs +8 -0
- package/install/_unattended-profile.mjs +113 -0
- package/install/index.mjs +48 -0
- package/install/templates/claude-hooks.json +1 -1
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +28 -28
- package/manifest.json +1065 -0
- package/package.json +6 -3
- package/pipeline/agents/dev-critic.md +3 -3
- package/pipeline/commands/figma-to-swiftui.md +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +8 -8
- package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
- package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
- package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
- package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
- package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
- package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/status/SKILL.md +54 -23
- package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
- package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
- package/pipeline/lib/_jira-auth.sh +8 -0
- package/pipeline/lib/analysis-jira-write.sh +32 -0
- package/pipeline/lib/ask-choice.sh +13 -2
- package/pipeline/lib/autopilot-state.sh +8 -0
- package/pipeline/lib/credential-inventory.sh +1 -1
- package/pipeline/lib/fatal.mjs +129 -0
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/figma-mcp-refresh.sh +18 -0
- package/pipeline/lib/figma-screenshot.sh +18 -0
- package/pipeline/lib/invoked-directly.mjs +43 -0
- package/pipeline/lib/jira-publish.sh +42 -0
- package/pipeline/lib/md2confluence-v3.py +47 -0
- package/pipeline/lib/model-rung.sh +142 -0
- package/pipeline/lib/outbound-gate.mjs +175 -0
- package/pipeline/lib/phase-schema.mjs +88 -0
- package/pipeline/lib/plan-todos.sh +32 -11
- package/pipeline/lib/post-pr-review.sh +77 -8
- package/pipeline/lib/repo-hygiene.sh +8 -3
- package/pipeline/lib/require-jq.sh +40 -0
- package/pipeline/lib/route-state.sh +161 -0
- package/pipeline/lib/run-paths.sh +335 -0
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
- package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
- package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
- package/pipeline/multi-agent-refs/analysis/render.md +1 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
- package/pipeline/multi-agent-refs/android-guide.md +1 -1
- package/pipeline/multi-agent-refs/audit-guide.md +13 -13
- package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +3 -3
- package/pipeline/multi-agent-refs/channels/pr.md +4 -4
- package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
- package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +74 -4
- package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
- package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
- package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
- package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
- package/pipeline/multi-agent-refs/features/doctor.md +47 -2
- package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
- package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
- package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
- package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
- package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
- package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
- package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
- package/pipeline/multi-agent-refs/features/verify.md +83 -0
- package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
- package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
- package/pipeline/multi-agent-refs/knowledge.md +11 -11
- package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
- package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
- package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
- package/pipeline/multi-agent-refs/phases/modes.md +30 -30
- package/pipeline/multi-agent-refs/phases/operations.md +21 -10
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +25 -25
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
- package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
- package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
- package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
- package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
- package/pipeline/multi-agent-refs/phases.md +44 -48
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/progress-contract.md +6 -6
- package/pipeline/multi-agent-refs/readiness-review.md +1 -1
- package/pipeline/multi-agent-refs/rules.md +7 -7
- package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
- package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
- package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
- package/pipeline/preferences-template.json +9 -1
- package/pipeline/rules/outside-the-pipeline.md +1 -1
- package/pipeline/schemas/agent-state.schema.json +50 -50
- package/pipeline/schemas/analysis-output.schema.json +2 -2
- package/pipeline/schemas/autopilot-config.schema.json +1 -1
- package/pipeline/schemas/code-graph.schema.json +1 -1
- package/pipeline/schemas/criteria-manifest.schema.json +1 -1
- package/pipeline/schemas/dev-critic-output.schema.json +1 -1
- package/pipeline/schemas/diff-risk.schema.json +1 -1
- package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
- package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
- package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
- package/pipeline/schemas/phases.json +105 -0
- package/pipeline/schemas/plan-todos.schema.json +5 -5
- package/pipeline/schemas/planning-output.schema.json +1 -1
- package/pipeline/schemas/prefs.schema.json +100 -56
- package/pipeline/schemas/reviewer-output.schema.json +3 -3
- package/pipeline/schemas/route-config.schema.json +74 -0
- package/pipeline/schemas/scope-check.schema.json +1 -1
- package/pipeline/schemas/test-gap.schema.json +1 -1
- package/pipeline/schemas/token-budget.json +12 -18
- package/pipeline/schemas/triage-output.schema.json +6 -6
- package/pipeline/scripts/README.md +3 -3
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_run-paths.mjs +372 -0
- package/pipeline/scripts/_smoke-root.sh +1 -1
- package/pipeline/scripts/aggregate-metrics.mjs +65 -65
- package/pipeline/scripts/autopilot-arming.mjs +2 -1
- package/pipeline/scripts/autopilot-intake.mjs +2 -1
- package/pipeline/scripts/autopilot-runner.mjs +206 -2
- package/pipeline/scripts/build-references.mjs +2 -1
- package/pipeline/scripts/build-stack-plugins.mjs +10 -2
- package/pipeline/scripts/capture-evidence.sh +7 -2
- package/pipeline/scripts/capture-flush.sh +8 -8
- package/pipeline/scripts/capture-resume.sh +3 -3
- package/pipeline/scripts/classify-plan-safety.mjs +3 -2
- package/pipeline/scripts/cost-analyze.mjs +600 -0
- package/pipeline/scripts/cost-budget-check.mjs +4 -12
- package/pipeline/scripts/council-view.mjs +2 -1
- package/pipeline/scripts/crush-json.mjs +2 -1
- package/pipeline/scripts/diff-explain.mjs +7 -10
- package/pipeline/scripts/diff-risk-score.mjs +2 -1
- package/pipeline/scripts/doctor.mjs +140 -6
- package/pipeline/scripts/evidence-gate.mjs +9 -3
- package/pipeline/scripts/feedback-send.mjs +12 -2
- package/pipeline/scripts/gc-abandoned.sh +32 -16
- package/pipeline/scripts/gc-tmp.sh +1 -1
- package/pipeline/scripts/gc-worktrees.sh +12 -5
- package/pipeline/scripts/gen-facts.mjs +175 -0
- package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
- package/pipeline/scripts/gen-ref-toc.mjs +1 -1
- package/pipeline/scripts/github-ssh-setup.sh +64 -7
- package/pipeline/scripts/graph-mermaid.mjs +4 -2
- package/pipeline/scripts/graph-report.mjs +1 -1
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain-save.sh +101 -30
- package/pipeline/scripts/learn-from-transcripts.mjs +3 -2
- package/pipeline/scripts/learning-curve.mjs +36 -31
- package/pipeline/scripts/log-metric.sh +17 -4
- package/pipeline/scripts/make-manifest.mjs +199 -0
- package/pipeline/scripts/memory-save.sh +1 -1
- package/pipeline/scripts/migrate-prefs.mjs +24 -6
- package/pipeline/scripts/migrate-state.mjs +94 -4
- package/pipeline/scripts/phase-banner.sh +26 -22
- package/pipeline/scripts/phase-tracker.sh +48 -10
- package/pipeline/scripts/plan-coverage-gate.mjs +8 -4
- package/pipeline/scripts/pre-commit-check.sh +7 -0
- package/pipeline/scripts/pre-push-check.sh +7 -0
- package/pipeline/scripts/purge.sh +23 -6
- package/pipeline/scripts/render-agent-log-cost.sh +10 -3
- package/pipeline/scripts/render-cost-summary.sh +9 -2
- package/pipeline/scripts/render-work-summary.sh +14 -7
- package/pipeline/scripts/review-file-filter.mjs +5 -3
- package/pipeline/scripts/review-scope.mjs +2 -1
- package/pipeline/scripts/routine-registry.mjs +2 -1
- package/pipeline/scripts/run-aggregator.mjs +26 -20
- package/pipeline/scripts/run-metrics.mjs +4 -2
- package/pipeline/scripts/runs-index.mjs +353 -0
- package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
- package/pipeline/scripts/search-logs.sh +18 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
- package/pipeline/scripts/smoke-schema-validation.sh +26 -7
- package/pipeline/scripts/test-gap-scan.mjs +2 -1
- package/pipeline/scripts/test-integrity-gate.mjs +2 -1
- package/pipeline/scripts/token-budget-report.mjs +13 -2
- package/pipeline/scripts/triage-memory.mjs +2 -2
- package/pipeline/scripts/update-issue-progress.sh +56 -7
- package/pipeline/scripts/usage-report.mjs +12 -1
- package/pipeline/scripts/validate-analysis-doc.mjs +75 -18
- package/pipeline/scripts/validate-code-graph.mjs +6 -3
- package/pipeline/scripts/validate-complaint-doc.mjs +2 -1
- package/pipeline/scripts/validate-diff-risk.mjs +6 -3
- package/pipeline/scripts/validate-planning.mjs +1 -1
- package/pipeline/scripts/validate-reviewer.mjs +1 -1
- package/pipeline/scripts/validate-state.mjs +45 -5
- package/pipeline/scripts/validate-test-gap.mjs +6 -3
- package/pipeline/scripts/validate-triage.mjs +6 -4
- package/pipeline/scripts/verify-citations.mjs +4 -2
- package/pipeline/scripts/verify.mjs +327 -0
- package/pipeline/scripts/worktree-finalize.sh +18 -9
- package/pipeline/scripts/write-state.mjs +154 -15
- package/pipeline/skills/.skill-manifest.json +37 -21
- package/pipeline/skills/.skills-index.json +104 -5
- package/pipeline/skills/shared/README.md +15 -6
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
- package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
- package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +35 -11
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +4 -1
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +4 -1
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +2 -1
- package/pipeline/skills/skills-index.md +13 -4
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file phase-schema.mjs - read a phase number that may have been written
|
|
3
|
+
* under either phase vocabulary.
|
|
4
|
+
*
|
|
5
|
+
* metrics.jsonl is append-only and v19.0.0 renumbered the phases, so the same
|
|
6
|
+
* field means two different things depending on when the line was written:
|
|
7
|
+
* `phase: 3` is Dev in a pre-v19 row and Review in a post-v19 one. Rows from
|
|
8
|
+
* v19.0.0 on carry `phaseSchema: 2`; rows without the field are generation 1.
|
|
9
|
+
*
|
|
10
|
+
* Every aggregator that compares a phase number to a literal, or prints a
|
|
11
|
+
* phase name, goes through here. Comparing directly is the bug this module
|
|
12
|
+
* exists to prevent - it does not throw, it silently attributes work to the
|
|
13
|
+
* wrong phase and the roll-up still looks plausible.
|
|
14
|
+
*
|
|
15
|
+
* @module pipeline/lib/phase-schema
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
|
|
20
|
+
const CONTRACT = JSON.parse(
|
|
21
|
+
readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const CURRENT_SCHEMA = CONTRACT.phaseSchema;
|
|
25
|
+
|
|
26
|
+
/** Generation 1 number -> current number, derived from each phase's `was`. */
|
|
27
|
+
const GEN1_TO_CURRENT = new Map();
|
|
28
|
+
for (const p of CONTRACT.phases) {
|
|
29
|
+
for (const old of p.was) GEN1_TO_CURRENT.set(Number(old), p.id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const CURRENT_NAMES = new Map(CONTRACT.phases.map((p) => [p.id, p.name]));
|
|
33
|
+
const GEN1_NAMES = new Map(
|
|
34
|
+
Object.entries(CONTRACT.legacyPhaseNames)
|
|
35
|
+
.filter(([k]) => /^\d+$/.test(k))
|
|
36
|
+
.map(([k, v]) => [Number(k), v]),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Which vocabulary a metrics row was written under.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} row - a parsed metrics.jsonl line
|
|
43
|
+
* @returns {number} - 1 or 2
|
|
44
|
+
*/
|
|
45
|
+
export function schemaOf(row) {
|
|
46
|
+
const s = Number(row?.phaseSchema);
|
|
47
|
+
return Number.isInteger(s) && s > 0 ? s : 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A row's phase expressed in the CURRENT vocabulary, so rows from both
|
|
52
|
+
* generations can be counted together.
|
|
53
|
+
*
|
|
54
|
+
* @param {string|number} phase
|
|
55
|
+
* @param {number} schema - from schemaOf()
|
|
56
|
+
* @returns {number|null} - null when the value is not a phase number at all
|
|
57
|
+
*/
|
|
58
|
+
export function toCurrentPhase(phase, schema) {
|
|
59
|
+
const n = Number(phase);
|
|
60
|
+
if (!Number.isInteger(n)) return null;
|
|
61
|
+
if (schema >= CURRENT_SCHEMA) return CURRENT_NAMES.has(n) ? n : null;
|
|
62
|
+
return GEN1_TO_CURRENT.has(n) ? GEN1_TO_CURRENT.get(n) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The name the phase number carried WHEN IT WAS WRITTEN. A historical row is
|
|
67
|
+
* labelled with its own vocabulary's name; relabelling it with today's name
|
|
68
|
+
* would make the archive disagree with the log it came from.
|
|
69
|
+
*
|
|
70
|
+
* @param {string|number} phase
|
|
71
|
+
* @param {number} schema
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export function phaseNameAsWritten(phase, schema) {
|
|
75
|
+
const n = Number(phase);
|
|
76
|
+
const table = schema >= CURRENT_SCHEMA ? CURRENT_NAMES : GEN1_NAMES;
|
|
77
|
+
return table.get(n) ?? `Phase ${phase}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convenience for the common shape: a row in, its current-vocabulary phase out.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} row
|
|
84
|
+
* @returns {number|null}
|
|
85
|
+
*/
|
|
86
|
+
export function rowPhase(row) {
|
|
87
|
+
return toCurrentPhase(row?.phase, schemaOf(row));
|
|
88
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# plan-todos.sh - manage the Phase
|
|
3
|
+
# plan-todos.sh - manage the Phase 1 plan as a live Todo list.
|
|
4
4
|
#
|
|
5
|
-
# The Phase
|
|
5
|
+
# The Phase 1 plan is broken into a live, reviewable Todo list that updates
|
|
6
6
|
# step-by-step as Phase 3 works through it.
|
|
7
7
|
#
|
|
8
8
|
# State lives in `agent-state.json` under `.plan.todos[]` per
|
|
9
9
|
# pipeline/schemas/plan-todos.schema.json. Phase 2 (Planning) emits the
|
|
10
10
|
# initial plan; Phase 3 (Dev) iterates step-by-step; Phase 4 (Review)
|
|
11
|
-
# inspects completion; Phase
|
|
11
|
+
# inspects completion; Phase 5 (Report) renders the rollup.
|
|
12
12
|
#
|
|
13
13
|
# Subcommands:
|
|
14
14
|
# init <task-id> [title] Initialize with empty todos[] (Phase 2 usually pipes the JSON instead)
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# skip <task-id> <todo-id> <reason> Mark skipped (counts as satisfied for dependents)
|
|
20
20
|
# next <task-id> Print next pending todo (deps-respecting); empty if none
|
|
21
21
|
# list <task-id> Render Markdown checklist
|
|
22
|
-
# status <task-id> Print one-line summary: "
|
|
22
|
+
# status <task-id> Print one-line summary: "2/5 done, 1 in progress"
|
|
23
23
|
# show <task-id> Print the full plan JSON
|
|
24
24
|
#
|
|
25
25
|
# Exit codes:
|
|
@@ -30,6 +30,31 @@
|
|
|
30
30
|
|
|
31
31
|
set -euo pipefail
|
|
32
32
|
|
|
33
|
+
# jq is not optional on this path. Without the guard below a missing binary
|
|
34
|
+
# renders as EMPTY DATA and the work continues on it; see lib/require-jq.sh.
|
|
35
|
+
for _rq in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/require-jq.sh" \
|
|
36
|
+
"$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/require-jq.sh" \
|
|
37
|
+
"$HOME/.claude/lib/require-jq.sh" \
|
|
38
|
+
"$HOME/.copilot/lib/require-jq.sh" \
|
|
39
|
+
"$HOME/.codex/lib/require-jq.sh"; do
|
|
40
|
+
[ -f "$_rq" ] || continue
|
|
41
|
+
# shellcheck source=/dev/null
|
|
42
|
+
. "$_rq" && break
|
|
43
|
+
done
|
|
44
|
+
unset _rq
|
|
45
|
+
if ! command -v ma_require_jq >/dev/null 2>&1; then
|
|
46
|
+
# The helper itself is missing, which is an install problem, not a jq one.
|
|
47
|
+
ma_require_jq() { command -v jq >/dev/null 2>&1 || { echo "jq not found - cannot ${1:-continue}." >&2; return 1; }; }
|
|
48
|
+
fi
|
|
49
|
+
ma_require_jq "read or write the plan" || exit 3
|
|
50
|
+
|
|
51
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
|
|
52
|
+
# (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
|
|
53
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
54
|
+
# shellcheck source=/dev/null
|
|
55
|
+
. "$_MA_RP_HERE/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
56
|
+
|
|
57
|
+
|
|
33
58
|
CMD="${1:-}"
|
|
34
59
|
shift || true
|
|
35
60
|
|
|
@@ -49,12 +74,8 @@ resolve_state() {
|
|
|
49
74
|
# directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/) - not the
|
|
50
75
|
# ~/.claude/projects/*/state/ path this used to glob, which no writer in
|
|
51
76
|
# the pipeline ever populates.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
found="$candidate"
|
|
55
|
-
break
|
|
56
|
-
fi
|
|
57
|
-
done
|
|
77
|
+
# This globbed ONLY the nested layout; flat-layout runs resolved to nothing.
|
|
78
|
+
found="$(ma_resolve_run_file "$task_id" agent-state.json 2>/dev/null || true)"
|
|
58
79
|
if [ -z "$found" ]; then
|
|
59
80
|
echo "plan-todos: agent-state.json not found for task=$task_id" >&2
|
|
60
81
|
exit 1
|
|
@@ -96,7 +117,7 @@ do_set() {
|
|
|
96
117
|
fi
|
|
97
118
|
# A planning-output document is accepted directly and converted here.
|
|
98
119
|
#
|
|
99
|
-
# The conversion used to live as a jq blob inside phase-
|
|
120
|
+
# The conversion used to live as a jq blob inside phase-1-plan.md, which
|
|
100
121
|
# made the mapping from `tasks[]` to `todos[]` a thing two files defined - and
|
|
101
122
|
# the phase doc was the copy nothing tested. Accepting both shapes costs four
|
|
102
123
|
# lines and removes the second definition.
|
|
@@ -34,6 +34,65 @@
|
|
|
34
34
|
|
|
35
35
|
set -euo pipefail
|
|
36
36
|
|
|
37
|
+
# jq is not optional on this path. Without the guard below a missing binary
|
|
38
|
+
# renders as EMPTY DATA and the work continues on it; see lib/require-jq.sh.
|
|
39
|
+
for _rq in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/require-jq.sh" \
|
|
40
|
+
"$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/require-jq.sh" \
|
|
41
|
+
"$HOME/.claude/lib/require-jq.sh" \
|
|
42
|
+
"$HOME/.copilot/lib/require-jq.sh" \
|
|
43
|
+
"$HOME/.codex/lib/require-jq.sh"; do
|
|
44
|
+
[ -f "$_rq" ] || continue
|
|
45
|
+
# shellcheck source=/dev/null
|
|
46
|
+
. "$_rq" && break
|
|
47
|
+
done
|
|
48
|
+
unset _rq
|
|
49
|
+
if ! command -v ma_require_jq >/dev/null 2>&1; then
|
|
50
|
+
# The helper itself is missing, which is an install problem, not a jq one.
|
|
51
|
+
ma_require_jq() { command -v jq >/dev/null 2>&1 || { echo "jq not found - cannot ${1:-continue}." >&2; return 1; }; }
|
|
52
|
+
fi
|
|
53
|
+
ma_require_jq "parse the review findings" || exit 3
|
|
54
|
+
|
|
55
|
+
# Outbound leak gate. Every byte below is composed at runtime out of command
|
|
56
|
+
# output, error text and file excerpts, any of which can carry a token that was
|
|
57
|
+
# on this machine a second earlier - and once it is in a comment it is in
|
|
58
|
+
# someone else's database. The repo's own leak scanner looks at FILES IN THE
|
|
59
|
+
# REPO and never sees this text. See lib/outbound-gate.mjs.
|
|
60
|
+
ma_outbound_gate() {
|
|
61
|
+
local body_file="$1" og=""
|
|
62
|
+
for c in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/outbound-gate.mjs" \
|
|
63
|
+
"$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/outbound-gate.mjs" \
|
|
64
|
+
"$HOME/.claude/lib/outbound-gate.mjs" \
|
|
65
|
+
"$HOME/.copilot/lib/outbound-gate.mjs" \
|
|
66
|
+
"$HOME/.codex/lib/outbound-gate.mjs"; do
|
|
67
|
+
[ -f "$c" ] && { og="$c"; break; }
|
|
68
|
+
done
|
|
69
|
+
# Missing gate is NOT an open door: refusing to publish beats publishing
|
|
70
|
+
# unchecked, and the only way this file is absent is a broken install.
|
|
71
|
+
if [ -z "$og" ]; then
|
|
72
|
+
echo "outbound-gate.mjs not found - refusing to publish unchecked text." >&2
|
|
73
|
+
return 7
|
|
74
|
+
fi
|
|
75
|
+
node "$og" --file "$body_file"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Same gate, for the bodies this file keeps in a variable rather than a file.
|
|
79
|
+
ma_outbound_gate_text() {
|
|
80
|
+
local tmp
|
|
81
|
+
tmp=$(mktemp) || return 7
|
|
82
|
+
printf '%s' "$1" > "$tmp"
|
|
83
|
+
ma_outbound_gate "$tmp"
|
|
84
|
+
local rc=$?
|
|
85
|
+
rm -f "$tmp"
|
|
86
|
+
return $rc
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
|
|
90
|
+
# (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
|
|
91
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
92
|
+
# shellcheck source=/dev/null
|
|
93
|
+
. "$_MA_RP_HERE/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
94
|
+
|
|
95
|
+
|
|
37
96
|
# Basic-auth via a curl config fed through process substitution so the
|
|
38
97
|
# credential never appears in argv (argv is visible to `ps` / process audit).
|
|
39
98
|
bb_auth_cfg() { printf 'user = "%s:%s"\n' "$1" "$2"; }
|
|
@@ -49,13 +108,10 @@ fi
|
|
|
49
108
|
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
50
109
|
# - not the ~/.claude/projects/*/state/ path this used to glob, which no
|
|
51
110
|
# writer in the pipeline ever populates.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
break
|
|
57
|
-
fi
|
|
58
|
-
done
|
|
111
|
+
# This globbed ONLY the nested layout. phase-tracker.sh and most Phase 0 paths
|
|
112
|
+
# write flat, so on a real machine the majority of runs resolved to nothing and
|
|
113
|
+
# this script reported "no agent-state.json" for a run whose state existed.
|
|
114
|
+
AGENT_STATE="$(ma_resolve_run_file "$TASK_ID" agent-state.json 2>/dev/null || true)"
|
|
59
115
|
|
|
60
116
|
if [ -z "$AGENT_STATE" ]; then
|
|
61
117
|
echo "post-pr-review: agent-state.json not found for task=$TASK_ID" >&2
|
|
@@ -271,6 +327,13 @@ post_github() {
|
|
|
271
327
|
continue
|
|
272
328
|
fi
|
|
273
329
|
|
|
330
|
+
# A finding's body quotes the diff and the reviewer's prose, so it is
|
|
331
|
+
# the most likely of the three to carry something from the machine.
|
|
332
|
+
if ! ma_outbound_gate_text "$body"; then
|
|
333
|
+
echo "post-pr-review: outbound gate refused the comment for $path:$line - not posted" >&2
|
|
334
|
+
continue
|
|
335
|
+
fi
|
|
336
|
+
|
|
274
337
|
local inline_ok=0
|
|
275
338
|
if [ -n "$head_sha" ] && [ "$line" -gt 0 ] 2>/dev/null; then
|
|
276
339
|
if gh api -X POST "/repos/$org_repo/pulls/$PR_NUM/comments" \
|
|
@@ -290,7 +353,13 @@ post_github() {
|
|
|
290
353
|
|
|
291
354
|
case "$DECISION" in
|
|
292
355
|
approve) gh pr review "$PR_NUM" --repo "$org_repo" --approve ;;
|
|
293
|
-
needs_work)
|
|
356
|
+
needs_work)
|
|
357
|
+
if ! ma_outbound_gate_text "$rc_body"; then
|
|
358
|
+
echo "post-pr-review: outbound gate refused the request-changes body; no verdict posted" >&2
|
|
359
|
+
return 7
|
|
360
|
+
fi
|
|
361
|
+
gh pr review "$PR_NUM" --repo "$org_repo" --request-changes --body "$rc_body"
|
|
362
|
+
;;
|
|
294
363
|
esac
|
|
295
364
|
}
|
|
296
365
|
|
|
@@ -141,11 +141,16 @@ ma_hygiene_local_gitignore() {
|
|
|
141
141
|
# rmdir, never rm -rf: it fails harmlessly when anything is left, which is the
|
|
142
142
|
# behaviour we want. An empty .worktrees/ and .pipeline/evidence/ survive a
|
|
143
143
|
# clean run today and read as leftovers to anyone looking at the checkout.
|
|
144
|
+
#
|
|
145
|
+
# "Harmlessly" is only true while the CALLER does not run under `set -e`, and
|
|
146
|
+
# this file is sourced, so that is the caller's choice and not ours. Every
|
|
147
|
+
# rmdir here is therefore explicitly forgiven: a non-empty directory is the
|
|
148
|
+
# ordinary case, and it must not abort a teardown half-way.
|
|
144
149
|
ma_hygiene_prune_empty() {
|
|
145
150
|
local root d
|
|
146
151
|
root=$(ma_hygiene_safe_root "${1:-}") || return 0
|
|
147
152
|
for d in "$root/.pipeline/evidence" "$root/.pipeline" "$root/.worktrees/.archive" "$root/.worktrees" "$root/.multi-agent/refs"; do
|
|
148
|
-
[ -d "$d" ] && rmdir "$d" 2>/dev/null
|
|
153
|
+
[ -d "$d" ] && { rmdir "$d" 2>/dev/null || true; }
|
|
149
154
|
done
|
|
150
155
|
|
|
151
156
|
# .multi-agent survives the loop above because it still holds the .gitignore
|
|
@@ -156,8 +161,8 @@ ma_hygiene_prune_empty() {
|
|
|
156
161
|
local leftover
|
|
157
162
|
leftover=$(ls -A "$root/.multi-agent" 2>/dev/null)
|
|
158
163
|
if [ "$leftover" = ".gitignore" ]; then
|
|
159
|
-
rm -f "$root/.multi-agent/.gitignore" 2>/dev/null
|
|
160
|
-
rmdir "$root/.multi-agent" 2>/dev/null
|
|
164
|
+
rm -f "$root/.multi-agent/.gitignore" 2>/dev/null || true
|
|
165
|
+
rmdir "$root/.multi-agent" 2>/dev/null || true
|
|
161
166
|
fi
|
|
162
167
|
fi
|
|
163
168
|
return 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# require-jq.sh - one place to refuse when `jq` is missing.
|
|
3
|
+
#
|
|
4
|
+
# 78 shell files in this repo call `jq`. 46 of them called it with no check at
|
|
5
|
+
# all, and the README described that as helpers which "skip in silence" - which
|
|
6
|
+
# is the defect stated as if it were the design. A bare `jq` on a machine
|
|
7
|
+
# without it does not skip. It writes "command not found" to stderr, produces
|
|
8
|
+
# an empty string on stdout, and the caller carries on with that empty string
|
|
9
|
+
# as if it were the answer.
|
|
10
|
+
#
|
|
11
|
+
# The nine production paths are where that matters:
|
|
12
|
+
#
|
|
13
|
+
# autopilot-state.sh an unreadable queue reads as an EMPTY queue, so the
|
|
14
|
+
# runner concludes there is no work and goes quiet
|
|
15
|
+
# jira-publish.sh posts a comment built from empty fields
|
|
16
|
+
# post-pr-review.sh publishes a review whose findings did not parse
|
|
17
|
+
# update-issue-progress.sh same, on an issue
|
|
18
|
+
# _jira-auth.sh no credential, and the failure surfaces later as 401
|
|
19
|
+
# figma-mcp-refresh.sh / figma-screenshot.sh silent no-op on a design fetch
|
|
20
|
+
# plan-todos.sh an empty plan looks like a plan with no work in it
|
|
21
|
+
# search-logs.sh "no results" instead of "could not search"
|
|
22
|
+
#
|
|
23
|
+
# Every one of those is the same shape: absence of a tool rendered as absence
|
|
24
|
+
# of data. Refusing is the only honest answer, and the exit code is distinct
|
|
25
|
+
# so a caller can tell "jq missing" from "the work failed".
|
|
26
|
+
#
|
|
27
|
+
# Usage:
|
|
28
|
+
# . "$(dirname "$0")/require-jq.sh"
|
|
29
|
+
# ma_require_jq "read the autopilot queue" || exit 3
|
|
30
|
+
#
|
|
31
|
+
# It returns rather than exits: this file is SOURCED, and a sourced file that
|
|
32
|
+
# exits kills the caller's shell in ways the caller did not write down.
|
|
33
|
+
|
|
34
|
+
# shellcheck disable=SC2329 # sourced by other scripts, not called here
|
|
35
|
+
ma_require_jq() {
|
|
36
|
+
command -v jq >/dev/null 2>&1 && return 0
|
|
37
|
+
printf 'jq not found - cannot %s.\n' "${1:-continue}" >&2
|
|
38
|
+
printf ' install it: brew install jq\n' >&2
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# route-state.sh - read and write prefs.global.modelRouting.
|
|
3
|
+
#
|
|
4
|
+
# Backs /multi-agent:route-on, route-off and route-status. Ships disabled and
|
|
5
|
+
# stays that way until someone turns it on; while `enabled` is false nothing in
|
|
6
|
+
# this file changes one dispatch.
|
|
7
|
+
#
|
|
8
|
+
# The `off` contract is borrowed from autopilot-off and for the same reason: it
|
|
9
|
+
# clears the ON STATE, never the rules. Turning routing back on must not re-ask
|
|
10
|
+
# for a configuration the user already gave.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# route-state.sh status print the current policy
|
|
14
|
+
# route-state.sh on [--strategy=S] [--scope=a,b]
|
|
15
|
+
# route-state.sh off
|
|
16
|
+
# route-state.sh set-rules <file.json> replace rules[] from a JSON array
|
|
17
|
+
#
|
|
18
|
+
# Exit codes:
|
|
19
|
+
# 0 - done
|
|
20
|
+
# 2 - bad usage
|
|
21
|
+
# 3 - preferences missing/unparseable, or a scope value this refuses
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
PREFS="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}"
|
|
26
|
+
|
|
27
|
+
# The scope values this accepts, spelled once. `host-session` is NOT here and
|
|
28
|
+
# must never be added: it would mean rewriting the host's base URL, which routes
|
|
29
|
+
# the user's whole session - work that has nothing to do with this pipeline
|
|
30
|
+
# included - through a third layer, breaks the subscription's auth model, and
|
|
31
|
+
# silently changes which model answers. The schema encodes the same list so a
|
|
32
|
+
# hand-edited preferences file is rejected too, rather than only this entry point.
|
|
33
|
+
ALLOWED_SCOPE="subagent bulk-read research"
|
|
34
|
+
|
|
35
|
+
die() { echo "route-state: $1" >&2; exit "${2:-3}"; }
|
|
36
|
+
|
|
37
|
+
# A missing `jq` must not look like routing being off. Every read below would
|
|
38
|
+
# come back empty, `route-status` would report disabled, and a user with rules
|
|
39
|
+
# armed would be told there are none.
|
|
40
|
+
command -v jq >/dev/null 2>&1 || die "jq not found - cannot read or write preferences (install: brew install jq)"
|
|
41
|
+
[ -f "$PREFS" ] || die "preferences not found at $PREFS - run /multi-agent:setup first"
|
|
42
|
+
jq -e . "$PREFS" >/dev/null 2>&1 || die "$PREFS is not valid JSON - refusing to write over it"
|
|
43
|
+
|
|
44
|
+
ACTION="${1:-status}"; shift 2>/dev/null || true
|
|
45
|
+
|
|
46
|
+
write_prefs() {
|
|
47
|
+
local filter="$1"; shift
|
|
48
|
+
local tmp
|
|
49
|
+
tmp=$(mktemp) || die "mktemp failed"
|
|
50
|
+
if ! jq "$@" "$filter" "$PREFS" > "$tmp"; then rm -f "$tmp"; die "jq failed - preferences left untouched"; fi
|
|
51
|
+
jq -e . "$tmp" >/dev/null 2>&1 || { rm -f "$tmp"; die "produced invalid JSON - preferences left untouched"; }
|
|
52
|
+
mv "$tmp" "$PREFS"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case "$ACTION" in
|
|
56
|
+
status)
|
|
57
|
+
ENABLED=$(jq -r '.global.modelRouting.enabled // false' "$PREFS")
|
|
58
|
+
STRATEGY=$(jq -r '.global.modelRouting.strategy // "manual"' "$PREFS")
|
|
59
|
+
SCOPE=$(jq -r '(.global.modelRouting.scope // ["subagent"]) | join(", ")' "$PREFS")
|
|
60
|
+
NRULES=$(jq -r '(.global.modelRouting.rules // []) | length' "$PREFS")
|
|
61
|
+
CEIL=$(jq -r '.global.modelRouting.budgetCeilingUsd // "none"' "$PREFS")
|
|
62
|
+
REC=$(jq -r '.global.modelRouting.recordDecisions // true' "$PREFS")
|
|
63
|
+
|
|
64
|
+
echo "routing: $ENABLED"
|
|
65
|
+
echo "strategy: $STRATEGY"
|
|
66
|
+
echo "scope: $SCOPE"
|
|
67
|
+
echo "rules: $NRULES"
|
|
68
|
+
echo "ceiling: $CEIL"
|
|
69
|
+
echo "decisions: $REC"
|
|
70
|
+
echo ""
|
|
71
|
+
|
|
72
|
+
if [ "$ENABLED" != "true" ]; then
|
|
73
|
+
echo "Disabled. Rules are stored but read by nothing; dispatch is unchanged."
|
|
74
|
+
[ "$NRULES" -gt 0 ] && echo "route-on turns these $NRULES rule(s) back on without re-asking."
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ "$NRULES" -eq 0 ]; then
|
|
79
|
+
echo "Armed with no rules. Nothing matches, so nothing is routed - this is a"
|
|
80
|
+
echo "configuration state, not an error."
|
|
81
|
+
else
|
|
82
|
+
jq -r '(.global.modelRouting.rules // [])[] |
|
|
83
|
+
" when " + ([.when | to_entries[] | "\(.key)=\(.value)"] | join(" ")) +
|
|
84
|
+
" -> " + (.prefer | join(" > "))' "$PREFS"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
echo ""
|
|
88
|
+
# The limit is printed every time rather than documented once, because the
|
|
89
|
+
# question it answers ("routing is on, why is the reviewer still on Opus")
|
|
90
|
+
# otherwise arrives days later as a bug report.
|
|
91
|
+
echo "Honest limit: on Claude Code a subagent cannot be sent to a non-Anthropic"
|
|
92
|
+
echo "model - subagent dispatch belongs to the host. Phase 1/2/3 personas stay"
|
|
93
|
+
echo "inside the Anthropic ladder whatever the rules say. External providers apply"
|
|
94
|
+
echo "only where this pipeline makes the call itself (bulk-read, research)."
|
|
95
|
+
;;
|
|
96
|
+
|
|
97
|
+
on)
|
|
98
|
+
STRATEGY="manual"; SCOPE_ARG=""
|
|
99
|
+
for arg in "$@"; do
|
|
100
|
+
case "$arg" in
|
|
101
|
+
--strategy=*) STRATEGY="${arg#*=}" ;;
|
|
102
|
+
--scope=*) SCOPE_ARG="${arg#*=}" ;;
|
|
103
|
+
*) die "unknown option: $arg" 2 ;;
|
|
104
|
+
esac
|
|
105
|
+
done
|
|
106
|
+
case "$STRATEGY" in
|
|
107
|
+
manual|task-fit|cost-ceiling) ;;
|
|
108
|
+
*) die "strategy must be manual, task-fit or cost-ceiling (got '$STRATEGY')" 2 ;;
|
|
109
|
+
esac
|
|
110
|
+
|
|
111
|
+
if [ -n "$SCOPE_ARG" ]; then
|
|
112
|
+
SCOPE_JSON="[]"
|
|
113
|
+
IFS=',' read -r -a parts <<< "$SCOPE_ARG"
|
|
114
|
+
for one in "${parts[@]}"; do
|
|
115
|
+
one="${one// /}"
|
|
116
|
+
# shellcheck disable=SC2076
|
|
117
|
+
case " $ALLOWED_SCOPE " in
|
|
118
|
+
*" $one "*) ;;
|
|
119
|
+
*) die "scope '$one' is not allowed (allowed: $ALLOWED_SCOPE). 'host-session' is refused by design: it would route the whole session, not this pipeline's calls." ;;
|
|
120
|
+
esac
|
|
121
|
+
SCOPE_JSON=$(jq -c --arg s "$one" '. + [$s]' <<< "$SCOPE_JSON")
|
|
122
|
+
done
|
|
123
|
+
write_prefs '
|
|
124
|
+
.global.modelRouting //= {}
|
|
125
|
+
| .global.modelRouting.enabled = true
|
|
126
|
+
| .global.modelRouting.strategy = $st
|
|
127
|
+
| .global.modelRouting.scope = ($sc | fromjson)
|
|
128
|
+
' --arg st "$STRATEGY" --arg sc "$SCOPE_JSON"
|
|
129
|
+
else
|
|
130
|
+
write_prefs '
|
|
131
|
+
.global.modelRouting //= {}
|
|
132
|
+
| .global.modelRouting.enabled = true
|
|
133
|
+
| .global.modelRouting.strategy = $st
|
|
134
|
+
| .global.modelRouting.scope //= ["subagent"]
|
|
135
|
+
' --arg st "$STRATEGY"
|
|
136
|
+
fi
|
|
137
|
+
echo "routing enabled (strategy: $STRATEGY)"
|
|
138
|
+
exec "$0" status
|
|
139
|
+
;;
|
|
140
|
+
|
|
141
|
+
off)
|
|
142
|
+
# Rules survive on purpose. Clearing them here would make route-off a
|
|
143
|
+
# destructive action wearing the name of a toggle.
|
|
144
|
+
write_prefs '.global.modelRouting //= {} | .global.modelRouting.enabled = false'
|
|
145
|
+
echo "routing disabled. Rules kept - route-on restores this configuration as it is."
|
|
146
|
+
;;
|
|
147
|
+
|
|
148
|
+
set-rules)
|
|
149
|
+
FILE="${1:-}"
|
|
150
|
+
[ -n "$FILE" ] || die "set-rules needs a JSON file containing an array of rules" 2
|
|
151
|
+
[ -f "$FILE" ] || die "no such file: $FILE" 2
|
|
152
|
+
jq -e 'type == "array"' "$FILE" >/dev/null 2>&1 || die "$FILE must contain a JSON ARRAY of rules" 2
|
|
153
|
+
write_prefs '.global.modelRouting //= {} | .global.modelRouting.rules = $r' --slurpfile _ignore /dev/null --argjson r "$(cat "$FILE")"
|
|
154
|
+
echo "rules replaced: $(jq -r 'length' "$FILE")"
|
|
155
|
+
;;
|
|
156
|
+
|
|
157
|
+
*)
|
|
158
|
+
echo "usage: route-state.sh [status|on|off|set-rules <file>]" >&2
|
|
159
|
+
exit 2
|
|
160
|
+
;;
|
|
161
|
+
esac
|