@mmerterden/multi-agent-pipeline 20.23.2 → 20.25.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 +46 -0
- package/README.md +8 -6
- package/README.tr.md +7 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +7 -6
- package/docs/features.md +9 -4
- package/install/catalog-history.json +1 -1
- package/install/templates/claude-hooks.json +14 -1
- package/install/templates/codex-hooks.json +18 -0
- package/manifest.json +126 -98
- package/package.json +1 -1
- package/pipeline/agents/task-clarifier.md +1 -1
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/deps/SKILL.md +61 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/postmortem/SKILL.md +90 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/route-on/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/sync/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +2 -3
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/lib/analysis-jira-write.sh +70 -0
- package/pipeline/lib/credential-inventory.sh +5 -5
- package/pipeline/lib/decisions.mjs +130 -0
- package/pipeline/lib/fetch-crashlytics.sh +6 -8
- package/pipeline/lib/model-rung.sh +1 -1
- package/pipeline/lib/plan-todos.sh +177 -37
- package/pipeline/lib/post-pr-review.sh +4 -6
- package/pipeline/lib/route-state.sh +1 -1
- package/pipeline/lib/shadow-git.sh +4 -5
- package/pipeline/lib/task-graph.mjs +446 -0
- package/pipeline/lib/tdd-evidence.mjs +579 -0
- package/pipeline/multi-agent-refs/analysis-template-corporate.md +2 -0
- package/pipeline/multi-agent-refs/analysis-template.md +4 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -11
- package/pipeline/multi-agent-refs/features/analysis-jira.md +42 -0
- package/pipeline/multi-agent-refs/features/autopilot-decisions.md +34 -0
- package/pipeline/multi-agent-refs/features/cost-analysis.md +4 -0
- package/pipeline/multi-agent-refs/features/dep-bump.md +176 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +21 -14
- package/pipeline/multi-agent-refs/features/mutation-probe.md +58 -0
- package/pipeline/multi-agent-refs/features/plan-checks.md +67 -0
- package/pipeline/multi-agent-refs/features/plan-todos.md +40 -1
- package/pipeline/multi-agent-refs/features/rework-escalation.md +49 -0
- package/pipeline/multi-agent-refs/features/scope-check.md +1 -0
- package/pipeline/multi-agent-refs/help/en.md +2 -0
- package/pipeline/multi-agent-refs/help/tr.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +2 -3
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +18 -16
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +3 -3
- package/pipeline/multi-agent-refs/picker-contract.md +3 -2
- package/pipeline/multi-agent-refs/response-shape.md +65 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +6 -6
- package/pipeline/rules/response-shape.md +18 -0
- package/pipeline/schemas/agent-state.schema.json +36 -1
- package/pipeline/schemas/phases.json +4 -0
- package/pipeline/schemas/plan-todos.schema.json +104 -3
- package/pipeline/schemas/planning-output.schema.json +46 -0
- package/pipeline/schemas/prefs.schema.json +122 -3
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_jsonl.mjs +151 -0
- package/pipeline/scripts/_plan-overlap.mjs +231 -0
- package/pipeline/scripts/_redact.mjs +124 -10
- package/pipeline/scripts/_transcript-hosts.mjs +406 -0
- package/pipeline/scripts/analysis-story-tree.mjs +153 -8
- package/pipeline/scripts/autopilot-publish.mjs +12 -1
- package/pipeline/scripts/build-stack-plugins.mjs +3 -3
- package/pipeline/scripts/capture-flush.sh +7 -7
- package/pipeline/scripts/code-graph-rules/android.json +1 -1
- package/pipeline/scripts/cost-analyze.mjs +32 -43
- package/pipeline/scripts/cost-table.json +35 -19
- package/pipeline/scripts/decisions-log.mjs +118 -0
- package/pipeline/scripts/dep-bump.mjs +1556 -0
- package/pipeline/scripts/edit-syntax-gate.mjs +362 -0
- package/pipeline/scripts/edit-syntax-gate.sh +32 -0
- package/pipeline/scripts/find-polluter.mjs +281 -0
- package/pipeline/scripts/gate-ledger.mjs +46 -13
- package/pipeline/scripts/gc-abandoned.sh +2 -2
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/git-path.mjs +1 -2
- package/pipeline/scripts/host-hooks.mjs +265 -0
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain.py +3 -3
- package/pipeline/scripts/mutation-probe.mjs +484 -0
- package/pipeline/scripts/output-quality-check.sh +1 -1
- package/pipeline/scripts/phase-tracker.sh +5 -6
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -3
- package/pipeline/scripts/plan-complexity.mjs +259 -0
- package/pipeline/scripts/pre-push-check.sh +3 -6
- package/pipeline/scripts/review-decision-gate.mjs +33 -1
- package/pipeline/scripts/rework-escalation.mjs +264 -0
- package/pipeline/scripts/test-strength.mjs +5 -3
- package/pipeline/scripts/transcript-index.mjs +1131 -0
- package/pipeline/scripts/uninstall.mjs +90 -4
- package/pipeline/scripts/update-issue-progress.sh +4 -6
- package/pipeline/scripts/usage-report.mjs +2 -2
- package/pipeline/scripts/validate-planning.mjs +48 -6
- package/pipeline/skills/.skill-manifest.json +20 -12
- package/pipeline/skills/.skills-index.json +23 -1
- package/pipeline/skills/shared/README.md +5 -3
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-deps/SKILL.md +60 -0
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-postmortem/SKILL.md +69 -0
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -3
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +15 -0
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +13 -0
- package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +8 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/skills-index.md +3 -1
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
#
|
|
6
6
|
# WHY THIS EXISTS
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# established, and the next run on the same repo
|
|
8
|
+
# The persistent writes are triage ingest, the learnings ledger distill, the
|
|
9
|
+
# knowledge-base append and the code-graph refresh. Phase 5 is, per
|
|
10
|
+
# features/code-graph.md, the phase a run is LEAST likely to reach. A run killed
|
|
11
|
+
# in Phase 3, a session that hits its context ceiling, a crash after review -
|
|
12
|
+
# if the writes waited for Phase 5, each would throw away everything it had
|
|
13
|
+
# established, and the next run on the same repo would rediscover it.
|
|
14
14
|
#
|
|
15
|
-
# So the writes
|
|
15
|
+
# So the writes happen here, and Phase 5 is the LAST flush rather than the
|
|
16
16
|
# only one. Phase boundaries call this, and so does SessionEnd. Nothing about
|
|
17
17
|
# the trigger depends on a model noticing that a moment qualifies: it hangs on
|
|
18
18
|
# a phase transition and on process exit, both objectively visible without any
|
|
@@ -110,5 +110,5 @@
|
|
|
110
110
|
"Unit",
|
|
111
111
|
"Any"
|
|
112
112
|
],
|
|
113
|
-
"note": "Kotlin declaration modifiers are not separate patterns: `data class Foo`, `sealed class Foo` and `annotation class Foo` all still contain `class Foo`, so one pattern covers them. `enum` allows an optional `class` between keyword and name, which is the only shape that differs between Java (`enum Foo`) and Kotlin (`enum class Foo`). `object` matches the named form only - `companion object {` and the anonymous `object : Iface {` carry no name and correctly produce nothing. Imports are fully qualified on the JVM, so the pattern captures the final segment: `import com.example.ui.Button` resolves against the file named Button.kt, which is how the engine links a file-level import edge. A star import (`import com.example.ui.*`) captures nothing, deliberately - it names a package, and pointing every star import at an arbitrary file in that package would invent an edge nobody wrote. Pattern ORDER is load-bearing: `enum class Cabin` matches both the enum pattern and the class pattern, and the first match to register the name decides its kind, so the more specific patterns are listed first. With class first, every Kotlin enum
|
|
113
|
+
"note": "Kotlin declaration modifiers are not separate patterns: `data class Foo`, `sealed class Foo` and `annotation class Foo` all still contain `class Foo`, so one pattern covers them. `enum` allows an optional `class` between keyword and name, which is the only shape that differs between Java (`enum Foo`) and Kotlin (`enum class Foo`). `object` matches the named form only - `companion object {` and the anonymous `object : Iface {` carry no name and correctly produce nothing. Imports are fully qualified on the JVM, so the pattern captures the final segment: `import com.example.ui.Button` resolves against the file named Button.kt, which is how the engine links a file-level import edge. A star import (`import com.example.ui.*`) captures nothing, deliberately - it names a package, and pointing every star import at an arbitrary file in that package would invent an edge nobody wrote. Pattern ORDER is load-bearing: `enum class Cabin` matches both the enum pattern and the class pattern, and the first match to register the name decides its kind, so the more specific patterns are listed first. With class first, every Kotlin enum would be filed as a class and the report's symbol-kind census would be wrong."
|
|
114
114
|
}
|
|
@@ -46,20 +46,12 @@
|
|
|
46
46
|
* 2 usage error
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
|
-
import {
|
|
50
|
-
createReadStream,
|
|
51
|
-
existsSync,
|
|
52
|
-
mkdirSync,
|
|
53
|
-
readdirSync,
|
|
54
|
-
readFileSync,
|
|
55
|
-
statSync,
|
|
56
|
-
writeFileSync,
|
|
57
|
-
} from "node:fs";
|
|
58
|
-
import { createInterface } from "node:readline";
|
|
49
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
59
50
|
import { homedir } from "node:os";
|
|
60
51
|
import { dirname, join } from "node:path";
|
|
61
52
|
import { fileURLToPath } from "node:url";
|
|
62
53
|
import { costUsd } from "./_cost.mjs";
|
|
54
|
+
import { readJsonl } from "./_jsonl.mjs";
|
|
63
55
|
import { runMain } from "../lib/fatal.mjs";
|
|
64
56
|
import { messages, outputLanguage } from "../lib/user-facing.mjs";
|
|
65
57
|
|
|
@@ -176,30 +168,37 @@ const asNumber = (v, fallback) => {
|
|
|
176
168
|
const TABLE = JSON.parse(readFileSync(join(HERE, "cost-table.json"), "utf-8"));
|
|
177
169
|
|
|
178
170
|
/**
|
|
179
|
-
*
|
|
180
|
-
* price table is keyed by rung
|
|
181
|
-
*
|
|
182
|
-
*
|
|
171
|
+
* Wire ID -> rate. Transcripts carry the wire value ("claude-opus-5-5"); the
|
|
172
|
+
* price table is keyed by rung, so each rung's `modelId` and `copilotModelId`
|
|
173
|
+
* map to its rate, `legacy` adds previous-generation IDs at their own rates,
|
|
174
|
+
* and `models` overrides the rung rate for a dispatched ID whose model is not
|
|
175
|
+
* the rung's (Copilot CLI's older models bill at their own rate). Matching on
|
|
176
|
+
* the ID rather than the rung name keeps a generation move a data edit, which
|
|
177
|
+
* is the contract cost-table.json states for itself.
|
|
183
178
|
*/
|
|
184
|
-
const
|
|
185
|
-
Object.entries(TABLE.
|
|
186
|
-
)
|
|
179
|
+
const RATE_BY_MODEL_ID = new Map([
|
|
180
|
+
...Object.entries(TABLE.legacy || {}),
|
|
181
|
+
...Object.values(TABLE.prices || {}).flatMap((p) =>
|
|
182
|
+
[p.modelId, p.copilotModelId].filter(Boolean).map((id) => [id, p]),
|
|
183
|
+
),
|
|
184
|
+
...Object.entries(TABLE.models || {}),
|
|
185
|
+
]);
|
|
186
|
+
const IDS_LONGEST_FIRST = [...RATE_BY_MODEL_ID.keys()].sort((a, b) => b.length - a.length);
|
|
187
187
|
|
|
188
188
|
function rateFor(modelId) {
|
|
189
189
|
if (!modelId) return null;
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
// A dated snapshot id (
|
|
193
|
-
//
|
|
190
|
+
const exact = RATE_BY_MODEL_ID.get(modelId);
|
|
191
|
+
if (exact) return exact;
|
|
192
|
+
// A dated snapshot id (a known id, a hyphen, a release date) prices as that
|
|
193
|
+
// id; the longest known id wins, so a current id is never read as a shorter
|
|
194
|
+
// legacy one. Written as a shape rather than as a literal, because
|
|
194
195
|
// smoke-model-fallback.sh reads every model id in this tree and requires it
|
|
195
|
-
// to exist in cost-table.json
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
196
|
+
// to exist in cost-table.json. An id nobody knows prices as nothing and is
|
|
197
|
+
// COUNTED as unpriced rather than folded in at zero.
|
|
198
|
+
const id = IDS_LONGEST_FIRST.find(
|
|
199
|
+
(known) => modelId.startsWith(`${known}-`) || modelId.startsWith(`${known}[`),
|
|
200
|
+
);
|
|
201
|
+
return id ? RATE_BY_MODEL_ID.get(id) : null;
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
/**
|
|
@@ -263,20 +262,10 @@ async function readSeries({ days }) {
|
|
|
263
262
|
const unpricedModels = new Set();
|
|
264
263
|
|
|
265
264
|
for (const f of files) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
for await (const line of rl) {
|
|
273
|
-
if (!line || line.charCodeAt(0) !== 123) continue;
|
|
274
|
-
let d;
|
|
275
|
-
try {
|
|
276
|
-
d = JSON.parse(line);
|
|
277
|
-
} catch {
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
265
|
+
// Uncapped: a cost total that silently stops at a byte cap under-reports.
|
|
266
|
+
for await (const row of readJsonl(f.path, { maxBytes: Infinity, maxLineBytes: Infinity })) {
|
|
267
|
+
const d = row.record;
|
|
268
|
+
if (!d) continue;
|
|
280
269
|
const msg = d.message;
|
|
281
270
|
const u = msg?.usage;
|
|
282
271
|
if (!u) continue;
|
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_readme": "Per-model unit prices in USD per million tokens. Source: Anthropic public pricing (Claude-family rows verified 2026-
|
|
2
|
+
"_readme": "Per-model unit prices in USD per million tokens. Source: Anthropic public pricing (Claude-family rows verified 2026-10-14 against the current model table; the gpt-* rows remain approximate). Update when Anthropic publishes new tiers. Rung names (fable / opus / sonnet / haiku) are the pipeline's stable identifiers and `modelId` is the wire value they currently resolve to - dispatch reads the rung, so a generation move is a `modelId` edit here plus the phase specs, never a rename of the rungs. `copilotModelId`, where present, is the name Copilot CLI dispatches for the rung: one of the dotted IDs `copilot help config` lists (Copilot rejects any other as not available), the newest of the rung's family there. A rung without one (fable) is not offered on Copilot CLI, where its personas take the opus rung. smoke-model-fallback.sh checks the IDs against `copilot help config` when the CLI is installed. `provider` says which layer a rung belongs to: an `anthropic` rung is reachable from every call site; `free-chain` (the `free` rung) only from bulk-read and delegate, where the multi-agent-toolkit's verified free-model chain answers and the caller's default takes over when it escalates; any other provider from no call site. model-dispatch.sh enforces this. Unknown models render USD as ' - ' and emit a footnote - never block PR-body generation. cacheReadPerMtok is the discounted rate for prompt-cache hits, published per model and well below inPerMtok; the renderer prices a phase's tokens_cached at this rate when the tracker records it, so resume/cache reuse is visible in the ledger.",
|
|
3
3
|
"schemaVersion": "1.1.0",
|
|
4
|
+
"_legacyReadme": "`legacy` prices previous-generation wire IDs a transcript may still name, keyed by the exact ID (a dated snapshot of one prices as it). Dispatch never reads it; cost-analyze consults it after the current `modelId` values and the `models` map. Official Anthropic prices per million tokens.",
|
|
5
|
+
"legacy": {
|
|
6
|
+
"claude-fable-5": { "inPerMtok": 10.0, "outPerMtok": 50.0, "cacheReadPerMtok": 1.0 },
|
|
7
|
+
"claude-opus-5": { "inPerMtok": 5.0, "outPerMtok": 25.0, "cacheReadPerMtok": 0.5 },
|
|
8
|
+
"claude-sonnet-5": { "inPerMtok": 2.0, "outPerMtok": 10.0, "cacheReadPerMtok": 0.2 },
|
|
9
|
+
"claude-haiku-4-5": { "inPerMtok": 1.0, "outPerMtok": 5.0, "cacheReadPerMtok": 0.1 }
|
|
10
|
+
},
|
|
11
|
+
"_modelsReadme": "`models` prices wire IDs a rung dispatches whose model is not the rung's `modelId`: Copilot CLI's `copilotModelId` values name an older model than the rung's, billed at that model's own rate. Keyed by the exact ID; cost-analyze consults it before the rung rates, so a Copilot CLI session is priced at its own model. Official Anthropic prices per million tokens, verified 2026-10-14.",
|
|
12
|
+
"models": {
|
|
13
|
+
"claude-opus-4.7": { "inPerMtok": 5.0, "outPerMtok": 25.0, "cacheReadPerMtok": 0.5 },
|
|
14
|
+
"claude-sonnet-4.6": { "inPerMtok": 3.0, "outPerMtok": 15.0, "cacheReadPerMtok": 0.3 },
|
|
15
|
+
"claude-haiku-4.5": { "inPerMtok": 1.0, "outPerMtok": 5.0, "cacheReadPerMtok": 0.1 }
|
|
16
|
+
},
|
|
4
17
|
"prices": {
|
|
5
18
|
"fable": {
|
|
6
19
|
"inPerMtok": 10.0,
|
|
7
20
|
"outPerMtok": 50.0,
|
|
8
|
-
"cacheReadPerMtok":
|
|
9
|
-
"modelId": "claude-fable-5",
|
|
21
|
+
"cacheReadPerMtok": 0.25,
|
|
22
|
+
"modelId": "claude-fable-5-1",
|
|
10
23
|
"provider": "anthropic",
|
|
11
|
-
"note": "Top tier
|
|
24
|
+
"note": "Top tier - architects, Reviewer 1 and triage on Claude Code. Claude Fable 5.1, verified against Anthropic pricing 2026-10-14."
|
|
12
25
|
},
|
|
13
26
|
"opus": {
|
|
14
|
-
"inPerMtok":
|
|
15
|
-
"outPerMtok":
|
|
16
|
-
"cacheReadPerMtok": 0.
|
|
17
|
-
"modelId": "claude-opus-5",
|
|
27
|
+
"inPerMtok": 4.0,
|
|
28
|
+
"outPerMtok": 20.0,
|
|
29
|
+
"cacheReadPerMtok": 0.2,
|
|
30
|
+
"modelId": "claude-opus-5-5",
|
|
31
|
+
"copilotModelId": "claude-opus-4.7",
|
|
18
32
|
"provider": "anthropic",
|
|
19
|
-
"note": "Second tier - Reviewer 1 and triage on Copilot CLI, and the opus rung of the fable -> opus -> sonnet fallback ladder.
|
|
33
|
+
"note": "Second tier - Reviewer 2 on Claude Code, Reviewer 1 and triage on Copilot CLI, and the opus rung of the fable -> opus -> sonnet fallback ladder. Claude Opus 5.5, verified against Anthropic pricing 2026-10-14."
|
|
20
34
|
},
|
|
21
35
|
"sonnet": {
|
|
22
|
-
"inPerMtok":
|
|
23
|
-
"outPerMtok":
|
|
24
|
-
"cacheReadPerMtok": 0.
|
|
25
|
-
"modelId": "claude-sonnet-5",
|
|
36
|
+
"inPerMtok": 2.0,
|
|
37
|
+
"outPerMtok": 10.0,
|
|
38
|
+
"cacheReadPerMtok": 0.1,
|
|
39
|
+
"modelId": "claude-sonnet-5-5",
|
|
40
|
+
"copilotModelId": "claude-sonnet-4.6",
|
|
26
41
|
"provider": "anthropic",
|
|
27
|
-
"note": "Floor tier for Claude-family dispatch - Reviewer 3 on both hosts, and the terminal rung of the fallback ladder.
|
|
42
|
+
"note": "Floor tier for Claude-family dispatch - Reviewer 3 on both hosts, the dev phase, and the terminal rung of the fallback ladder. Claude Sonnet 5.5, verified against Anthropic pricing 2026-10-14."
|
|
28
43
|
},
|
|
29
44
|
"haiku": {
|
|
30
|
-
"inPerMtok":
|
|
31
|
-
"outPerMtok": 5
|
|
32
|
-
"cacheReadPerMtok": 0.
|
|
33
|
-
"modelId": "claude-haiku-
|
|
45
|
+
"inPerMtok": 0.5,
|
|
46
|
+
"outPerMtok": 2.5,
|
|
47
|
+
"cacheReadPerMtok": 0.05,
|
|
48
|
+
"modelId": "claude-haiku-5-5",
|
|
49
|
+
"copilotModelId": "claude-haiku-4.5",
|
|
34
50
|
"provider": "anthropic",
|
|
35
|
-
"note": "Speed tier - task-clarifier and other latency-sensitive dispatches, plus the terminal rung of the fallback ladder.
|
|
51
|
+
"note": "Speed tier - task-clarifier and other latency-sensitive dispatches, plus the terminal rung of the fallback ladder. Claude Haiku 5.5 is priced in two tiers by prompt length (in 0.10, cache-read 0.01, out 0.50 up to 100k input tokens; in 0.50, cache-read 0.05, out 2.50 above); this table holds one rate per rung, so it carries the over-100k tier: over-reporting is the safe direction for a cost ledger. Verified against Anthropic pricing 2026-10-14."
|
|
36
52
|
},
|
|
37
53
|
"gpt-5.4": {
|
|
38
54
|
"inPerMtok": 10.0,
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* decisions-log.mjs - append to and render `state.decisions[]`, the list of
|
|
4
|
+
* choices an autopilot run made where an attended run would have asked.
|
|
5
|
+
*
|
|
6
|
+
* Writers: a picker resolved without asking (picker-contract.md, autopilot),
|
|
7
|
+
* a split consensus taken on the triage verdict, a model fallback taken
|
|
8
|
+
* without the date-gate question, a rework dispatched one rung up
|
|
9
|
+
* (rework-escalation.mjs). gate-ledger.mjs park and review-decision-gate.mjs
|
|
10
|
+
* write their own entries through lib/decisions.mjs.
|
|
11
|
+
*
|
|
12
|
+
* `add` writes only while the quality gates are active (lib/unattended.mjs
|
|
13
|
+
* gatesActive): in an attended run the person answered, so there is nothing to
|
|
14
|
+
* report back. Writes take the write-state lock through gate-ledger.mjs
|
|
15
|
+
* updateState.
|
|
16
|
+
*
|
|
17
|
+
* `render` prints the markdown section Phase 5 puts in its report, redacted,
|
|
18
|
+
* and nothing at all for a run with no entries.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* decisions-log.mjs add --what <text> --why <text> --cost-if-wrong <text> --phase <0-5>
|
|
22
|
+
* [--state <path>] [--repo <dir>]
|
|
23
|
+
* decisions-log.mjs render [--state <path>] [--repo <dir>]
|
|
24
|
+
*
|
|
25
|
+
* Exit: 0 written, skipped (attended) or rendered · 2 usage · 3 no state or I/O error
|
|
26
|
+
*
|
|
27
|
+
* @module pipeline/scripts/decisions-log
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { writeFileSync } from "node:fs";
|
|
31
|
+
|
|
32
|
+
import { decisionEntry, renderDecisions, withDecision } from "../lib/decisions.mjs";
|
|
33
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
34
|
+
import { readRegularJson } from "../lib/regular-file.mjs";
|
|
35
|
+
import { gateContext, resolveStatePath, updateState } from "./gate-ledger.mjs";
|
|
36
|
+
|
|
37
|
+
class UsageError extends Error {}
|
|
38
|
+
|
|
39
|
+
function parse(argv) {
|
|
40
|
+
const opts = {};
|
|
41
|
+
for (let i = 1; i < argv.length; i++) {
|
|
42
|
+
const a = argv[i];
|
|
43
|
+
if (!a.startsWith("--")) throw new UsageError(`unexpected argument ${a}`);
|
|
44
|
+
const v = argv[++i];
|
|
45
|
+
if (v === undefined) throw new UsageError(`${a} needs a value`);
|
|
46
|
+
opts[a.slice(2)] = v;
|
|
47
|
+
}
|
|
48
|
+
return opts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Append one entry when the gates are active. Resolves to whether it wrote.
|
|
53
|
+
*
|
|
54
|
+
* @param {{what: string, why: string, costIfWrong: string, phase: number}} input
|
|
55
|
+
* @param {{statePath?: string|null, repo?: string|null, env?: Record<string,string|undefined>}} [opts]
|
|
56
|
+
*/
|
|
57
|
+
export async function addDecision(
|
|
58
|
+
input,
|
|
59
|
+
{ statePath = null, repo = null, env = process.env } = {},
|
|
60
|
+
) {
|
|
61
|
+
const entry = decisionEntry(input);
|
|
62
|
+
const ctx = gateContext({ statePath, repo, env });
|
|
63
|
+
if (!ctx.active || !ctx.statePath) return { written: false, entry };
|
|
64
|
+
await updateState(ctx.statePath, (s) => withDecision(s, entry));
|
|
65
|
+
return { written: true, entry, statePath: ctx.statePath };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function main(argv) {
|
|
69
|
+
const mode = argv[0];
|
|
70
|
+
const opts = parse(argv);
|
|
71
|
+
if (mode === "add") {
|
|
72
|
+
let input;
|
|
73
|
+
try {
|
|
74
|
+
input = decisionEntry({
|
|
75
|
+
what: opts.what,
|
|
76
|
+
why: opts.why,
|
|
77
|
+
costIfWrong: opts["cost-if-wrong"],
|
|
78
|
+
phase: opts.phase,
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
throw new UsageError(err.message);
|
|
82
|
+
}
|
|
83
|
+
await addDecision(input, { statePath: opts.state || null, repo: opts.repo || process.cwd() });
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
if (mode === "render") {
|
|
87
|
+
const path = resolveStatePath({ statePath: opts.state, repo: opts.repo || process.cwd() });
|
|
88
|
+
if (!path) {
|
|
89
|
+
process.stderr.write("decisions-log: no run state found\n");
|
|
90
|
+
return 3;
|
|
91
|
+
}
|
|
92
|
+
const state = readRegularJson(path, null);
|
|
93
|
+
if (!state || typeof state !== "object") {
|
|
94
|
+
process.stderr.write(`decisions-log: cannot read ${path}\n`);
|
|
95
|
+
return 3;
|
|
96
|
+
}
|
|
97
|
+
const md = renderDecisions(state.decisions);
|
|
98
|
+
if (md) writeFileSync(1, md);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
throw new UsageError("mode must be add or render");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (invokedDirectly(import.meta.url)) {
|
|
105
|
+
main(process.argv.slice(2)).then(
|
|
106
|
+
(code) => process.exit(code),
|
|
107
|
+
(err) => {
|
|
108
|
+
process.stderr.write(`decisions-log: ${err.message}\n`);
|
|
109
|
+
if (err instanceof UsageError) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
"usage: decisions-log.mjs add --what t --why t --cost-if-wrong t --phase n [--state p] | render [--state p]\n",
|
|
112
|
+
);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
process.exit(3);
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
}
|