@mmerterden/multi-agent-pipeline 20.20.0 → 20.22.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 +58 -0
- package/README.md +24 -13
- package/README.tr.md +21 -10
- package/docs/adr/0017-change-impact-from-the-graph.md +69 -0
- package/docs/adr/README.md +1 -0
- package/docs/ecosystem.md +7 -5
- package/docs/facts.json +6 -4
- package/docs/features.md +1 -0
- package/install/catalog-history.json +1 -1
- package/install/templates/copilot-instructions.md +1 -0
- package/manifest.json +71 -64
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/review/SKILL.md +2 -1
- package/pipeline/commands/multi-agent/route-on/SKILL.md +32 -8
- package/pipeline/commands/multi-agent/route-status/SKILL.md +12 -2
- package/pipeline/commands/multi-agent/setup/SKILL.md +8 -16
- package/pipeline/lib/model-dispatch.sh +72 -24
- package/pipeline/lib/outbound-gate.mjs +2 -0
- package/pipeline/lib/route-state.sh +118 -10
- package/pipeline/lib/toolkit-llm.sh +30 -0
- package/pipeline/lib/unattended-profile-check.mjs +23 -2
- package/pipeline/multi-agent-refs/_dev-context.md +15 -0
- package/pipeline/multi-agent-refs/analysis/evidence.md +7 -3
- package/pipeline/multi-agent-refs/analysis/locked.md +1 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +9 -3
- package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
- package/pipeline/multi-agent-refs/features/doctor.md +11 -0
- package/pipeline/multi-agent-refs/features/free-models.md +111 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +21 -15
- package/pipeline/multi-agent-refs/features/product-context.md +238 -0
- package/pipeline/multi-agent-refs/features/unattended-security.md +4 -2
- package/pipeline/multi-agent-refs/knowledge.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +7 -12
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +7 -6
- package/pipeline/multi-agent-refs/platform-parity.md +23 -9
- package/pipeline/multi-agent-refs/setup/free-models.md +22 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +24 -0
- package/pipeline/preferences-template.json +1 -0
- package/pipeline/schemas/agent-state.schema.json +31 -0
- package/pipeline/schemas/analysis-spec.schema.json +14 -0
- package/pipeline/schemas/code-graph.schema.json +12 -1
- package/pipeline/schemas/diff-risk.schema.json +2 -1
- package/pipeline/schemas/prefs.schema.json +68 -1
- package/pipeline/schemas/route-config.schema.json +33 -8
- package/pipeline/schemas/secret-patterns.json +14 -0
- package/pipeline/scripts/_code-graph.mjs +76 -3
- package/pipeline/scripts/_diff-impact.mjs +312 -0
- package/pipeline/scripts/bulk-read.sh +98 -12
- package/pipeline/scripts/capture-flush.sh +58 -3
- package/pipeline/scripts/cost-table.json +9 -1
- package/pipeline/scripts/diff-risk-score.mjs +80 -9
- package/pipeline/scripts/doctor.mjs +100 -0
- package/pipeline/scripts/graph-affected.mjs +167 -36
- package/pipeline/scripts/graph-query.mjs +16 -4
- package/pipeline/scripts/pre-commit-check.sh +1 -1
- package/pipeline/scripts/product-context.mjs +539 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +39 -1
- package/pipeline/scripts/unattended_policy.py +20 -0
- package/pipeline/scripts/validate-code-graph.mjs +2 -1
- package/pipeline/scripts/validate-diff-risk.mjs +1 -0
- package/pipeline/skills/.skill-manifest.json +6 -6
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +2 -0
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -1
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +31 -7
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +12 -2
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +9 -6
|
@@ -14,13 +14,13 @@ description: "Internal - cross-platform parity cross-check for Phase 4 and /mu
|
|
|
14
14
|
- [Severity](#severity)
|
|
15
15
|
<!-- /toc -->
|
|
16
16
|
|
|
17
|
-
A feature that exists on
|
|
18
|
-
drift. The drift is invisible from inside one repo: the iOS diff is
|
|
17
|
+
A feature that exists on several platforms is written more than once, and the
|
|
18
|
+
copies drift. The drift is invisible from inside one repo: the iOS diff is
|
|
19
19
|
self-consistent, the tests pass, and nobody notices that the Android screen
|
|
20
20
|
calls a different endpoint, sends one parameter fewer, or reads a localization
|
|
21
21
|
key that no longer exists on the other side.
|
|
22
22
|
|
|
23
|
-
This step reads the counterpart
|
|
23
|
+
This step reads the counterpart repositories and reports what differs. It is advisory:
|
|
24
24
|
it never blocks, never edits, and its silence is never evidence.
|
|
25
25
|
|
|
26
26
|
## When it runs
|
|
@@ -29,6 +29,17 @@ The counterpart is **resolved automatically**. Nothing is asked in the normal
|
|
|
29
29
|
case, because the answer is the same on every run of the same project and a
|
|
30
30
|
question asked every time is a question people learn to dismiss.
|
|
31
31
|
|
|
32
|
+
**Source 0, when present, replaces the rest.** If Phase 1 recorded
|
|
33
|
+
`state.productContext[]` (`features/product-context.md`), every entry without
|
|
34
|
+
an `error` is a counterpart: the product's other app, web and backend
|
|
35
|
+
repositories, read at the sha Phase 1 pinned, through `repo_grep` /
|
|
36
|
+
`repo_read` / `repo_find_keys`. No local checkout is needed and none is read,
|
|
37
|
+
so the comparison is against the latest integration branch rather than
|
|
38
|
+
whatever a sibling directory has checked out. A `stale` entry is still
|
|
39
|
+
compared, and the section header says it is stale. Resources entries are
|
|
40
|
+
compared on axis 4 only.
|
|
41
|
+
|
|
42
|
+
Without product context, the mirrored-platform resolution below applies.
|
|
32
43
|
Resolution order, first hit wins. `<slug>` is the primary project's prefs key,
|
|
33
44
|
and `MIRROR` is `android` when the run's stack is `ios`, `ios` when it is
|
|
34
45
|
`android` - the check runs in both directions and neither platform is the
|
|
@@ -38,7 +49,7 @@ default one.
|
|
|
38
49
|
|---|---|---|
|
|
39
50
|
| 1 | `--with <path\|owner/repo>` (`/multi-agent:review` only) | An explicit override for a one-off. Remembered like any other resolution. |
|
|
40
51
|
| 2 | `prefs.projects[<slug>].counterpartRoots[]` | What a previous run learned. Verified before use: the path must still exist and its markers must still resolve to `MIRROR`, otherwise the entry is dropped and detection continues at 3. A remembered answer that has gone stale is worse than none. |
|
|
41
|
-
| 3 | Sibling checkouts of the primary repo | List the primary checkout's **parent directory**, depth 1, keep the git repos, resolve each one's stack from its marker files
|
|
52
|
+
| 3 | Sibling checkouts of the primary repo | List the primary checkout's **parent directory**, depth 1, keep the git repos, resolve each one's stack from its marker files, and keep only those whose name core matches the primary's (`product-context.mjs suggest`: platform and version words dropped, so `acme-shop-ios-new` keeps `acme-shop-android` and drops an unrelated repo that merely has Android markers). For a run inside a worktree, the parent is that of the primary repository's main checkout, not of the worktree. One directory listing, not a scan of `$HOME`. |
|
|
42
53
|
| 4 | `state.siblings[]` | Anything the Phase 0 dev-context picker recorded, in case the counterpart is a submodule or a repo the user selected by hand. |
|
|
43
54
|
|
|
44
55
|
Then:
|
|
@@ -64,9 +75,9 @@ The second condition is the diff: it has to touch something with a counterpart
|
|
|
64
75
|
worth checking - a screen, a service/repository/use-case, a request model, or a
|
|
65
76
|
localization file. Otherwise the step is skipped even when a counterpart exists.
|
|
66
77
|
|
|
67
|
-
Nothing is
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
Nothing is cloned into a workspace. Product-context counterparts live in the
|
|
79
|
+
`repo_*` tools' read-only cache, which has no working tree; a fallback
|
|
80
|
+
counterpart that is not checked out locally is skipped.
|
|
70
81
|
|
|
71
82
|
## Read-only, without exception
|
|
72
83
|
|
|
@@ -80,8 +91,11 @@ for a human, not a task for the pipeline.
|
|
|
80
91
|
|
|
81
92
|
## Locating the counterpart, deterministically
|
|
82
93
|
|
|
83
|
-
Do not
|
|
84
|
-
|
|
94
|
+
Do not read the other repo broadly. For a product-context counterpart, look the
|
|
95
|
+
diff's identifiers up with one `repo_find_keys` call at the recorded sha (paths,
|
|
96
|
+
endpoint strings, request type names, localization keys), then `repo_read` the
|
|
97
|
+
hit files. For a local fallback counterpart, use the code graph, which is
|
|
98
|
+
LLM-free and token-budgeted:
|
|
85
99
|
|
|
86
100
|
```bash
|
|
87
101
|
SIB_STACK=android # or ios, mirrored
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Setup Step 3e - free-model keys
|
|
2
|
+
|
|
3
|
+
Runs inside setup Step 3, only when `prefs.global.modelRouting.free.enabled` is
|
|
4
|
+
true (`/multi-agent:route-on --free`). These are the keys the toolkit's
|
|
5
|
+
free-model chain reads; `llm_delegate` and `bulk-read.sh` look for them under
|
|
6
|
+
fixed names, so there is no `keychainMapping` entry.
|
|
7
|
+
|
|
8
|
+
| Key | Provider | Where to get it | Asked |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| `llm-groq` | Groq (free tier does not train on prompts) | https://console.groq.com/keys | always first |
|
|
11
|
+
| `llm-mistral` | Mistral | https://console.mistral.ai/api-keys | only with `modelRouting.free.allowTraining` |
|
|
12
|
+
| `llm-gemini` | Google Gemini | https://aistudio.google.com/apikey | only with `allowTraining` |
|
|
13
|
+
| `llm-openrouter` | OpenRouter, free models only | https://openrouter.ai/keys | only with `allowTraining` |
|
|
14
|
+
| `llm-zai` | Z.AI GLM Flash | https://z.ai/manage-apikey/apikey-list | only with `allowTraining` |
|
|
15
|
+
|
|
16
|
+
Each key is one Token Save Flow pass (`token-save-flow.md`) with the clipboard
|
|
17
|
+
save, `pbpaste | credential-store.sh set "<key>" -`, and no author or host step.
|
|
18
|
+
Skip writes nothing and moves to the next key. After the last pass, run
|
|
19
|
+
`route-state.sh status` so the user sees each saved provider reported ready.
|
|
20
|
+
|
|
21
|
+
A local Ollama needs no key: the user sets `OLLAMA_MODEL` for the server. The
|
|
22
|
+
full contract: `$HOME/.claude/multi-agent-refs/features/free-models.md`.
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## Token Save Flow (reusable)
|
|
2
2
|
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Token Save Flow (reusable)](#token-save-flow-reusable)
|
|
5
|
+
- [Step 3.5 - Host Prompt (embedded in Token Save Flow)](#step-35---host-prompt-embedded-in-token-save-flow)
|
|
6
|
+
- [Step 3.7 - Repository read binding (embedded in Token Save Flow)](#step-37---repository-read-binding-embedded-in-token-save-flow)
|
|
7
|
+
<!-- /toc -->
|
|
8
|
+
|
|
3
9
|
This flow is used in TWO contexts:
|
|
4
10
|
- **Setup Step 4** - for missing tokens during first-time onboarding
|
|
5
11
|
- **Phase 0 token pre-check** - when a token is missing or expired (401/403) at runtime
|
|
@@ -199,3 +205,21 @@ Test host → `hosts.graylogTest` (`{GRAYLOG_TEST_HOST}`); skipped means product
|
|
|
199
205
|
**Re-run / update**: `/multi-agent:setup hosts` re-opens the prompt to edit values without touching tokens.
|
|
200
206
|
|
|
201
207
|
Team-sharing note: Hosts are the only corporate metadata the pipeline needs. Tokens live in macOS Keychain (encrypted, never on disk inside the repo). Preferences file lives in `~/.claude/` - fully outside the repo clone. A teammate cloning the repo runs `/multi-agent:setup` once; host + token prompts run naturally together.
|
|
208
|
+
|
|
209
|
+
## Step 3.7 - Repository read binding (embedded in Token Save Flow)
|
|
210
|
+
|
|
211
|
+
Runs inline when the saved token can read git repositories over https (a
|
|
212
|
+
GitHub PAT, a Bitbucket Data Center HTTP access token). The multi-agent-toolkit
|
|
213
|
+
`repo_*` tools send a token only to a host bound to it here, and nothing else
|
|
214
|
+
in the pipeline writes this binding: a product map or a planner's request
|
|
215
|
+
naming a credential never creates one.
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Repository hosts this token may be sent to for read-only clone and fetch
|
|
219
|
+
(comma-separated, empty for none). Suggested: {github.com | prefs.global.hosts.bitbucket}
|
|
220
|
+
___
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Save the answer, lowercased, to `prefs.global.credentialHosts.{logical-key}`
|
|
224
|
+
(an array of hostnames). Empty leaves the key unbound. `/multi-agent:setup hosts`
|
|
225
|
+
re-opens it.
|
|
@@ -551,6 +551,37 @@
|
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
553
|
},
|
|
554
|
+
"productContext": {
|
|
555
|
+
"type": "array",
|
|
556
|
+
"maxItems": 20,
|
|
557
|
+
"description": "What Phase 1 read from the other repositories of the product (every product repo except the ones this run develops), each at its pinned sha through the toolkit's repo_* tools. Written by scripts/product-context.mjs record; read by Phase 1 planning, Phase 2 dev and Phase 3 platform parity. See multi-agent-refs/features/product-context.md.",
|
|
558
|
+
"items": {
|
|
559
|
+
"type": "object",
|
|
560
|
+
"required": ["remote", "slug"],
|
|
561
|
+
"properties": {
|
|
562
|
+
"remote": { "type": "string" },
|
|
563
|
+
"slug": { "type": "string" },
|
|
564
|
+
"stack": { "type": "string" },
|
|
565
|
+
"branch": { "type": "string" },
|
|
566
|
+
"sha": { "type": "string" },
|
|
567
|
+
"stale": { "type": "boolean" },
|
|
568
|
+
"error": { "type": "string" },
|
|
569
|
+
"keys": {
|
|
570
|
+
"type": "array",
|
|
571
|
+
"items": {
|
|
572
|
+
"type": "object",
|
|
573
|
+
"required": ["key", "status"],
|
|
574
|
+
"properties": {
|
|
575
|
+
"key": { "type": "string" },
|
|
576
|
+
"status": { "enum": ["implemented", "not-yet-implemented", "diverges", "error"] },
|
|
577
|
+
"hits": { "type": "array" },
|
|
578
|
+
"note": { "type": "string" }
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
},
|
|
554
585
|
"siblings": {
|
|
555
586
|
"type": "array",
|
|
556
587
|
"maxItems": 10,
|
|
@@ -610,6 +610,20 @@
|
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
},
|
|
613
|
+
"repoShas": {
|
|
614
|
+
"type": "array",
|
|
615
|
+
"description": "Every repo read through the toolkit's repo_* tools at its latest integration branch, with the sha it was read at. Sorted shas are an evidence_digest input, so a new commit invalidates the cache.",
|
|
616
|
+
"items": {
|
|
617
|
+
"type": "object",
|
|
618
|
+
"required": ["remote", "branch", "sha"],
|
|
619
|
+
"properties": {
|
|
620
|
+
"remote": { "type": "string" },
|
|
621
|
+
"branch": { "type": "string" },
|
|
622
|
+
"sha": { "type": "string" },
|
|
623
|
+
"stale": { "type": "boolean" }
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
},
|
|
613
627
|
"repoEvidence": {
|
|
614
628
|
"type": "object",
|
|
615
629
|
"description": "Phase 1b deep-scan result keyed by repo name. Each value holds the 13-bucket catalogue.",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
10
|
"schemaVersion": {
|
|
11
|
-
"
|
|
11
|
+
"enum": ["1.0.0", "1.1.0"],
|
|
12
|
+
"description": "1.1.0 adds endLine on symbol nodes. A 1.0.0 graph stays readable; diff mapping falls back to file level for it."
|
|
12
13
|
},
|
|
13
14
|
"stack": {
|
|
14
15
|
"type": "string",
|
|
@@ -51,6 +52,16 @@
|
|
|
51
52
|
"name": { "type": "string" },
|
|
52
53
|
"path": { "type": ["string", "null"] },
|
|
53
54
|
"line": { "type": "integer", "minimum": 1 },
|
|
55
|
+
"startLine": {
|
|
56
|
+
"type": "integer",
|
|
57
|
+
"minimum": 1,
|
|
58
|
+
"description": "First line of the declaration when attribute or decorator lines sit directly above it; absent when it equals line."
|
|
59
|
+
},
|
|
60
|
+
"endLine": {
|
|
61
|
+
"type": "integer",
|
|
62
|
+
"minimum": 1,
|
|
63
|
+
"description": "Last line indented deeper than the declaration, plus a closing bracket at its own indentation when one follows. Absent in graphs built before schema 1.1.0."
|
|
64
|
+
},
|
|
54
65
|
"symbolKind": { "type": "string" },
|
|
55
66
|
"nested": {
|
|
56
67
|
"type": "boolean",
|
|
@@ -599,10 +599,19 @@
|
|
|
599
599
|
"uniqueItems": true,
|
|
600
600
|
"items": {
|
|
601
601
|
"type": "string",
|
|
602
|
-
"enum": ["subagent", "bulk-read", "research"]
|
|
602
|
+
"enum": ["subagent", "bulk-read", "delegate", "research"]
|
|
603
603
|
},
|
|
604
604
|
"description": "No host-session member, deliberately: rewriting the host's base URL would route the user's whole session, not just this pipeline's calls."
|
|
605
605
|
},
|
|
606
|
+
"free": {
|
|
607
|
+
"type": "object",
|
|
608
|
+
"additionalProperties": false,
|
|
609
|
+
"description": "Layer 2: the verified free-model chain at bulk-read and delegate. Contract: route-config.schema.json.",
|
|
610
|
+
"properties": {
|
|
611
|
+
"enabled": { "type": "boolean", "default": false },
|
|
612
|
+
"allowTraining": { "type": "boolean", "default": false }
|
|
613
|
+
}
|
|
614
|
+
},
|
|
606
615
|
"rules": {
|
|
607
616
|
"type": "array",
|
|
608
617
|
"default": [],
|
|
@@ -1454,6 +1463,60 @@
|
|
|
1454
1463
|
}
|
|
1455
1464
|
}
|
|
1456
1465
|
},
|
|
1466
|
+
"credentialHosts": {
|
|
1467
|
+
"type": "object",
|
|
1468
|
+
"description": "Hosts each credential-store key may be sent to by the multi-agent-toolkit repo_* tools for read-only clone and fetch over https. Written by /multi-agent:setup (Token Save Flow Step 3.7) with the person present; product-context.mjs learn refuses a repository credential whose host is not listed here, and the toolkit refuses to send a token anywhere else.",
|
|
1469
|
+
"additionalProperties": {
|
|
1470
|
+
"type": "array",
|
|
1471
|
+
"items": { "type": "string", "pattern": "^[a-z0-9.-]+$" },
|
|
1472
|
+
"maxItems": 20
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
"products": {
|
|
1476
|
+
"type": "object",
|
|
1477
|
+
"description": "Products and the repositories each is made of, for the planning step that reads every repository of the product except the ones a run develops (scripts/product-context.mjs, toolkit repo_* tools). Keyed by product name; a project joins one through projects[<key>].product, or implicitly when one of its developed remotes is listed here. Written by product-context.mjs learn after the user confirms; read-only to everything else.",
|
|
1478
|
+
"propertyNames": { "pattern": "^[A-Za-z0-9._-]{1,64}$" },
|
|
1479
|
+
"additionalProperties": {
|
|
1480
|
+
"type": "object",
|
|
1481
|
+
"additionalProperties": false,
|
|
1482
|
+
"required": ["repos"],
|
|
1483
|
+
"properties": {
|
|
1484
|
+
"repos": {
|
|
1485
|
+
"type": "array",
|
|
1486
|
+
"minItems": 1,
|
|
1487
|
+
"maxItems": 20,
|
|
1488
|
+
"items": {
|
|
1489
|
+
"type": "object",
|
|
1490
|
+
"additionalProperties": false,
|
|
1491
|
+
"required": ["stack", "remote"],
|
|
1492
|
+
"properties": {
|
|
1493
|
+
"stack": { "enum": ["ios", "android", "web", "backend", "resources"] },
|
|
1494
|
+
"remote": {
|
|
1495
|
+
"type": "string",
|
|
1496
|
+
"description": "https://, ssh:// or user@host:path remote, without credentials."
|
|
1497
|
+
},
|
|
1498
|
+
"branch": {
|
|
1499
|
+
"type": "string",
|
|
1500
|
+
"description": "Override for the branch rule (develop when the remote has it, else its default branch)."
|
|
1501
|
+
},
|
|
1502
|
+
"credential": {
|
|
1503
|
+
"type": "string",
|
|
1504
|
+
"description": "Logical credential-store key of a read token for this host, accepted only when global.credentialHosts already binds the key to this https host. Omit to use git's own credential helper or SSH agent."
|
|
1505
|
+
},
|
|
1506
|
+
"authScheme": {
|
|
1507
|
+
"enum": ["basic", "bearer"],
|
|
1508
|
+
"description": "basic for GitHub, bearer for Bitbucket Data Center HTTP access tokens."
|
|
1509
|
+
},
|
|
1510
|
+
"username": {
|
|
1511
|
+
"type": "string",
|
|
1512
|
+
"description": "Username for basic auth. Default x-access-token."
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1457
1520
|
"codeGraph": {
|
|
1458
1521
|
"type": "object",
|
|
1459
1522
|
"additionalProperties": false,
|
|
@@ -2329,6 +2392,10 @@
|
|
|
2329
2392
|
}
|
|
2330
2393
|
}
|
|
2331
2394
|
},
|
|
2395
|
+
"product": {
|
|
2396
|
+
"type": "string",
|
|
2397
|
+
"description": "Name of the global.products entry this project belongs to. Planning reads every repository of that product except the ones the run develops. Written by product-context.mjs learn."
|
|
2398
|
+
},
|
|
2332
2399
|
"counterpartRoots": {
|
|
2333
2400
|
"type": "array",
|
|
2334
2401
|
"items": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/route-config.schema.json",
|
|
4
4
|
"title": "Model routing configuration",
|
|
5
|
-
"description": "Policy-driven model selection, OFF by default. Lives under prefs.global.modelRouting. Two layers, and the difference is the whole safety argument. Layer 1 chooses a rung INSIDE the Anthropic ladder and writes to the existing PHASE_MODEL_OVERRIDE seam - no new network path, no new account. Layer 2
|
|
5
|
+
"description": "Policy-driven model selection, OFF by default. Lives under prefs.global.modelRouting. Two layers, and the difference is the whole safety argument. Layer 1 chooses a rung INSIDE the Anthropic ladder and writes to the existing PHASE_MODEL_OVERRIDE seam - no new network path, no new account. Layer 2 is the `free` rung: a simple text task (a bulk read, a summary, an extraction, a label, a translation draft) goes to the multi-agent-toolkit's verified free-model chain (llm_delegate / multi-agent-toolkit-llm), whose every answer is checked against its source and discarded when it does not hold, with the Anthropic default taking over. It is reachable only where the pipeline makes the call itself (`bulk-read`, `delegate`); a subagent is dispatched by the host, so model-dispatch.sh refuses `free` and every other non-Anthropic rung at the subagent site and falls to the rule's next rung. `scope` has no `host-session` member and that absence is enforced here rather than written in prose: rewriting the host's base URL would route the user's ENTIRE session, including work that has nothing to do with this pipeline, through a third layer - breaking the subscription's auth model and silently changing which model answers. Shipping this disabled is the same reasoning as modelFallback.fableEnabled: a cost control that is on by default is not a control.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": ["enabled"],
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"type": "string",
|
|
17
17
|
"enum": ["manual", "task-fit", "cost-ceiling"],
|
|
18
18
|
"default": "manual",
|
|
19
|
-
"description": "manual: only the explicit rules[] apply, nothing is inferred. task-fit:
|
|
19
|
+
"description": "manual: only the explicit rules[] apply, nothing is inferred. task-fit: the matched rule's rungs are tried cheapest first (cost-table.json prices, free first). cost-ceiling: rules apply in order until the run reaches the warn threshold of budgetCeilingUsd, then the cheapest rung is tried first."
|
|
20
20
|
},
|
|
21
21
|
"scope": {
|
|
22
22
|
"type": "array",
|
|
23
23
|
"default": ["subagent"],
|
|
24
24
|
"uniqueItems": true,
|
|
25
|
-
"description": "Where routing is allowed to act. Every member names a call site the pipeline itself owns. There is deliberately no `host-session` member - see the title description.",
|
|
25
|
+
"description": "Where routing is allowed to act. Every member names a call site the pipeline itself owns: subagent (persona dispatch, Anthropic rungs only), bulk-read (bulk-read.sh), delegate (simple text tasks the skills hand to llm_delegate), research. There is deliberately no `host-session` member - see the title description.",
|
|
26
26
|
"items": {
|
|
27
27
|
"type": "string",
|
|
28
|
-
"enum": ["subagent", "bulk-read", "research"]
|
|
28
|
+
"enum": ["subagent", "bulk-read", "delegate", "research"]
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"rules": {
|
|
@@ -42,8 +42,14 @@
|
|
|
42
42
|
"additionalProperties": false,
|
|
43
43
|
"minProperties": 1,
|
|
44
44
|
"properties": {
|
|
45
|
-
"persona": {
|
|
46
|
-
|
|
45
|
+
"persona": {
|
|
46
|
+
"type": "string"
|
|
47
|
+
},
|
|
48
|
+
"phase": {
|
|
49
|
+
"type": "integer",
|
|
50
|
+
"minimum": 0,
|
|
51
|
+
"maximum": 5
|
|
52
|
+
},
|
|
47
53
|
"taskKind": {
|
|
48
54
|
"type": "string",
|
|
49
55
|
"enum": ["bugfix", "feature", "refactor", "chore", "component"]
|
|
@@ -54,16 +60,35 @@
|
|
|
54
60
|
"type": "array",
|
|
55
61
|
"minItems": 1,
|
|
56
62
|
"description": "Rungs in descending preference. Rung NAMES are the contract; model ids are not - they live in cost-table.json and change without a config edit.",
|
|
57
|
-
"items": {
|
|
63
|
+
"items": {
|
|
64
|
+
"type": "string"
|
|
65
|
+
}
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
},
|
|
70
|
+
"free": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"additionalProperties": false,
|
|
73
|
+
"description": "Layer 2. With enabled:true, the bulk-read and delegate call sites try the free rung before the rule's Anthropic rungs. Written by route-on --free.",
|
|
74
|
+
"properties": {
|
|
75
|
+
"enabled": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"default": false,
|
|
78
|
+
"description": "Try the verified free-model chain first at bulk-read and delegate. Ships false."
|
|
79
|
+
},
|
|
80
|
+
"allowTraining": {
|
|
81
|
+
"type": "boolean",
|
|
82
|
+
"default": false,
|
|
83
|
+
"description": "Also use free tiers that may train on prompts (Mistral, Gemini) or whose data use is unknown. false keeps text to providers that do not (Groq) and a local Ollama."
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
62
87
|
"budgetCeilingUsd": {
|
|
63
88
|
"type": ["number", "null"],
|
|
64
89
|
"default": null,
|
|
65
90
|
"minimum": 0,
|
|
66
|
-
"description": "Only consulted by the cost-ceiling strategy. null means no ceiling, which is not the same as a ceiling of 0."
|
|
91
|
+
"description": "Only consulted by the cost-ceiling strategy, through cost-budget-check.mjs against the run's tracker. null means no ceiling, which is not the same as a ceiling of 0."
|
|
67
92
|
},
|
|
68
93
|
"recordDecisions": {
|
|
69
94
|
"type": "boolean",
|
|
@@ -78,6 +78,20 @@
|
|
|
78
78
|
"len": 36,
|
|
79
79
|
"suffix": ""
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
"id": "groq",
|
|
83
|
+
"prefix": "gsk_",
|
|
84
|
+
"fill": "A",
|
|
85
|
+
"len": 52,
|
|
86
|
+
"suffix": ""
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "openrouter",
|
|
90
|
+
"prefix": "sk-or-v1-",
|
|
91
|
+
"fill": "a",
|
|
92
|
+
"len": 64,
|
|
93
|
+
"suffix": ""
|
|
94
|
+
},
|
|
81
95
|
{
|
|
82
96
|
"id": "huggingface",
|
|
83
97
|
"prefix": "hf_",
|
|
@@ -35,7 +35,7 @@ import { isExcludedBy } from "./_path-match.mjs";
|
|
|
35
35
|
|
|
36
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
37
|
|
|
38
|
-
export const GRAPH_SCHEMA_VERSION = "1.
|
|
38
|
+
export const GRAPH_SCHEMA_VERSION = "1.1.0";
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Stack words the pipeline uses elsewhere, mapped to the rule-file id.
|
|
@@ -256,6 +256,74 @@ function indentOf(text, index) {
|
|
|
256
256
|
return i - start;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
const leading = (line) => line.length - line.replace(/^[ \t]+/, "").length;
|
|
260
|
+
const ATTRIBUTE_RE = /^[ \t]*@/;
|
|
261
|
+
const CLOSER_RE = /^[ \t]*[}\])]/;
|
|
262
|
+
const OPENS_BLOCK_RE = /(?:[{:=]|=>|->)[ \t]*$/;
|
|
263
|
+
const DIRECTIVE_RE = /^#(?:if|elseif|else|endif)\b/;
|
|
264
|
+
const STORED_PROPERTY_RE = /\b(?:var|let|val)\b/;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Give every definition a span: `startLine` (the declaration line, or the first
|
|
268
|
+
* of the attribute or decorator lines directly above it, never a stored
|
|
269
|
+
* property) and `endLine` (the last line indented deeper than the declaration,
|
|
270
|
+
* plus a closing bracket at the declaration's own indentation when one
|
|
271
|
+
* follows). A closing bracket that opens the body again - the end of a
|
|
272
|
+
* multi-line parameter list, `) -> Int {`, `):`, `) : Base() {` - continues the
|
|
273
|
+
* span, and compiler directives in column 0 (`#if`, `#endif`) are skipped like
|
|
274
|
+
* blank lines. Code after the span, at the same or a lower indentation,
|
|
275
|
+
* belongs to no declaration until the next one starts.
|
|
276
|
+
*
|
|
277
|
+
* Indentation is the nesting signal the engine already trusts (see indentOf),
|
|
278
|
+
* so this needs no parser and holds for Python as well as the brace languages.
|
|
279
|
+
*
|
|
280
|
+
* @param {Array<{line: number, indent: number}>} definitions - mutated in place
|
|
281
|
+
* @param {string[]} lines - the stripped body, one entry per source line
|
|
282
|
+
*/
|
|
283
|
+
export function assignSpans(definitions, lines) {
|
|
284
|
+
for (const d of definitions) {
|
|
285
|
+
let start = d.line;
|
|
286
|
+
while (start > 1) {
|
|
287
|
+
const above = lines[start - 2];
|
|
288
|
+
if (
|
|
289
|
+
!ATTRIBUTE_RE.test(above) ||
|
|
290
|
+
leading(above) !== d.indent ||
|
|
291
|
+
STORED_PROPERTY_RE.test(above)
|
|
292
|
+
)
|
|
293
|
+
break;
|
|
294
|
+
start--;
|
|
295
|
+
}
|
|
296
|
+
d.startLine = start;
|
|
297
|
+
}
|
|
298
|
+
const ordered = [...definitions].sort((a, b) => a.line - b.line || a.indent - b.indent);
|
|
299
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
300
|
+
const d = ordered[i];
|
|
301
|
+
let limit = lines.length;
|
|
302
|
+
for (let j = i + 1; j < ordered.length; j++) {
|
|
303
|
+
if (ordered[j].line > d.line && ordered[j].indent <= d.indent) {
|
|
304
|
+
limit = ordered[j].startLine - 1;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
let end = d.line;
|
|
309
|
+
for (let n = d.line + 1; n <= limit; n++) {
|
|
310
|
+
const text = lines[n - 1];
|
|
311
|
+
if (!text.trim() || DIRECTIVE_RE.test(text)) continue;
|
|
312
|
+
const ind = leading(text);
|
|
313
|
+
if (ind > d.indent) {
|
|
314
|
+
end = n;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (ind === d.indent && CLOSER_RE.test(text)) {
|
|
318
|
+
end = n;
|
|
319
|
+
if (OPENS_BLOCK_RE.test(text)) continue;
|
|
320
|
+
}
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
d.endLine = end;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
259
327
|
/**
|
|
260
328
|
* Extract one file's definitions, imports and identifier set.
|
|
261
329
|
*
|
|
@@ -303,11 +371,14 @@ export function extractFile(text, rules) {
|
|
|
303
371
|
kind: pattern.kind || pattern.id,
|
|
304
372
|
patternId: pattern.id,
|
|
305
373
|
line: lineOf(body, m.index),
|
|
374
|
+
indent: indentOf(body, m.index),
|
|
306
375
|
nested: indentOf(body, m.index) > 0,
|
|
307
376
|
});
|
|
308
377
|
}
|
|
309
378
|
}
|
|
310
379
|
|
|
380
|
+
assignSpans(definitions, body.split("\n"));
|
|
381
|
+
for (const d of definitions) delete d.indent;
|
|
311
382
|
return { definitions, imports, tokens: identifiers(body) };
|
|
312
383
|
}
|
|
313
384
|
|
|
@@ -325,11 +396,11 @@ export function extractFile(text, rules) {
|
|
|
325
396
|
export function listSourceFiles(root, rules) {
|
|
326
397
|
let candidates;
|
|
327
398
|
try {
|
|
328
|
-
const out = execFileSync("git", ["-C", root, "ls-files", "-co", "--exclude-standard"], {
|
|
399
|
+
const out = execFileSync("git", ["-C", root, "ls-files", "-z", "-co", "--exclude-standard"], {
|
|
329
400
|
encoding: "utf8",
|
|
330
401
|
maxBuffer: 64 * 1024 * 1024,
|
|
331
402
|
});
|
|
332
|
-
candidates = out.split("\
|
|
403
|
+
candidates = out.split("\0").filter(Boolean);
|
|
333
404
|
} catch {
|
|
334
405
|
candidates = walkDir(root, root);
|
|
335
406
|
}
|
|
@@ -427,6 +498,8 @@ export function buildGraph({
|
|
|
427
498
|
name: def.name,
|
|
428
499
|
path,
|
|
429
500
|
line: def.line,
|
|
501
|
+
...(def.endLine ? { endLine: def.endLine } : {}),
|
|
502
|
+
...(def.startLine && def.startLine !== def.line ? { startLine: def.startLine } : {}),
|
|
430
503
|
symbolKind: def.kind,
|
|
431
504
|
isTest: isTestPath(path, rules),
|
|
432
505
|
nested: def.nested === true,
|