@smartmemory/compose 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/bin/compose.js +24 -3
- package/lib/agent-string.js +9 -4
- package/lib/build-stream-writer.js +6 -0
- package/lib/build.js +643 -84
- package/lib/consumer-fanout.js +403 -16
- package/lib/experiment-pricing.js +5 -1
- package/lib/flow-state.js +38 -0
- package/lib/gsd.js +95 -48
- package/lib/model-pricing.js +4 -1
- package/lib/output-gate.js +81 -0
- package/lib/pipeline-profiles.js +200 -0
- package/lib/result-normalizer.js +13 -0
- package/lib/stratum-mcp-client.js +4 -4
- package/lib/team-flag.js +1 -1
- package/lib/wave-checkpoint.js +100 -0
- package/package.json +2 -2
- package/presets/team-fable-astra.profiles.json +18 -0
- package/presets/team-fable-astra.stratum.yaml +236 -0
- package/server/model-tiers.js +14 -6
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** Git object/ref plumbing. Never checks out a branch or changes the real index. */
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { withTemporaryIndex, snapshotWorkingTree } from './consumer-fanout.js';
|
|
4
|
+
|
|
5
|
+
export class WaveCheckpointError extends Error {
|
|
6
|
+
constructor(code, message) { super(message); this.name = 'WaveCheckpointError'; this.code = code; }
|
|
7
|
+
}
|
|
8
|
+
const fail = (code, message) => { throw new WaveCheckpointError(code, message); };
|
|
9
|
+
function git(cwd, args, opts = {}) {
|
|
10
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: 'pipe', maxBuffer: 512 * 1024 * 1024, ...opts }).trim();
|
|
11
|
+
}
|
|
12
|
+
function symbolicRef(cwd, ref) {
|
|
13
|
+
try { return git(cwd, ['symbolic-ref', '-q', ref]); }
|
|
14
|
+
catch (error) { if (error.status === 1) return null; throw error; }
|
|
15
|
+
}
|
|
16
|
+
function validateRef(cwd, ref) {
|
|
17
|
+
if (typeof ref !== 'string' || !ref.startsWith('refs/heads/compose/wave/')) fail('WAVE_CHECKPOINT_DIVERGED', 'Expected a Compose wave ref');
|
|
18
|
+
try { git(cwd, ['check-ref-format', ref]); }
|
|
19
|
+
catch { fail('WAVE_CHECKPOINT_DIVERGED', 'Invalid wave ref'); }
|
|
20
|
+
if (symbolicRef(cwd, ref)) fail('WAVE_CHECKPOINT_DIVERGED', 'Symbolic wave refs are not allowed');
|
|
21
|
+
// Include other linked worktrees: updating their checked-out branch moves HEAD too.
|
|
22
|
+
const worktrees = git(cwd, ['worktree', 'list', '--porcelain']);
|
|
23
|
+
if (worktrees.split('\n').includes(`branch ${ref}`)) fail('WAVE_CHECKPOINT_DIVERGED', 'Wave ref is checked out in a worktree');
|
|
24
|
+
}
|
|
25
|
+
export function readCheckpointRef({ cwd, ref }) {
|
|
26
|
+
validateRef(cwd, ref);
|
|
27
|
+
try { return git(cwd, ['show-ref', '--verify', '--hash', ref]); }
|
|
28
|
+
catch (error) { if (error.status === 1 || error.status === 128 && /not a valid ref/.test(error.stderr?.toString())) return null; throw error; }
|
|
29
|
+
}
|
|
30
|
+
export function prepareCheckpoint({ cwd, ref, parentCommit, tree, workingTree, message, commitMetadata }) {
|
|
31
|
+
validateRef(cwd, ref);
|
|
32
|
+
if (typeof parentCommit !== 'string' || !/^[a-f0-9]{40,64}$/.test(parentCommit)) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Parent must be a commit OID');
|
|
33
|
+
const parent = git(cwd, ['rev-parse', '--verify', `${parentCommit}^{commit}`]);
|
|
34
|
+
const capturedTree = tree ?? (workingTree === true ? snapshotWorkingTree(cwd) : workingTree);
|
|
35
|
+
if (!capturedTree) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Checkpoint needs a tree or workingTree:true');
|
|
36
|
+
const treeId = git(cwd, ['rev-parse', '--verify', `${capturedTree}^{tree}`]);
|
|
37
|
+
const metadata = structuredClone(commitMetadata ?? {
|
|
38
|
+
authorName: git(cwd, ['config', 'user.name']), authorEmail: git(cwd, ['config', 'user.email']),
|
|
39
|
+
date: new Date().toISOString(),
|
|
40
|
+
});
|
|
41
|
+
if (!metadata.authorName || !metadata.authorEmail || !Number.isFinite(Date.parse(metadata.date))) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Invalid pinned commit metadata');
|
|
42
|
+
metadata.date = new Date(metadata.date).toISOString();
|
|
43
|
+
if (typeof message !== 'string' || !message) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Checkpoint message is required');
|
|
44
|
+
const env = { ...process.env, GIT_AUTHOR_NAME: metadata.authorName, GIT_COMMITTER_NAME: metadata.authorName,
|
|
45
|
+
GIT_AUTHOR_EMAIL: metadata.authorEmail, GIT_COMMITTER_EMAIL: metadata.authorEmail,
|
|
46
|
+
GIT_AUTHOR_DATE: metadata.date, GIT_COMMITTER_DATE: metadata.date };
|
|
47
|
+
const commit = git(cwd, ['commit-tree', treeId, '-p', parent], { input: message, env });
|
|
48
|
+
return { ref, parentCommit: parent, tree: treeId, commit, message, commitMetadata: metadata };
|
|
49
|
+
}
|
|
50
|
+
export function publishCheckpoint({ cwd, ref, expected, commit }) {
|
|
51
|
+
validateRef(cwd, ref);
|
|
52
|
+
git(cwd, ['cat-file', '-e', `${commit}^{commit}`]);
|
|
53
|
+
const zero = '0'.repeat(git(cwd, ['rev-parse', 'HEAD']).length);
|
|
54
|
+
try { git(cwd, ['update-ref', ref, commit, expected ?? zero]); }
|
|
55
|
+
catch (error) { fail('WAVE_CHECKPOINT_DIVERGED', `Checkpoint compare-and-swap refused: ${error.message}`); }
|
|
56
|
+
return commit;
|
|
57
|
+
}
|
|
58
|
+
export function worktreeBaseFor({ journal, ref }) {
|
|
59
|
+
const wave = journal?.wave;
|
|
60
|
+
if (!wave) return null;
|
|
61
|
+
if (wave.checkpoints.some(checkpoint => checkpoint.state !== 'published')) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Checkpoint publication is pending');
|
|
62
|
+
const expected = wave.checkpoints.at(-1)?.commit ?? null;
|
|
63
|
+
if (ref !== expected) fail('WAVE_CHECKPOINT_DIVERGED', 'Wave ref differs from the published journal tip');
|
|
64
|
+
return expected ?? wave.baseCommit;
|
|
65
|
+
}
|
|
66
|
+
export function squashOntoBase({ cwd, ref, base }) {
|
|
67
|
+
if (git(cwd, ['rev-parse', 'HEAD']) !== base) fail('WAVE_CHECKPOINT_DIVERGED', 'HEAD moved from the pinned base');
|
|
68
|
+
const tip = readCheckpointRef({ cwd, ref });
|
|
69
|
+
if (!tip) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Wave ref is missing');
|
|
70
|
+
return withTemporaryIndex(cwd, env => {
|
|
71
|
+
git(cwd, ['read-tree', base], { env });
|
|
72
|
+
const diff = execFileSync('git', ['diff', '--binary', base, tip, '--'], { cwd, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 });
|
|
73
|
+
if (diff) git(cwd, ['apply', '--cached', '--binary', '-'], { env, input: diff });
|
|
74
|
+
const tree = git(cwd, ['write-tree'], { env });
|
|
75
|
+
if (tree !== git(cwd, ['rev-parse', `${tip}^{tree}`])) fail('WAVE_CHECKPOINT_DIVERGED', 'Squashed tree differs from checkpoint');
|
|
76
|
+
return tree;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export function removeCheckpointRef({ cwd, ref, expected }) {
|
|
80
|
+
validateRef(cwd, ref);
|
|
81
|
+
if (!expected) fail('WAVE_CHECKPOINT_EVIDENCE_MISSING', 'Deletion requires the expected tip');
|
|
82
|
+
git(cwd, ['update-ref', '-d', ref, expected]);
|
|
83
|
+
}
|
|
84
|
+
/** Pure resume-table classification; all supplied decisions are durable ordinal evidence. */
|
|
85
|
+
export function reconcileCheckpoint({ journalEntry: entry, refValue }) {
|
|
86
|
+
if (!entry) return refValue == null ? 'ADMIT_FROM_BASE' : 'WAVE_CHECKPOINT_DIVERGED';
|
|
87
|
+
if (entry.evidenceMissing) return 'WAVE_CHECKPOINT_EVIDENCE_MISSING';
|
|
88
|
+
if (entry.diverged) return 'WAVE_CHECKPOINT_DIVERGED';
|
|
89
|
+
if (!entry.commit) {
|
|
90
|
+
if (refValue !== (entry.previousCommit ?? null)) return 'WAVE_CHECKPOINT_DIVERGED';
|
|
91
|
+
return entry.gateOutcome === 'approve' ? 'PREPARE_AND_PUBLISH' : 'RECOVER_WAITING_GATE';
|
|
92
|
+
}
|
|
93
|
+
if (refValue === entry.commit) {
|
|
94
|
+
if (entry.state === 'prepared') return 'MARK_PUBLISHED';
|
|
95
|
+
return entry.terminal ? 'PRESERVE_PUBLISHED' : 'ALREADY_PUBLISHED';
|
|
96
|
+
}
|
|
97
|
+
if (refValue === entry.parentCommit || refValue == null && entry.waveNumber === 1) return 'REPLAY_AND_PUBLISH';
|
|
98
|
+
if (entry.knownAncestorCommits?.includes(refValue)) return 'RECONCILE_IN_ORDER';
|
|
99
|
+
return 'WAVE_CHECKPOINT_DIVERGED';
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartmemory/compose",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Structured AI dev pipeline: your agent writes the code, Compose makes it prove it. Gated design decisions, enforced postconditions, and independent review from goal to shipped code.",
|
|
5
5
|
"author": "SmartMemory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
|
89
89
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
90
90
|
"@smartmemory/sdk-js": "^1.4.60",
|
|
91
|
-
"@smartmemory/stratum": "^0.5.
|
|
91
|
+
"@smartmemory/stratum": "^0.5.2",
|
|
92
92
|
"@tanstack/react-virtual": "^3.13.23",
|
|
93
93
|
"ajv": "^8.18.0",
|
|
94
94
|
"ajv-formats": "^3.0.1",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plan": "claude:orchestrator:coordinator",
|
|
3
|
+
"execute": { "default": "codex:implementer:critical", "tier_from": "item.tier" },
|
|
4
|
+
"verify": "claude:orchestrator:standard",
|
|
5
|
+
"review": "codex:read-only-reviewer:critical",
|
|
6
|
+
"assess": "claude:orchestrator:coordinator",
|
|
7
|
+
"assess_gate": {
|
|
8
|
+
"decide_from": {
|
|
9
|
+
"step": "assess", "field": "action",
|
|
10
|
+
"approve": ["complete"], "revise": ["repair", "implement"], "kill": ["blocked"]
|
|
11
|
+
},
|
|
12
|
+
"validators": [{ "name": "WaveDecision", "review_step": "review", "tasks_field": "tasks" }]
|
|
13
|
+
},
|
|
14
|
+
"_consumer": {
|
|
15
|
+
"execute": { "ownership": "item.files_owned", "independent": true, "checkpoint_gate": "execute_merge" }
|
|
16
|
+
},
|
|
17
|
+
"_costCeiling": { "input": "cost_ceiling_usd", "default": 150, "gates": ["assess_gate"] }
|
|
18
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# TEAM PRESET: fable-astra
|
|
2
|
+
#
|
|
3
|
+
# Purpose: Fable plans and assesses bounded waves; Astra implements and reviews.
|
|
4
|
+
# Pattern: Plan → parallel execute → merge → verify → fresh review → assess
|
|
5
|
+
# → repair/implement another wave, ship on complete, or kill on blocked.
|
|
6
|
+
# Capability: Per-task Codex tiers; fresh read-only Astra reviewer; Fable decisions.
|
|
7
|
+
# Isolation: worktree, literal file ownership; sequential merge and checkpoints.
|
|
8
|
+
# Use with: compose build <feature-code> --team fable-astra
|
|
9
|
+
# Customize: Copy BOTH team-fable-astra.stratum.yaml AND its .profiles.json
|
|
10
|
+
# sidecar into your project's pipelines/ and edit them together.
|
|
11
|
+
# Ruling: Concurrency is literal 3, customized by copying this preset, rather
|
|
12
|
+
# than an input as designed (TS schema accepts numeric literals only).
|
|
13
|
+
# cost_ceiling_usd remains an optional flow input, default 150 in the
|
|
14
|
+
# sidecar; compose build --cost-ceiling-usd overrides that ceiling.
|
|
15
|
+
# Carry rules: References use ${} only, never when/set/ensure/iterate.until.
|
|
16
|
+
# Every wave consumer must descend from plan, be reset by the
|
|
17
|
+
# assess_gate revise closure, and precede that gate through after.
|
|
18
|
+
# execute_merge retries preserve wave; assess_gate replaces it.
|
|
19
|
+
|
|
20
|
+
version: 1
|
|
21
|
+
|
|
22
|
+
contracts:
|
|
23
|
+
Task:
|
|
24
|
+
id: string
|
|
25
|
+
description: string
|
|
26
|
+
files_owned: string[]
|
|
27
|
+
files_read: string[]
|
|
28
|
+
depends_on: string[]
|
|
29
|
+
tier: critical|standard|fast
|
|
30
|
+
tier_rationale: string
|
|
31
|
+
TaskGraph:
|
|
32
|
+
tasks: Task[]
|
|
33
|
+
Verification:
|
|
34
|
+
commands: string[]
|
|
35
|
+
outcomes: string[]
|
|
36
|
+
TaskResult:
|
|
37
|
+
outcome: string
|
|
38
|
+
summary: string
|
|
39
|
+
files_changed: string[]
|
|
40
|
+
verification: Verification
|
|
41
|
+
VerifyResult:
|
|
42
|
+
tests_pass: boolean
|
|
43
|
+
summary: string
|
|
44
|
+
verification: Verification
|
|
45
|
+
merged_diff: string
|
|
46
|
+
Finding:
|
|
47
|
+
severity: string
|
|
48
|
+
files: string[]
|
|
49
|
+
claim: string
|
|
50
|
+
evidence: string
|
|
51
|
+
ReviewFindings:
|
|
52
|
+
findings: Finding[]
|
|
53
|
+
blocking: boolean
|
|
54
|
+
WaveDecision:
|
|
55
|
+
action: repair|implement|complete|blocked
|
|
56
|
+
tasks: Task[]
|
|
57
|
+
rationale: string
|
|
58
|
+
addressed_findings: Finding[]
|
|
59
|
+
open_findings: Finding[]
|
|
60
|
+
blocking: boolean
|
|
61
|
+
open_count: integer
|
|
62
|
+
ShipResult:
|
|
63
|
+
phase: string
|
|
64
|
+
artifact: string
|
|
65
|
+
outcome: string
|
|
66
|
+
summary: string
|
|
67
|
+
files_changed: string[]?
|
|
68
|
+
commit_hash: string?
|
|
69
|
+
|
|
70
|
+
flows:
|
|
71
|
+
entry: team_fable_astra
|
|
72
|
+
team_fable_astra:
|
|
73
|
+
input:
|
|
74
|
+
featureCode: string
|
|
75
|
+
description: string
|
|
76
|
+
# Accept the existing feature-build envelope; providers stay pinned below.
|
|
77
|
+
implementer_agent: string?
|
|
78
|
+
reviewer_agent: string?
|
|
79
|
+
pre_merge_gate: string[]?
|
|
80
|
+
cost_ceiling_usd: number?
|
|
81
|
+
output:
|
|
82
|
+
from: "${ship.output}"
|
|
83
|
+
contract: ShipResult
|
|
84
|
+
max_rounds: 4
|
|
85
|
+
carry:
|
|
86
|
+
wave:
|
|
87
|
+
initial: "${plan.output.tasks}"
|
|
88
|
+
on_revise:
|
|
89
|
+
assess_gate: "${assess.output.tasks}"
|
|
90
|
+
steps:
|
|
91
|
+
- id: plan
|
|
92
|
+
agent: claude
|
|
93
|
+
do: |
|
|
94
|
+
Plan wave 1 for ${input.featureCode}. Goal and acceptance criteria:
|
|
95
|
+
${input.description}
|
|
96
|
+
Read the feature's design, brief, plan and acceptance criteria before planning.
|
|
97
|
+
MUST checklist:
|
|
98
|
+
- Return TaskGraph with 1..6 tasks, each with id, description, files_owned,
|
|
99
|
+
files_read, depends_on, tier and a one-line tier_rationale.
|
|
100
|
+
- Wave 1 must contain only mutually independent tasks: depends_on is empty
|
|
101
|
+
for every task. Waves carry dependencies; dependent work goes in later
|
|
102
|
+
waves after its prerequisite checkpoint, not into this wave.
|
|
103
|
+
- No two tasks may share files_owned. Redo the graph if ownership overlaps.
|
|
104
|
+
- files_owned must be literal repo-relative paths, never globs or directories.
|
|
105
|
+
- Use critical for tasks with design judgment or root-causing, standard
|
|
106
|
+
for brief-bounded implementation, fast for transcription-level edits; repair
|
|
107
|
+
waves default to the tier of the task being repaired or higher, never lower.
|
|
108
|
+
- Each task must fit one focused session; target 2-4 tasks for a typical feature.
|
|
109
|
+
Return the task graph only; do not implement or commit.
|
|
110
|
+
out: TaskGraph
|
|
111
|
+
ensure:
|
|
112
|
+
- expr: "len(result.tasks) >= 1"
|
|
113
|
+
- expr: "len(result.tasks) <= 6"
|
|
114
|
+
attempts: 3
|
|
115
|
+
|
|
116
|
+
- id: execute
|
|
117
|
+
after: [plan]
|
|
118
|
+
fanout:
|
|
119
|
+
over: "${wave}"
|
|
120
|
+
dispatch: consumer
|
|
121
|
+
concurrency: 3
|
|
122
|
+
isolation: worktree
|
|
123
|
+
require: all
|
|
124
|
+
merge: sequential
|
|
125
|
+
steps:
|
|
126
|
+
- agent: codex
|
|
127
|
+
do: |
|
|
128
|
+
Implement the task described by ${item} using TDD. Write the test
|
|
129
|
+
first, watch it fail, implement, and watch it pass. Honor the
|
|
130
|
+
item's id, description, files_owned (you may create/modify these)
|
|
131
|
+
and files_read (you may read but NOT modify these).
|
|
132
|
+
MUST touch only files_owned — anything else fails the task at merge.
|
|
133
|
+
MUST return {outcome, summary, files_changed, verification}, with
|
|
134
|
+
verification {commands, outcomes}: exact commands and corresponding
|
|
135
|
+
observed outcomes, including failures. Do not commit.
|
|
136
|
+
out: TaskResult
|
|
137
|
+
|
|
138
|
+
- id: execute_merge
|
|
139
|
+
after: [execute]
|
|
140
|
+
gate:
|
|
141
|
+
on_approve: verify
|
|
142
|
+
on_revise: execute
|
|
143
|
+
on_kill: null
|
|
144
|
+
max_rounds: 2
|
|
145
|
+
|
|
146
|
+
- id: verify
|
|
147
|
+
after: [execute_merge]
|
|
148
|
+
agent: claude
|
|
149
|
+
do: |
|
|
150
|
+
Verify the integrated tree for ${input.featureCode} against the goal and
|
|
151
|
+
acceptance criteria: ${input.description}
|
|
152
|
+
MUST run the project's relevant tests and integration checks on the merged
|
|
153
|
+
tree, check for merge conflicts, and record exact commands and outcomes.
|
|
154
|
+
MUST capture the full cumulative merged diff: git diff HEAD -- for tracked
|
|
155
|
+
files, plus git ls-files --others --exclude-standard and added-file diffs
|
|
156
|
+
using git diff --no-index -- /dev/null <path> for each new feature file
|
|
157
|
+
(exit 1 means differences). HEAD remains the build base until ship; new
|
|
158
|
+
merged files can still be untracked. Return VerifyResult with tests_pass,
|
|
159
|
+
summary, verification {commands, outcomes}, and merged_diff. Describe only
|
|
160
|
+
observed verification evidence; never copy worker summaries. Do not edit.
|
|
161
|
+
Report failures truthfully so review and assess can request a repair wave.
|
|
162
|
+
out: VerifyResult
|
|
163
|
+
ensure:
|
|
164
|
+
- expr: "len(result.summary) > 0"
|
|
165
|
+
|
|
166
|
+
- id: review
|
|
167
|
+
after: [verify]
|
|
168
|
+
agent: codex
|
|
169
|
+
do: |
|
|
170
|
+
Fresh independent read-only review of ${input.featureCode}.
|
|
171
|
+
Goal and acceptance criteria: ${input.description}
|
|
172
|
+
VerifyResult (including the cumulative merged diff): ${verify.output}
|
|
173
|
+
MUST checklist:
|
|
174
|
+
- Read the feature's design, brief and plan for its acceptance criteria.
|
|
175
|
+
Read the merged diff against the goal, those criteria and VerifyResult;
|
|
176
|
+
inspect the integrated source and affected callers.
|
|
177
|
+
- Probe cross-module wiring: a green unit suite is not evidence that the
|
|
178
|
+
user-facing path reaches the implementation. Cite the actual call path.
|
|
179
|
+
- Never read worker summaries, TaskResults, worker journals or transcripts.
|
|
180
|
+
Form your judgment from the diff, source, criteria and verification only.
|
|
181
|
+
- Return one finding per defect, with severity, files (literal repo-relative
|
|
182
|
+
paths), claim and concrete evidence. Include failed verification and unmet
|
|
183
|
+
criteria as findings; set blocking when any defect prevents completion.
|
|
184
|
+
- Return ReviewFindings {findings, blocking}. Do not edit or commit.
|
|
185
|
+
out: ReviewFindings
|
|
186
|
+
|
|
187
|
+
- id: assess
|
|
188
|
+
after: [review]
|
|
189
|
+
agent: claude
|
|
190
|
+
do: |
|
|
191
|
+
Assess ${input.featureCode} against its goal and acceptance criteria:
|
|
192
|
+
${input.description}
|
|
193
|
+
Current tasks: ${wave}
|
|
194
|
+
TaskResults with verification evidence: ${execute.output}
|
|
195
|
+
VerifyResult and cumulative merged diff: ${verify.output}
|
|
196
|
+
Fresh ReviewFindings: ${review.output}
|
|
197
|
+
MUST checklist:
|
|
198
|
+
- Address every finding: reproduce its full finding object in exactly one
|
|
199
|
+
of addressed_findings or open_findings; justify dispositions in rationale.
|
|
200
|
+
- Copy blocking from review. Set open_count to len(open_findings).
|
|
201
|
+
- repair tasks must each own at least one file named by an open finding.
|
|
202
|
+
Dispatch only affected work; do not repeat accepted unaffected tasks.
|
|
203
|
+
- For repair or implement return 1..6 mutually independent tasks with empty
|
|
204
|
+
depends_on, disjoint literal repo-relative files_owned, and all Task fields.
|
|
205
|
+
Later waves start from the previous checkpoint containing prerequisites.
|
|
206
|
+
- Use critical for tasks with design judgment or root-causing, standard
|
|
207
|
+
for brief-bounded implementation, fast for transcription-level edits; repair
|
|
208
|
+
waves default to the tier of the task being repaired or higher, never lower.
|
|
209
|
+
- Choose implement when additional scoped work is needed; repair for defects.
|
|
210
|
+
- complete only when all acceptance criteria are met, verification passes,
|
|
211
|
+
there are zero open findings and blocking is false; return tasks: [].
|
|
212
|
+
- blocked when repair cannot proceed; retain the open findings (open_count
|
|
213
|
+
must be positive), explain why, and return tasks: [].
|
|
214
|
+
Return WaveDecision only. Do not edit or commit.
|
|
215
|
+
out: WaveDecision
|
|
216
|
+
ensure:
|
|
217
|
+
- expr: "result.action != 'complete' || (result.open_count == 0 && result.blocking == false)"
|
|
218
|
+
- expr: "result.action != 'blocked' || result.open_count > 0"
|
|
219
|
+
- expr: "result.open_count >= 0"
|
|
220
|
+
|
|
221
|
+
- id: assess_gate
|
|
222
|
+
after: [assess]
|
|
223
|
+
gate:
|
|
224
|
+
on_approve: ship
|
|
225
|
+
on_revise: execute
|
|
226
|
+
on_kill: null
|
|
227
|
+
max_rounds: 2
|
|
228
|
+
|
|
229
|
+
# Compose intercepts ship: squash the wave checkpoints and selectively commit.
|
|
230
|
+
- id: ship
|
|
231
|
+
after: [assess_gate]
|
|
232
|
+
agent: claude
|
|
233
|
+
do: |
|
|
234
|
+
Ship ${input.featureCode}: run final tests and squash the accepted wave
|
|
235
|
+
changes into one commit whose parent is the build base. Return ShipResult.
|
|
236
|
+
out: ShipResult
|
package/server/model-tiers.js
CHANGED
|
@@ -2,21 +2,26 @@
|
|
|
2
2
|
* model-tiers.js — Model tier routing for STRAT-TIER.
|
|
3
3
|
*
|
|
4
4
|
* Maps symbolic tier names to provider-specific model IDs.
|
|
5
|
-
* Tiers let pipeline specs declare intent (critical / standard / fast)
|
|
6
|
-
* without hard-coding model strings — the map here is the single source of truth
|
|
5
|
+
* Tiers let pipeline specs declare intent (critical / standard / fast / coordinator)
|
|
6
|
+
* without hard-coding model strings — the map here is the single source of truth,
|
|
7
|
+
* including the agent-string tier allow-list. Coordinator lets one preset role
|
|
8
|
+
* explicitly name Fable while critical stays Opus 5; other presets do not move
|
|
9
|
+
* silently to Fable.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
/** @type {Record<string, string>} */
|
|
10
13
|
export const MODEL_TIERS = {
|
|
11
|
-
critical: 'claude-opus-
|
|
12
|
-
standard: 'claude-sonnet-
|
|
14
|
+
critical: 'claude-opus-5',
|
|
15
|
+
standard: 'claude-sonnet-5',
|
|
13
16
|
fast: 'claude-haiku-4-5-20251001',
|
|
17
|
+
coordinator: 'claude-fable-5-1',
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export const CODEX_MODEL_TIERS = {
|
|
17
21
|
critical: 'gpt-6-astra',
|
|
18
22
|
standard: 'gpt-5.6-terra',
|
|
19
23
|
fast: 'gpt-5.3-codex-spark',
|
|
24
|
+
coordinator: null,
|
|
20
25
|
};
|
|
21
26
|
|
|
22
27
|
// C12: codex efforts follow the routing convention — `low` is for trivial
|
|
@@ -26,11 +31,13 @@ const CODEX_TIER_THINKING = {
|
|
|
26
31
|
critical: { mode: null, effort: 'high' },
|
|
27
32
|
standard: { mode: null, effort: 'high' },
|
|
28
33
|
fast: { mode: null, effort: 'medium' },
|
|
34
|
+
coordinator: null,
|
|
29
35
|
};
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
38
|
* Default thinking config per tier.
|
|
33
|
-
* - Opus
|
|
39
|
+
* - Opus 5 / Sonnet 5 support adaptive thinking and the effort parameter.
|
|
40
|
+
* - Fable 5.1 thinking is always on; adaptive thinking uses effort to control depth.
|
|
34
41
|
* - Haiku 4.5 doesn't accept the effort parameter (400 error), so fast tier stays off.
|
|
35
42
|
*
|
|
36
43
|
* @type {Record<string, { mode: 'adaptive'|'off', effort: 'low'|'medium'|'high'|'xhigh'|'max'|null }>}
|
|
@@ -39,13 +46,14 @@ export const TIER_THINKING = {
|
|
|
39
46
|
critical: { mode: 'adaptive', effort: 'xhigh' },
|
|
40
47
|
standard: { mode: 'adaptive', effort: 'high' },
|
|
41
48
|
fast: { mode: 'off', effort: null },
|
|
49
|
+
coordinator: { mode: 'adaptive', effort: 'high' },
|
|
42
50
|
};
|
|
43
51
|
|
|
44
52
|
/**
|
|
45
53
|
* Resolve a tier name to a concrete model ID.
|
|
46
54
|
*
|
|
47
55
|
* @param {string|null|undefined} tier
|
|
48
|
-
* @returns {string|null} Model ID, or null if tier is unknown
|
|
56
|
+
* @returns {string|null} Model ID, or null if tier is unknown, unavailable for the provider, or not provided.
|
|
49
57
|
*/
|
|
50
58
|
export function resolveTierModel(tier, provider = 'claude') {
|
|
51
59
|
if (!tier) return null;
|