axstack 0.20.29 → 0.20.31
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 +6 -4
- package/bin/axstack.js +1 -0
- package/docs/installation.md +20 -9
- package/docs/workflows.md +42 -17
- package/package.json +1 -1
- package/profiles/presets/claude-only.json +81 -45
- package/profiles/presets/codex-only.json +78 -42
- package/profiles/presets/mixed.json +83 -47
- package/skills/axstack/references/autopilot.md +108 -0
- package/skills/axstack/references/candidate-publication.md +9 -0
- package/skills/axstack/references/contracts.md +5 -1
- package/skills/axstack/references/diligence.md +23 -0
- package/skills/axstack/references/lifecycle.md +2 -2
- package/skills/axstack/references/orca-runtime.md +25 -6
- package/skills/axstack/references/role-roster.md +37 -0
- package/skills/axstack/references/routing.md +19 -31
- package/skills/axstack/references/run-record.md +2 -0
- package/skills/axstack/scripts/resolve-models.js +46 -0
- package/skills/axstack-align/SKILL.md +8 -4
- package/skills/axstack-audit/SKILL.md +15 -2
- package/skills/axstack-implement/SKILL.md +30 -11
- package/skills/axstack-improve/SKILL.md +5 -0
- package/skills/axstack-relay/SKILL.md +9 -2
- package/skills/axstack-research/SKILL.md +14 -2
- package/skills/axstack-review/SKILL.md +29 -13
- package/skills/axstack-spec/SKILL.md +8 -1
- package/skills/axstack-tickets/SKILL.md +9 -3
- package/skills/axstack-watch/SKILL.md +27 -13
- package/skills/axstack-watch/references/watch-runtime.md +11 -4
- package/src/installer.js +8 -0
- package/src/roles.js +39 -11
|
@@ -204,15 +204,18 @@ This section applies to peer and authored PR modes.
|
|
|
204
204
|
- **Authored:** exactly one eligible independent reviewer from this complete
|
|
205
205
|
mapping:
|
|
206
206
|
|
|
207
|
-
| Preset | Actual author provider/
|
|
207
|
+
| Preset | Actual author provider/class | Reviewer role (configured class/effort) |
|
|
208
208
|
| --- | --- | --- |
|
|
209
|
-
| `mixed` |
|
|
210
|
-
| `mixed` |
|
|
211
|
-
| `codex-only` |
|
|
212
|
-
| `claude-only` |
|
|
209
|
+
| `mixed` | `codex/sol` | `axstack-reviewer-secondary` (`claude/opus` medium) |
|
|
210
|
+
| `mixed` | `claude/opus` | `axstack-reviewer-primary` (`codex/sol` high) |
|
|
211
|
+
| `codex-only` | `codex/sol` | `axstack-reviewer-secondary` (`codex/luna` xhigh) |
|
|
212
|
+
| `claude-only` | `claude/opus` | `axstack-reviewer-secondary` (`claude/sonnet` high) |
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
The diligence receipt is separate and does not count as a reviewer receipt.
|
|
215
|
+
|
|
216
|
+
From the recorded exact model ID, derive its class and match provenance
|
|
217
|
+
on provider/class; record effort, but never use effort to create a mapping.
|
|
218
|
+
An ID with no class is `INCOMPLETE`. Any other author provenance for the
|
|
216
219
|
selected preset is unsupported and `INCOMPLETE`, including its secondary
|
|
217
220
|
reviewer model, Astra, Luna, or Fable. Report the exact provenance gap and
|
|
218
221
|
ask the user. Never derive a reverse pairing from slot position. The
|
|
@@ -231,9 +234,19 @@ This section applies to peer and authored PR modes.
|
|
|
231
234
|
effort and spawn no redundant final reviewer. If a required reviewer is
|
|
232
235
|
unavailable, report that exact model gap, mark review `INCOMPLETE`, and ask
|
|
233
236
|
the user; do not lower effort or choose any automatic fallback.
|
|
237
|
+
The only within-class exception is explicit model rejection before the first
|
|
238
|
+
turn: Codex may use
|
|
239
|
+
`--retry-of` with the next eligible ID in the same class, provider, and
|
|
240
|
+
effort; fence and record the rejected attempt. Timeout, quota, and auth
|
|
241
|
+
failures hold; Claude rejection holds. Never cross class or provider.
|
|
234
242
|
|
|
235
243
|
Continue only when session receipts prove the required models, non-author
|
|
236
244
|
independence, actual author provenance where applicable, and exact brief.
|
|
245
|
+
In peer and authored PR review rounds, dispatch `axstack-diligence`
|
|
246
|
+
independently alongside the configured reviewer(s) on the same exact revision
|
|
247
|
+
and base. Follow [Diligence](../axstack/references/diligence.md). A diligence
|
|
248
|
+
`FINDINGS` receipt returns validated findings to the same author in the same
|
|
249
|
+
round; it is not an extra `REQUEST_CHANGES` round.
|
|
237
250
|
3. **Inspect all six angles.** In peer mode each reviewer covers every angle;
|
|
238
251
|
in authored mode the one reviewer covers all six angles:
|
|
239
252
|
1. Security and trust boundaries.
|
|
@@ -321,12 +334,14 @@ These verdicts apply only to PR modes. Codebase findings use coverage status.
|
|
|
321
334
|
covering the whole brief, all six angles, and applicable acceptance.
|
|
322
335
|
- **Complete verdict:** validated blocking defects permit `REQUEST_CHANGES`;
|
|
323
336
|
complete evidence with no blocker permits `APPROVE`.
|
|
337
|
+
- **Diligence:** a current `PASS` is required with reviewer approval for
|
|
338
|
+
merge-ready; `FINDINGS` return to the author in that round.
|
|
324
339
|
- **Incomplete or stale:** use `INCOMPLETE`; never fabricate `APPROVE` or
|
|
325
340
|
`REQUEST_CHANGES`.
|
|
326
341
|
|
|
327
342
|
The owner verifies and synthesizes the mode-required evidence without voting.
|
|
328
|
-
A peer receipt count of one is incomplete; an authored
|
|
329
|
-
than one is not the selected mode. Passing tests or reviewer unanimity grants
|
|
343
|
+
A peer reviewer receipt count of one is incomplete; an authored reviewer
|
|
344
|
+
receipt count other than one is not the selected mode. Passing tests or reviewer unanimity grants
|
|
330
345
|
no merge authority.
|
|
331
346
|
|
|
332
347
|
## Template: candidate review brief
|
|
@@ -397,10 +412,11 @@ Mode-required exact-revision completeness gates external approval,
|
|
|
397
412
|
[merge-ready declarations](#authored-mode-own-pr), and authorized submission. It never gates returning
|
|
398
413
|
evidence, limitations, validated risk, or an internal `INCOMPLETE` report.
|
|
399
414
|
|
|
400
|
-
- Peer mode requires both current reviews
|
|
401
|
-
beyond the validated defects reported by
|
|
402
|
-
|
|
403
|
-
|
|
415
|
+
- Peer mode requires both current reviews, a separate current diligence receipt,
|
|
416
|
+
and no unresolved material finding beyond the validated defects reported by
|
|
417
|
+
`REQUEST_CHANGES`.
|
|
418
|
+
- Authored mode requires its one current eligible configured reviewer receipt and
|
|
419
|
+
a separate current diligence receipt; applicable scope identity must remain valid.
|
|
404
420
|
- A missing, mismatched, stale, or materially changed input blocks approval and
|
|
405
421
|
merge-ready declarations while readonly investigation continues.
|
|
406
422
|
|
|
@@ -5,6 +5,9 @@ description: When agreed work needs an approved baseline, use axstack-spec to wr
|
|
|
5
5
|
|
|
6
6
|
# Specification baseline
|
|
7
7
|
|
|
8
|
+
For authorized delivery runs, follow [Autopilot](../axstack/references/autopilot.md)
|
|
9
|
+
for phase continuation and holds.
|
|
10
|
+
|
|
8
11
|
On driver entry, sweep under [Workspace hygiene](../axstack/references/workspace-hygiene.md); dispatched workers do not sweep.
|
|
9
12
|
For every dispatch brief, name its private `<run dir>/evidence/<dispatch>/` folder.
|
|
10
13
|
|
|
@@ -54,6 +57,9 @@ and the lifecycle's [audit skill](../axstack-audit/SKILL.md) hook.
|
|
|
54
57
|
session to return plain AGREE. Present one
|
|
55
58
|
reviewable, identified revision for this checkpoint. Its user approval
|
|
56
59
|
creates the execution baseline.
|
|
60
|
+
Before user approval, dispatch `axstack-diligence` under
|
|
61
|
+
[Diligence](../axstack/references/diligence.md) to check the draft against
|
|
62
|
+
the Align decisions for anything dropped, added, or softened.
|
|
57
63
|
5. **Snapshot the baseline.** Record the approved revision identity and a
|
|
58
64
|
concise repository Markdown counterpart. In Linear mode, the native
|
|
59
65
|
document remains authoritative. In GitHub mode, the approved issue body is
|
|
@@ -72,7 +78,8 @@ Material change: <none | description + affected PRs/tasks + hold state>
|
|
|
72
78
|
The snapshot is ready for ticketing when its authoritative revision,
|
|
73
79
|
counterpart, and preserved ref resolve to the approved content. Return that
|
|
74
80
|
exact identity; routine execution of the settled plan needs no repeat adviser
|
|
75
|
-
consultation or spec approval.
|
|
81
|
+
consultation or spec approval. In an eligible delivery run with no hold,
|
|
82
|
+
continue to Tickets in the same driver chat.
|
|
76
83
|
|
|
77
84
|
## Material revisions
|
|
78
85
|
|
|
@@ -5,12 +5,15 @@ description: When an approved capability needs executable tasks, use axstack-tic
|
|
|
5
5
|
|
|
6
6
|
# Tickets
|
|
7
7
|
|
|
8
|
+
For authorized delivery runs, follow [Autopilot](../axstack/references/autopilot.md)
|
|
9
|
+
for phase continuation and holds.
|
|
10
|
+
|
|
8
11
|
On driver entry, sweep under [Workspace hygiene](../axstack/references/workspace-hygiene.md); dispatched workers do not sweep.
|
|
9
12
|
For every dispatch brief, name its private `<run dir>/evidence/<dispatch>/` folder.
|
|
10
13
|
|
|
11
14
|
Produce an executable capability map tied to the exact approved spec revision.
|
|
12
15
|
Keep user-visible capabilities in the selected store, keep implementation detail
|
|
13
|
-
in the repository, reconcile lifecycle state, and
|
|
16
|
+
in the repository, reconcile lifecycle state, and return a map for continuation.
|
|
14
17
|
|
|
15
18
|
Before mapping, load [Standing contracts](../axstack/references/contracts.md).
|
|
16
19
|
Follow its required edge to [Shared lifecycle](../axstack/references/lifecycle.md),
|
|
@@ -54,6 +57,9 @@ an actual checker dispatch, not for ordinary mapping or state reconciliation.
|
|
|
54
57
|
rationale with the task; actual measurement and exception evidence follow in
|
|
55
58
|
the implement receipt. Mapping time requires no actual SHAs or line counts.
|
|
56
59
|
Every capability ends with the fields below and an explicit dependency list.
|
|
60
|
+
Before accepting the map, dispatch `axstack-diligence` under
|
|
61
|
+
[Diligence](../axstack/references/diligence.md) to confirm every spec
|
|
62
|
+
acceptance item maps to a capability's acceptance.
|
|
57
63
|
These routine mapping and split choices are autonomous driver decisions
|
|
58
64
|
within the approved spec; size alone never requires user approval.
|
|
59
65
|
|
|
@@ -97,5 +103,5 @@ Recommendation: <move to In Review | keep open | close | other> (driver verifies
|
|
|
97
103
|
|
|
98
104
|
5. **Return the mapping.** Report the pinned spec revision, selected store, map
|
|
99
105
|
references, mutations performed by the driver, recorded gaps, and unresolved
|
|
100
|
-
decisions.
|
|
101
|
-
|
|
106
|
+
decisions. With a complete map and no hold, an eligible delivery run
|
|
107
|
+
continues to Implement in the same driver chat.
|
|
@@ -5,6 +5,9 @@ description: When babysitting an existing PR, use axstack-watch to monitor or ma
|
|
|
5
5
|
|
|
6
6
|
# Watch
|
|
7
7
|
|
|
8
|
+
For authorized delivery runs, follow [Autopilot](../axstack/references/autopilot.md)
|
|
9
|
+
for phase continuation and holds.
|
|
10
|
+
|
|
8
11
|
On driver entry, sweep under [Workspace hygiene](../axstack/references/workspace-hygiene.md); dispatched workers do not sweep.
|
|
9
12
|
For every dispatch brief, name its private `<run dir>/evidence/<dispatch>/` folder.
|
|
10
13
|
|
|
@@ -49,7 +52,8 @@ authority is unverified, record the hold and continue read-only.
|
|
|
49
52
|
|
|
50
53
|
Choose one mode from the user's authority and record it before dispatch:
|
|
51
54
|
|
|
52
|
-
- **Chat-run watch:**
|
|
55
|
+
- **Chat-run watch:** authorized maintain mode is the default for run-created
|
|
56
|
+
PRs. The initiating chat remains the only driver and record
|
|
53
57
|
writer for every PR raised in its Run, including later verified publications
|
|
54
58
|
and explicitly adopted members. Follow [Chat-run watch runtime](references/watch-runtime.md#chat-run-watch)
|
|
55
59
|
for its scheduled driver wake and Orca fallback. This mode has no replacement `axstack-owner` or
|
|
@@ -121,11 +125,16 @@ A handled wake has an acknowledged event ID, an observation or action bound to
|
|
|
121
125
|
the current revision, and a recorded hold or next owner where work remains.
|
|
122
126
|
|
|
123
127
|
Under a recorded `Notification policy`, the owner may use the optional
|
|
124
|
-
[axstack-relay](../axstack-relay/SKILL.md) only for a serious risk immediately
|
|
125
|
-
|
|
126
|
-
recovery
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
[axstack-relay](../axstack-relay/SKILL.md) only for a serious risk immediately,
|
|
129
|
+
a genuine blocked operation needing user intervention after bounded safe
|
|
130
|
+
recovery, or decision holds and capped milestones named by the recorded policy.
|
|
131
|
+
Routine questions stay in Orca. Progress, CI pending, and completion always stay
|
|
132
|
+
in Orca.
|
|
133
|
+
Only the bounded categories—user-decision holds (including spec approval),
|
|
134
|
+
serious-risk holds, and at most two merge-ready/merged milestones per run—may
|
|
135
|
+
be relayed under the recorded Notification policy.
|
|
136
|
+
The standalone monitor never sends; the chat-run observer reports only
|
|
137
|
+
internally. Deduplicate authorized notifications;
|
|
129
138
|
absent policy or failed relay uses the current Orca conversation and leaves
|
|
130
139
|
the existing hold open.
|
|
131
140
|
|
|
@@ -135,6 +144,7 @@ The owner checks current required checks, all feedback, approvals, mergeability,
|
|
|
135
144
|
and exact-revision receipts before any merge-ready statement. API errors leave
|
|
136
145
|
readiness `UNKNOWN`; review approval alone is not merge-ready. Merge-ready is an
|
|
137
146
|
observed state distinct from merged, and the human merges by default.
|
|
147
|
+
A current diligence `PASS` at the exact head is required before any merge-ready statement.
|
|
138
148
|
Under authorized own-PR maintenance, keep repairing and rebasing onto the base
|
|
139
149
|
when it moves, then re-run checks, until the head is rebased on the current base,
|
|
140
150
|
every review comment and thread is addressed, at least one human team member's
|
|
@@ -147,10 +157,14 @@ human approval remain allowed.
|
|
|
147
157
|
|
|
148
158
|
## 6. End and preserve continuity
|
|
149
159
|
|
|
150
|
-
End a chat-run watch after all members merged or closed
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
End a chat-run watch after all members merged or closed and the run's release
|
|
161
|
+
step is settled or not applicable, user cancellation, or the recorded wake
|
|
162
|
+
expires. Without an Autopilot or Release record, the release step is not
|
|
163
|
+
applicable to this watch. A required PR closed without merging records a
|
|
164
|
+
decision hold and the wake remains active while unexpired until the user
|
|
165
|
+
resolves scope, cancels, or the wake expires. Stop the chosen wake and verify
|
|
166
|
+
its stop receipt; a failed or uncertain harness wake stop is a hold.
|
|
167
|
+
The Orca fallback also needs own-automation disable/readback and driver-owned automation
|
|
154
168
|
removal and workspace cleanup under
|
|
155
169
|
[Watch runtime](references/watch-runtime.md#chat-run-watch).
|
|
156
170
|
|
|
@@ -182,6 +196,6 @@ Resume: <known commands or verified refs needed to reconcile from this revision>
|
|
|
182
196
|
|
|
183
197
|
The watch ends only when registrations are stopped, receipts are recorded, and
|
|
184
198
|
the PR is either merged or represented by this resumable state.
|
|
185
|
-
When every required PR is merged
|
|
186
|
-
[Close-out](../axstack/references/lifecycle.md#close-out)
|
|
187
|
-
run as done.
|
|
199
|
+
When every required PR is merged and the run's Release step is settled or not
|
|
200
|
+
applicable, follow the lifecycle [Close-out](../axstack/references/lifecycle.md#close-out)
|
|
201
|
+
before reporting the run as done.
|
|
@@ -90,8 +90,10 @@ busy, missing, fenced, protected, or permission-held driver is never interrupted
|
|
|
90
90
|
or replaced.
|
|
91
91
|
|
|
92
92
|
The driver records one Notification policy: `axstack-relay` Telegram home only
|
|
93
|
-
for a user-decision hold
|
|
94
|
-
two
|
|
93
|
+
for a user-decision hold (including spec and npm approval), merge-ready or
|
|
94
|
+
merged milestones (at most two across implementation and release), or a
|
|
95
|
+
serious-risk hold.
|
|
96
|
+
Quiet ticks never notify.
|
|
95
97
|
|
|
96
98
|
The driver alone routes repair. Re-read remote head/base and native ownership.
|
|
97
99
|
Independent PRs may repair in parallel in separate Orca child worktrees within
|
|
@@ -116,8 +118,13 @@ re-read all feedback and approvals at the current head before readiness.
|
|
|
116
118
|
Re-reading approvals checks current state, not re-requesting review from a
|
|
117
119
|
human who already approved.
|
|
118
120
|
|
|
119
|
-
Stop the chosen wake only when every watched PR is merged or closed
|
|
120
|
-
|
|
121
|
+
Stop the chosen wake only when every watched PR is merged or closed and the
|
|
122
|
+
run's release step is settled or not applicable, the user cancels, or it
|
|
123
|
+
expires. Without an Autopilot or Release record, the release step is not
|
|
124
|
+
applicable to this watch. A required PR closed without merging records a
|
|
125
|
+
decision hold and the wake remains active while unexpired until the user
|
|
126
|
+
resolves scope, cancels, or the wake expires; the run is not release-eligible.
|
|
127
|
+
The driver stops a harness-native wake and verifies its stop receipt;
|
|
121
128
|
a failed or uncertain stop is a hold. Re-read membership and confirm no ambiguous
|
|
122
129
|
publication or unsettled pass; cancellation
|
|
123
130
|
prevents new work but does not prove running workers exited. The observer may
|
package/src/installer.js
CHANGED
|
@@ -635,8 +635,16 @@ export async function installBundle({
|
|
|
635
635
|
const newIds = new Set(bundle.bundleRoles.map((role) => role.id));
|
|
636
636
|
const addedRoleIds = bundle.bundleRoles.filter((role) => !oldIds.has(role.id)).map((role) => role.id);
|
|
637
637
|
const removedRoleIds = old.roles.filter((role) => role?.id && !newIds.has(role.id)).map((role) => role.id);
|
|
638
|
+
const oldById = new Map(old.roles.filter((role) => role?.id).map((role) => [role.id, role]));
|
|
639
|
+
const changedRoleModels = bundle.bundleRoles.flatMap((role) => {
|
|
640
|
+
const previous = oldById.get(role.id);
|
|
641
|
+
if (!previous) return [];
|
|
642
|
+
const fields = ['modelClass', 'model'].filter((field) => previous[field] !== role[field]);
|
|
643
|
+
return fields.length ? [`${role.id} (${fields.join(', ')})`] : [];
|
|
644
|
+
});
|
|
638
645
|
summary.addedRoleIds = addedRoleIds;
|
|
639
646
|
summary.removedRoleIds = removedRoleIds;
|
|
647
|
+
summary.changedRoleModels = changedRoleModels;
|
|
640
648
|
}
|
|
641
649
|
} catch { /* No reliable role-ID diff for malformed prior bytes. */ }
|
|
642
650
|
}
|
package/src/roles.js
CHANGED
|
@@ -4,19 +4,33 @@ const PROVIDER_BOUNDS = Object.freeze({
|
|
|
4
4
|
'claude-only': new Set(['claude']),
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
+
const CLASS_PROVIDERS = Object.freeze({
|
|
8
|
+
fable: 'claude', opus: 'claude', sonnet: 'claude', haiku: 'claude',
|
|
9
|
+
astra: 'codex', sol: 'codex', luna: 'codex',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function deriveModelClass(model) {
|
|
13
|
+
if (typeof model !== 'string') return null;
|
|
14
|
+
return model.match(/^gpt-(\d+(?:\.\d+)*)-(astra|sol|luna)$/)?.[2]
|
|
15
|
+
?? model.match(/^claude-(fable|opus|sonnet|haiku)-/)?.[1]
|
|
16
|
+
?? null;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
const AUTHORED_ROUTES = Object.freeze({
|
|
8
20
|
mixed: {
|
|
9
|
-
'codex/
|
|
10
|
-
'claude/
|
|
21
|
+
'codex/sol': ['axstack-reviewer-secondary', 'claude/opus', 'medium'],
|
|
22
|
+
'claude/opus': ['axstack-reviewer-primary', 'codex/sol', 'high'],
|
|
11
23
|
},
|
|
12
24
|
'codex-only': {
|
|
13
|
-
'codex/
|
|
25
|
+
'codex/sol': ['axstack-reviewer-secondary', 'codex/luna', 'xhigh'],
|
|
14
26
|
},
|
|
15
27
|
'claude-only': {
|
|
16
|
-
'claude/
|
|
28
|
+
'claude/opus': ['axstack-reviewer-secondary', 'claude/sonnet', 'high'],
|
|
17
29
|
},
|
|
18
30
|
});
|
|
19
31
|
|
|
32
|
+
const roleClass = (role) => role.modelClass ?? deriveModelClass(role.model);
|
|
33
|
+
|
|
20
34
|
export function assertBundleRoles(roles) {
|
|
21
35
|
if (!Array.isArray(roles) || roles.length === 0) {
|
|
22
36
|
throw new Error('invalid bundle roles: expected a non-empty roles array');
|
|
@@ -36,12 +50,22 @@ export function assertBundleRoles(roles) {
|
|
|
36
50
|
}
|
|
37
51
|
if (ids.has(role.id)) throw new Error(`invalid bundle roles: duplicate role ID ${role.id}`);
|
|
38
52
|
ids.add(role.id);
|
|
39
|
-
|
|
53
|
+
const hasClass = Object.hasOwn(role, 'modelClass');
|
|
54
|
+
if (hasClass && (typeof role.modelClass !== 'string' || !Object.hasOwn(CLASS_PROVIDERS, role.modelClass))) {
|
|
55
|
+
throw new Error(`invalid bundle roles: ${role.id} has an unsupported modelClass`);
|
|
56
|
+
}
|
|
57
|
+
if (hasClass && role.provider !== CLASS_PROVIDERS[role.modelClass]) {
|
|
58
|
+
throw new Error(`invalid bundle roles: ${role.id} model class does not match provider`);
|
|
59
|
+
}
|
|
60
|
+
if (!hasClass && !Object.hasOwn(role, 'model')) {
|
|
40
61
|
throw new Error('invalid bundle roles: model must be a non-empty string or explicit null');
|
|
41
62
|
}
|
|
42
|
-
if (role.model !== null && (typeof role.model !== 'string' || role.model.trim() === '')) {
|
|
63
|
+
if (Object.hasOwn(role, 'model') && role.model !== null && (typeof role.model !== 'string' || role.model.trim() === '')) {
|
|
43
64
|
throw new Error('invalid bundle roles: model must be a non-empty string or explicit null');
|
|
44
65
|
}
|
|
66
|
+
if (hasClass && typeof role.model === 'string' && deriveModelClass(role.model) !== role.modelClass) {
|
|
67
|
+
throw new Error(`invalid bundle roles: ${role.id} model pin does not match model class`);
|
|
68
|
+
}
|
|
45
69
|
for (const field of ['icon', 'color', 'modeId', 'thinkingOptionId', 'notes']) {
|
|
46
70
|
if (role[field] !== undefined && typeof role[field] !== 'string') {
|
|
47
71
|
throw new Error(`invalid bundle roles: ${field} must be a string when present`);
|
|
@@ -69,13 +93,13 @@ export function assessRoleReadiness(roles, preset) {
|
|
|
69
93
|
(preset === 'mixed' && role.id === 'axstack-arena-candidate-grok' && role.provider === 'grok') ||
|
|
70
94
|
(preset === 'mixed' && role.id === 'axstack-arena-candidate-antigravity' && role.provider === 'antigravity') ||
|
|
71
95
|
(preset === 'codex-only' && ['axstack-advisor-opus', 'axstack-escalation-fable', 'axstack-arena-judge-opus', 'axstack-research-web-google', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-arena-candidate-antigravity'].includes(role.id)) ||
|
|
72
|
-
(preset === 'claude-only' && ['axstack-advisor-astra', 'axstack-arena-judge-astra', 'axstack-research-web-google', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-arena-candidate-antigravity'].includes(role.id))
|
|
96
|
+
(preset === 'claude-only' && ['axstack-advisor-astra', 'axstack-arena-judge-astra', 'axstack-research-web-google', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-arena-candidate-antigravity', 'axstack-auditor-sol', 'axstack-research-code-sol', 'axstack-explore-execution-sol'].includes(role.id))
|
|
73
97
|
);
|
|
74
98
|
for (const role of roles) {
|
|
75
99
|
if (!bounds.has(role.provider)) {
|
|
76
100
|
gaps.push(`${role.id} provider ${JSON.stringify(role.provider)} is outside ${preset} bounds (${[...bounds].join('|')})`);
|
|
77
101
|
}
|
|
78
|
-
if (!isIntentionalAbsence(role) && (typeof role.model !== 'string' || role.model.trim() === '')) {
|
|
102
|
+
if (!isIntentionalAbsence(role) && !Object.hasOwn(role, 'modelClass') && (typeof role.model !== 'string' || role.model.trim() === '')) {
|
|
79
103
|
gaps.push(`${role.id} requires a configured model`);
|
|
80
104
|
}
|
|
81
105
|
}
|
|
@@ -86,7 +110,11 @@ export function assessRoleReadiness(roles, preset) {
|
|
|
86
110
|
const primary = byId.get('axstack-reviewer-primary');
|
|
87
111
|
const secondary = byId.get('axstack-reviewer-secondary');
|
|
88
112
|
if (primary && secondary) {
|
|
89
|
-
|
|
113
|
+
const bothResolved = typeof primary.model === 'string' && typeof secondary.model === 'string';
|
|
114
|
+
const sameReviewer = bothResolved
|
|
115
|
+
? primary.model === secondary.model
|
|
116
|
+
: roleClass(primary) !== null && roleClass(primary) === roleClass(secondary);
|
|
117
|
+
if (sameReviewer) gaps.push('reviewer pair must use two distinct models');
|
|
90
118
|
if (preset === 'mixed' && primary.provider === secondary.provider) {
|
|
91
119
|
gaps.push('mixed reviewer pair must use different providers');
|
|
92
120
|
}
|
|
@@ -94,14 +122,14 @@ export function assessRoleReadiness(roles, preset) {
|
|
|
94
122
|
|
|
95
123
|
const author = byId.get('axstack-author');
|
|
96
124
|
if (author) {
|
|
97
|
-
const authorRoute = `${author.provider}/${author.model}`;
|
|
125
|
+
const authorRoute = `${author.provider}/${roleClass(author) ?? author.model}`;
|
|
98
126
|
const route = AUTHORED_ROUTES[preset]?.[authorRoute];
|
|
99
127
|
if (!route) {
|
|
100
128
|
gaps.push(`authored routing gap: unsupported axstack-author route ${authorRoute}`);
|
|
101
129
|
} else {
|
|
102
130
|
const [reviewerId, reviewerRoute, effort] = route;
|
|
103
131
|
const reviewer = byId.get(reviewerId);
|
|
104
|
-
if (!reviewer || `${reviewer.provider}/${reviewer
|
|
132
|
+
if (!reviewer || `${reviewer.provider}/${roleClass(reviewer)}` !== reviewerRoute || reviewer.thinkingOptionId !== effort) {
|
|
105
133
|
gaps.push(`authored routing gap: ${reviewerId} must be ${reviewerRoute}/${effort} for author ${authorRoute}`);
|
|
106
134
|
}
|
|
107
135
|
}
|