instar 1.3.191 → 1.3.192

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.
@@ -0,0 +1,310 @@
1
+ /**
2
+ * crossModelReviewer — Step B of the tiered development process.
3
+ *
4
+ * Re-platforms the `/spec-converge` external "cross-model" reviewer onto the
5
+ * agent's own installed `codex` CLI. The external pass that the skill used to
6
+ * describe as running "via the /crossreview pattern" (a never-built
7
+ * placeholder) is now a grounded mechanism: detect whether a supported reviewer
8
+ * framework is installed + authed, assemble the cross-model reviewer prompt
9
+ * (spec + referenced context, bounded to a budget), and run it THROUGH the
10
+ * existing `CodexCliIntelligenceProvider` (the factory with
11
+ * `framework: 'codex-cli'`, model `capable` → GPT-tier).
12
+ *
13
+ * Design invariants (see docs/specs/codex-crossreview-stepB-spec.md):
14
+ * - Detection is a pure function with injectable inputs (no real spawns in
15
+ * unit tests). It is SIGNAL-ONLY — it never throws and never blocks.
16
+ * - Reviewer invocation reuses the provider (its scratch-dir clean-notepad,
17
+ * env allowlist, `--skip-git-repo-check`, and the account-global circuit
18
+ * breaker the factory wraps it in). The ONLY new spawn-adjacent code is
19
+ * prompt assembly + result parsing.
20
+ * - Every failure mode routes toward internal-only convergence or a captured
21
+ * raw finding — never a stall. `unavailable` (no framework) is distinct
22
+ * from `degraded` (framework present, this call failed) and
23
+ * `skipped-abbreviated` (author chose the fast path).
24
+ *
25
+ * codex is the FIRST supported framework; the registry
26
+ * (`SUPPORTED_REVIEWER_FRAMEWORKS`) is the single seam for gemini-cli and
27
+ * others to plug in later (Out of Scope here). Adding a framework is one
28
+ * registry entry + one `id`-union extension — no skill change.
29
+ */
30
+ import { type IntelligenceFramework } from './intelligenceProviderFactory.js';
31
+ /**
32
+ * Per-call timeout for a cross-model spec review. A reasoning review of a
33
+ * full spec is far heavier than the provider's 30s judgment-call default, so
34
+ * Step B bumps it to 120s (spec §2).
35
+ */
36
+ export declare const REVIEW_TIMEOUT_MS = 120000;
37
+ /**
38
+ * Total context budget (spec + referenced docs) inlined into the reviewer
39
+ * prompt. codex runs in an empty read-only scratch dir with no repo access,
40
+ * so referenced context MUST be inlined; this bounds the prompt size (spec §2).
41
+ * The spec is always included in full; referenced context fills the remainder
42
+ * and is truncated (with a loud note) if it overflows.
43
+ */
44
+ export declare const CONTEXT_BUDGET_BYTES: number;
45
+ /**
46
+ * Deterministic priority ordering for referenced context (spec §2, F4).
47
+ *
48
+ * When the 60KB budget can't hold every referenced doc, truncation MUST be
49
+ * deterministic — the same spec + same docs always drop the same docs — so a
50
+ * review is reproducible and the "what got dropped" note is stable. The
51
+ * constitutional / lessons docs are the highest-value context for a reviewer
52
+ * (they're what the lessons-aware internal reviewer reads), so they are kept
53
+ * FIRST; everything else keeps the spec-declared link order (the order the
54
+ * caller passed the docs in, which is the order they appear in the spec).
55
+ *
56
+ * A doc whose path contains one of these substrings sorts ahead of the rest,
57
+ * in THIS order. Ties (and all non-priority docs) preserve the caller's order
58
+ * via a stable sort.
59
+ */
60
+ export declare const CONTEXT_PRIORITY_SUBSTRINGS: readonly string[];
61
+ /**
62
+ * Order referenced context deterministically: constitutional/lessons docs
63
+ * first (per CONTEXT_PRIORITY_SUBSTRINGS), then the caller's spec-declared link
64
+ * order for the rest. A stable sort on the priority rank achieves both — equal
65
+ * ranks keep their input order. Pure; never mutates the input.
66
+ */
67
+ export declare function orderContextDeterministically(context: readonly ReferencedContextDoc[]): ReferencedContextDoc[];
68
+ /**
69
+ * Reasons a supported reviewer framework is unavailable. Mirrors the
70
+ * Rule-1 / auth-probe vocabulary so a report can render a specific
71
+ * remediation.
72
+ */
73
+ export type CrossModelUnavailableReason = 'codex-not-installed' | 'codex-not-authed' | 'codex-auth-apikey-forbidden' | 'no-supported-framework';
74
+ export interface CrossModelDetectionResult {
75
+ available: boolean;
76
+ /** Present when available; the framework id that will run the review. */
77
+ framework?: IntelligenceFramework;
78
+ /** Present when available; the concrete model the review resolves to. */
79
+ model?: string;
80
+ /** Present when unavailable; a specific machine-readable reason. */
81
+ reason?: CrossModelUnavailableReason;
82
+ }
83
+ /**
84
+ * Injectable inputs for `detectCrossModelReviewer` so the detection logic is
85
+ * unit-testable without real spawns or a real `~/.codex/auth.json`.
86
+ */
87
+ export interface CrossModelDetectInputs {
88
+ /**
89
+ * Path to the codex binary if detected, else null. Defaults to
90
+ * `detectCodexPath()` (PATH + asdf/nvm-shim resolution).
91
+ */
92
+ codexPathDetected?: string | null;
93
+ /**
94
+ * Path to the codex auth.json. Defaults to
95
+ * `${CODEX_HOME || ~/.codex}/auth.json`.
96
+ */
97
+ authJsonPath?: string;
98
+ /** Process env (for the Rule-1 OPENAI_API_KEY probe). Defaults to process.env. */
99
+ env?: NodeJS.ProcessEnv;
100
+ /** Clock injection for the Rule-1 killswitch sunset check. */
101
+ now?: Date;
102
+ }
103
+ /**
104
+ * Detect a codex reviewer. Returns `{ available: true, framework, model }`
105
+ * iff ALL of: codex binary detected, OAuth `access_token` present, Rule-1
106
+ * clean (no raw API key in env or auth.json). Any miss → a specific reason.
107
+ *
108
+ * Pure-ish: all external inputs are injectable. With no inputs it probes the
109
+ * real host. It NEVER throws.
110
+ */
111
+ export declare function detectCodexReviewer(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
112
+ export interface ReviewerResult {
113
+ /** Outcome class for the cross-model pass. */
114
+ status: 'ok' | 'degraded' | 'unavailable';
115
+ /** The framework that ran (or would have run). */
116
+ framework?: IntelligenceFramework;
117
+ /** Concrete model used. */
118
+ model?: string;
119
+ /** Parsed verdict, when the review returned. */
120
+ verdict?: ReviewVerdict;
121
+ /** Structured findings (one record), folded alongside internal reviewers. */
122
+ findings?: ReviewFinding[];
123
+ /** A reason string for degraded/unavailable outcomes. */
124
+ reason?: string;
125
+ /** The flag string that gets written to frontmatter + the report banner. */
126
+ flag: string;
127
+ }
128
+ export type ReviewVerdict = 'CLEAN' | 'MINOR ISSUES' | 'SERIOUS ISSUES' | 'UNKNOWN';
129
+ export interface ReviewFinding {
130
+ /** Reviewer tag, e.g. 'cross-model:codex-cli:gpt-5.5'. */
131
+ reviewer: string;
132
+ verdict: ReviewVerdict;
133
+ /** The findings body (verbatim text the reviewer produced). */
134
+ body: string;
135
+ /** True when the reply could not be parsed into a verdict (captured raw). */
136
+ unstructured?: boolean;
137
+ }
138
+ interface SupportedReviewerFramework {
139
+ /** Extend this union (in the type below) to add a framework. */
140
+ id: IntelligenceFramework;
141
+ /** Detection — does this framework's reviewer have what it needs to run? */
142
+ detect(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
143
+ /**
144
+ * Run the review: assemble already done by the caller; this builds the
145
+ * provider, evaluates the prompt, and parses the result. Returns a
146
+ * ReviewerResult (never throws — failures map to a degraded result).
147
+ */
148
+ review(args: ReviewerInvokeArgs): Promise<ReviewerResult>;
149
+ }
150
+ export interface ReviewerInvokeArgs {
151
+ /** The fully-assembled cross-model reviewer prompt (prompt + spec + context). */
152
+ promptText: string;
153
+ /** Per-call timeout. */
154
+ timeoutMs: number;
155
+ /**
156
+ * Optional provider override — tests inject a stub so no real codex spawn
157
+ * happens. Production passes nothing and the factory builds the real one.
158
+ */
159
+ providerOverride?: {
160
+ evaluate(prompt: string, options?: {
161
+ model?: 'fast' | 'balanced' | 'capable';
162
+ timeoutMs?: number;
163
+ }): Promise<string>;
164
+ };
165
+ }
166
+ /**
167
+ * The supported-reviewer registry. codex first — the order IS the preference
168
+ * order. gemini-cli and others land here as later steps (Out of Scope).
169
+ */
170
+ export declare const SUPPORTED_REVIEWER_FRAMEWORKS: SupportedReviewerFramework[];
171
+ /**
172
+ * Walk the registry in preference order and return the FIRST available
173
+ * framework's detection result. If none is available, returns the codex
174
+ * reason when there's exactly one entry (so the report can be specific), else
175
+ * the generic `no-supported-framework`.
176
+ *
177
+ * SIGNAL-ONLY: never throws, never blocks. A `false` simply routes the skill
178
+ * to the internal-only fallback (spec §4).
179
+ */
180
+ export declare function detectCrossModelReviewer(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
181
+ /**
182
+ * Map a provider rejection into a coarse `degraded` reason. The provider
183
+ * surfaces timeouts, non-zero exits, and (via the circuit breaker) rate
184
+ * limits as thrown Errors; we classify on the message text the same way the
185
+ * rate-limit classifier does.
186
+ */
187
+ export declare function classifyReviewFailure(err: unknown): string;
188
+ /**
189
+ * Parse a reviewer reply into a structured finding. The prompt mandates a
190
+ * `Verdict: CLEAN | MINOR ISSUES | SERIOUS ISSUES` line + a findings list. If
191
+ * the verdict line is unparseable (or the reply is blank), the whole reply is
192
+ * captured as one raw "unstructured external review — read manually" finding
193
+ * (never dropped, never thrown, never zero).
194
+ */
195
+ export declare function parseReviewerReply(raw: string, reviewerTag: string): ReviewFinding;
196
+ export interface ReferencedContextDoc {
197
+ /** Repo-relative path used as the `--- CONTEXT: <path> ---` header. */
198
+ path: string;
199
+ /** The doc's contents (already read by the caller). */
200
+ content: string;
201
+ }
202
+ export interface AssemblePromptInputs {
203
+ /** Contents of skills/spec-converge/templates/reviewer-cross-model.md. */
204
+ reviewerTemplate: string;
205
+ /** The full spec markdown. */
206
+ specMarkdown: string;
207
+ /** Repo-relative spec path, substituted for {SPEC_PATH} in the template. */
208
+ specPath: string;
209
+ /** Referenced architectural context docs (same set internal reviewers see). */
210
+ context?: ReferencedContextDoc[];
211
+ /** Total budget in bytes. Defaults to CONTEXT_BUDGET_BYTES. */
212
+ budgetBytes?: number;
213
+ }
214
+ export interface AssembledPrompt {
215
+ /** The final prompt string fed to the provider. */
216
+ promptText: string;
217
+ /** True when referenced context had to be truncated to fit the budget. */
218
+ truncated: boolean;
219
+ /** Byte size of the assembled prompt. */
220
+ bytes: number;
221
+ }
222
+ /**
223
+ * Assemble the cross-model reviewer prompt: the reviewer template (with
224
+ * `{SPEC_PATH}` substituted) + the full spec + as much referenced context as
225
+ * fits the budget. codex runs with NO repo access, so context is inlined under
226
+ * `--- CONTEXT: <path> ---` headers. The spec is ALWAYS included in full; if
227
+ * referenced docs overflow the budget, they are truncated and a loud
228
+ * truncation note is added so the reviewer knows its view was partial (a
229
+ * silently-truncated review is a trap; a disclosed-partial one is still signal).
230
+ *
231
+ * Truncation is DETERMINISTIC (spec §2, F4): referenced docs are ordered by
232
+ * `orderContextDeterministically` (constitutional/lessons docs first, then the
233
+ * spec-declared link order) BEFORE the budget walk, so the same spec + docs
234
+ * always drop the same docs. When a doc is fully or partially dropped, the
235
+ * truncation note NAMES the affected docs (which were partial, which were fully
236
+ * omitted) — a reviewer must know exactly which context it could not see, not
237
+ * just that "something" was cut.
238
+ */
239
+ export declare function assembleReviewerPrompt(inputs: AssemblePromptInputs): AssembledPrompt;
240
+ /**
241
+ * The discrete cross-model review outcome states the report + frontmatter
242
+ * record. Distinct so "you have no cross-model reviewer" reads differently
243
+ * from "your reviewer was rate-limited this round" from "you chose the fast
244
+ * path" from "the framework was present but NOT ONE round ever succeeded".
245
+ *
246
+ * `degraded-all-rounds` (spec §2/§4, F2) is the SPEC-LEVEL aggregate: a single
247
+ * round's `degraded` lives on a ReviewerResult, but convergence runs many
248
+ * rounds and the spec gets ONE final `cross-model-review:` value. When a
249
+ * framework was present every round but ZERO rounds produced a successful
250
+ * external pass (all degraded), the final flag is `degraded-all-rounds` —
251
+ * treated as loud as `unavailable`, because the spec converged having never
252
+ * once received a real external opinion. This must surface at SPEC level, not
253
+ * hide in per-round notes.
254
+ */
255
+ export type CrossModelFlagStatus = 'available' | 'unavailable' | 'degraded' | 'degraded-all-rounds' | 'skipped-abbreviated';
256
+ export interface CrossModelFlag {
257
+ status: CrossModelFlagStatus;
258
+ /** The `cross-model-review:` frontmatter value. */
259
+ flag: string;
260
+ /** Optional `cross-model-review-reason:` value. */
261
+ reason?: string;
262
+ }
263
+ /**
264
+ * Build the fallback flag for the unavailable / skipped / degraded-all-rounds
265
+ * states. (The available and per-round degraded flags come back on the
266
+ * ReviewerResult.) Centralizes the exact strings the frontmatter writer +
267
+ * report banner consume.
268
+ *
269
+ * `degraded-all-rounds` is the spec-level aggregate the skill writes when a
270
+ * framework was present but no round ever succeeded (see
271
+ * `aggregateRoundOutcomes`).
272
+ */
273
+ export declare function buildCrossModelFlag(status: 'unavailable' | 'skipped-abbreviated' | 'degraded-all-rounds', reason?: string): CrossModelFlag;
274
+ /**
275
+ * Aggregate per-round cross-model outcomes into the ONE final spec-level flag
276
+ * (spec §2/§4, F2). Convergence runs multiple rounds; each round yields a
277
+ * `ReviewerResult` (`ok` / `degraded` / `unavailable`). The skill collects the
278
+ * per-round statuses and calls this to decide what `write-convergence-tag.mjs`
279
+ * stamps:
280
+ *
281
+ * - `skipped-abbreviated` if the author opted out (passed explicitly) — wins
282
+ * over everything, since no external pass was attempted by choice.
283
+ * - `codex-cli:<model>` (the LAST successful round's flag) if ANY round got a
284
+ * real external pass — one genuine outside opinion is enough to say the spec
285
+ * received cross-model review.
286
+ * - `degraded-all-rounds` if a framework was present every round but ZERO
287
+ * rounds succeeded (all degraded) — as loud as `unavailable`.
288
+ * - `unavailable` if no framework was ever available (all rounds unavailable).
289
+ *
290
+ * Returns the `{ flag, reason }` the tag writer + report banner consume.
291
+ */
292
+ export declare function aggregateRoundOutcomes(rounds: ReviewerResult[], opts?: {
293
+ skippedAbbreviated?: boolean;
294
+ }): CrossModelFlag;
295
+ /**
296
+ * The high-level entry the skill driver calls: detect, and if available run
297
+ * the first available framework's reviewer with the assembled prompt;
298
+ * otherwise return the `unavailable` flag. NEVER throws, NEVER blocks.
299
+ *
300
+ * `assembled` is produced by `assembleReviewerPrompt`. `detectInputs` and
301
+ * `providerOverride` exist for tests; production omits them.
302
+ */
303
+ export declare function runCrossModelReview(args: {
304
+ assembled: AssembledPrompt;
305
+ timeoutMs?: number;
306
+ detectInputs?: CrossModelDetectInputs;
307
+ providerOverride?: ReviewerInvokeArgs['providerOverride'];
308
+ }): Promise<ReviewerResult>;
309
+ export {};
310
+ //# sourceMappingURL=crossModelReviewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crossModelReviewer.d.ts","sourceRoot":"","sources":["../../src/core/crossModelReviewer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,489 @@
1
+ /**
2
+ * crossModelReviewer — Step B of the tiered development process.
3
+ *
4
+ * Re-platforms the `/spec-converge` external "cross-model" reviewer onto the
5
+ * agent's own installed `codex` CLI. The external pass that the skill used to
6
+ * describe as running "via the /crossreview pattern" (a never-built
7
+ * placeholder) is now a grounded mechanism: detect whether a supported reviewer
8
+ * framework is installed + authed, assemble the cross-model reviewer prompt
9
+ * (spec + referenced context, bounded to a budget), and run it THROUGH the
10
+ * existing `CodexCliIntelligenceProvider` (the factory with
11
+ * `framework: 'codex-cli'`, model `capable` → GPT-tier).
12
+ *
13
+ * Design invariants (see docs/specs/codex-crossreview-stepB-spec.md):
14
+ * - Detection is a pure function with injectable inputs (no real spawns in
15
+ * unit tests). It is SIGNAL-ONLY — it never throws and never blocks.
16
+ * - Reviewer invocation reuses the provider (its scratch-dir clean-notepad,
17
+ * env allowlist, `--skip-git-repo-check`, and the account-global circuit
18
+ * breaker the factory wraps it in). The ONLY new spawn-adjacent code is
19
+ * prompt assembly + result parsing.
20
+ * - Every failure mode routes toward internal-only convergence or a captured
21
+ * raw finding — never a stall. `unavailable` (no framework) is distinct
22
+ * from `degraded` (framework present, this call failed) and
23
+ * `skipped-abbreviated` (author chose the fast path).
24
+ *
25
+ * codex is the FIRST supported framework; the registry
26
+ * (`SUPPORTED_REVIEWER_FRAMEWORKS`) is the single seam for gemini-cli and
27
+ * others to plug in later (Out of Scope here). Adding a framework is one
28
+ * registry entry + one `id`-union extension — no skill change.
29
+ */
30
+ import fs from 'node:fs';
31
+ import os from 'node:os';
32
+ import path from 'node:path';
33
+ import { detectCodexPath } from './Config.js';
34
+ import { validateRule1 } from '../providers/adapters/openai-codex/credentials.js';
35
+ import { resolveCliModelFlag } from '../providers/adapters/openai-codex/models.js';
36
+ import { buildIntelligenceProvider, } from './intelligenceProviderFactory.js';
37
+ // ── Constants (tunable) ─────────────────────────────────────────────────
38
+ /**
39
+ * Per-call timeout for a cross-model spec review. A reasoning review of a
40
+ * full spec is far heavier than the provider's 30s judgment-call default, so
41
+ * Step B bumps it to 120s (spec §2).
42
+ */
43
+ export const REVIEW_TIMEOUT_MS = 120_000;
44
+ /**
45
+ * Total context budget (spec + referenced docs) inlined into the reviewer
46
+ * prompt. codex runs in an empty read-only scratch dir with no repo access,
47
+ * so referenced context MUST be inlined; this bounds the prompt size (spec §2).
48
+ * The spec is always included in full; referenced context fills the remainder
49
+ * and is truncated (with a loud note) if it overflows.
50
+ */
51
+ export const CONTEXT_BUDGET_BYTES = 60 * 1024;
52
+ /**
53
+ * Deterministic priority ordering for referenced context (spec §2, F4).
54
+ *
55
+ * When the 60KB budget can't hold every referenced doc, truncation MUST be
56
+ * deterministic — the same spec + same docs always drop the same docs — so a
57
+ * review is reproducible and the "what got dropped" note is stable. The
58
+ * constitutional / lessons docs are the highest-value context for a reviewer
59
+ * (they're what the lessons-aware internal reviewer reads), so they are kept
60
+ * FIRST; everything else keeps the spec-declared link order (the order the
61
+ * caller passed the docs in, which is the order they appear in the spec).
62
+ *
63
+ * A doc whose path contains one of these substrings sorts ahead of the rest,
64
+ * in THIS order. Ties (and all non-priority docs) preserve the caller's order
65
+ * via a stable sort.
66
+ */
67
+ export const CONTEXT_PRIORITY_SUBSTRINGS = [
68
+ 'signal-vs-authority',
69
+ 'INSTAR-DESIGN-PRINCIPLES-AND-LESSONS',
70
+ 'STANDARDS-REGISTRY',
71
+ 'integrated-being',
72
+ ];
73
+ /**
74
+ * Return a deterministic priority rank for a context doc path: a small index
75
+ * for a constitutional/lessons doc (earlier substring = smaller rank), or a
76
+ * large sentinel for everything else (so non-priority docs keep their relative
77
+ * order behind the priority ones under a stable sort).
78
+ */
79
+ function contextPriorityRank(docPath) {
80
+ const lower = docPath.toLowerCase();
81
+ for (let i = 0; i < CONTEXT_PRIORITY_SUBSTRINGS.length; i++) {
82
+ if (lower.includes(CONTEXT_PRIORITY_SUBSTRINGS[i].toLowerCase()))
83
+ return i;
84
+ }
85
+ return CONTEXT_PRIORITY_SUBSTRINGS.length;
86
+ }
87
+ /**
88
+ * Order referenced context deterministically: constitutional/lessons docs
89
+ * first (per CONTEXT_PRIORITY_SUBSTRINGS), then the caller's spec-declared link
90
+ * order for the rest. A stable sort on the priority rank achieves both — equal
91
+ * ranks keep their input order. Pure; never mutates the input.
92
+ */
93
+ export function orderContextDeterministically(context) {
94
+ return context
95
+ .map((doc, idx) => ({ doc, idx, rank: contextPriorityRank(doc.path) }))
96
+ .sort((a, b) => a.rank - b.rank || a.idx - b.idx)
97
+ .map((e) => e.doc);
98
+ }
99
+ /** The canonical model tier a heavyweight cross-model review requests. */
100
+ const REVIEW_MODEL_TIER = 'capable';
101
+ /** Resolve the default codex auth.json path (CODEX_HOME-aware). */
102
+ function defaultAuthJsonPath(env) {
103
+ const home = env['CODEX_HOME'] || path.join(os.homedir(), '.codex');
104
+ return path.join(home, 'auth.json');
105
+ }
106
+ /**
107
+ * Is the codex auth.json an OAuth (`tokens.access_token`) shape? This is the
108
+ * subscription-OAuth shape D3 requires. A missing / unreadable / malformed
109
+ * file → false (not authed). Uses the same probe shape as the codex smoketest.
110
+ */
111
+ function authHasOAuthAccessToken(authJsonPath) {
112
+ try {
113
+ const raw = fs.readFileSync(authJsonPath, 'utf-8');
114
+ const parsed = JSON.parse(raw);
115
+ return typeof parsed?.tokens?.access_token === 'string' && parsed.tokens.access_token.length > 0;
116
+ }
117
+ catch {
118
+ // missing / unreadable / malformed → not authed.
119
+ return false;
120
+ }
121
+ }
122
+ /**
123
+ * Detect a codex reviewer. Returns `{ available: true, framework, model }`
124
+ * iff ALL of: codex binary detected, OAuth `access_token` present, Rule-1
125
+ * clean (no raw API key in env or auth.json). Any miss → a specific reason.
126
+ *
127
+ * Pure-ish: all external inputs are injectable. With no inputs it probes the
128
+ * real host. It NEVER throws.
129
+ */
130
+ export function detectCodexReviewer(inputs = {}) {
131
+ const env = inputs.env ?? process.env;
132
+ const now = inputs.now ?? new Date();
133
+ const codexPath = inputs.codexPathDetected !== undefined ? inputs.codexPathDetected : detectCodexPath();
134
+ const authJsonPath = inputs.authJsonPath ?? defaultAuthJsonPath(env);
135
+ // 1. Binary present?
136
+ if (!codexPath) {
137
+ return { available: false, reason: 'codex-not-installed' };
138
+ }
139
+ // 2. Rule-1 clean? (API-key forbidden — env OPENAI_API_KEY or auth.json
140
+ // API-key shape). Reuses existing policy rather than inventing one.
141
+ const rule1 = validateRule1(env, authJsonPath, now);
142
+ if (!rule1.ok) {
143
+ // The killswitch-expired / apikey-detected codes all collapse to the same
144
+ // policy outcome here: a forbidden credential shape → reviewer unavailable.
145
+ return { available: false, reason: 'codex-auth-apikey-forbidden' };
146
+ }
147
+ // 3. Authed via subscription OAuth?
148
+ if (!authHasOAuthAccessToken(authJsonPath)) {
149
+ return { available: false, reason: 'codex-not-authed' };
150
+ }
151
+ return {
152
+ available: true,
153
+ framework: 'codex-cli',
154
+ model: resolveCliModelFlag(REVIEW_MODEL_TIER),
155
+ };
156
+ }
157
+ /**
158
+ * The codex reviewer entry. Detection delegates to `detectCodexReviewer`;
159
+ * `review` routes through the factory-built `CodexCliIntelligenceProvider`.
160
+ */
161
+ const codexReviewer = {
162
+ id: 'codex-cli',
163
+ detect: (inputs) => detectCodexReviewer(inputs),
164
+ review: async (args) => {
165
+ const detection = detectCodexReviewer();
166
+ const model = detection.model ?? resolveCliModelFlag(REVIEW_MODEL_TIER);
167
+ const tag = `cross-model:codex-cli:${model}`;
168
+ // Build (or accept an injected) provider. The factory wraps it in the
169
+ // account-global circuit breaker, so a rate-limited review degrades the
170
+ // same way every other instar LLM call does.
171
+ const provider = args.providerOverride ??
172
+ buildIntelligenceProvider({ framework: 'codex-cli' });
173
+ if (!provider) {
174
+ // Binary vanished between detect and review (or detection said
175
+ // unavailable and review was called anyway). Degraded, not a throw.
176
+ return {
177
+ status: 'degraded',
178
+ framework: 'codex-cli',
179
+ model,
180
+ reason: 'provider-unavailable',
181
+ flag: `cross-model-review: codex-cli:${model} (degraded: provider-unavailable)`,
182
+ };
183
+ }
184
+ let raw;
185
+ try {
186
+ raw = await provider.evaluate(args.promptText, {
187
+ model: REVIEW_MODEL_TIER,
188
+ timeoutMs: args.timeoutMs,
189
+ });
190
+ }
191
+ catch (err) {
192
+ const reason = classifyReviewFailure(err);
193
+ return {
194
+ status: 'degraded',
195
+ framework: 'codex-cli',
196
+ model,
197
+ reason,
198
+ flag: `cross-model-review: codex-cli:${model} (degraded: ${reason})`,
199
+ };
200
+ }
201
+ const parsed = parseReviewerReply(raw, tag);
202
+ return {
203
+ status: 'ok',
204
+ framework: 'codex-cli',
205
+ model,
206
+ verdict: parsed.verdict,
207
+ findings: [parsed],
208
+ flag: `cross-model-review: codex-cli:${model}`,
209
+ };
210
+ },
211
+ };
212
+ /**
213
+ * The supported-reviewer registry. codex first — the order IS the preference
214
+ * order. gemini-cli and others land here as later steps (Out of Scope).
215
+ */
216
+ export const SUPPORTED_REVIEWER_FRAMEWORKS = [codexReviewer];
217
+ /**
218
+ * Walk the registry in preference order and return the FIRST available
219
+ * framework's detection result. If none is available, returns the codex
220
+ * reason when there's exactly one entry (so the report can be specific), else
221
+ * the generic `no-supported-framework`.
222
+ *
223
+ * SIGNAL-ONLY: never throws, never blocks. A `false` simply routes the skill
224
+ * to the internal-only fallback (spec §4).
225
+ */
226
+ export function detectCrossModelReviewer(inputs = {}) {
227
+ for (const framework of SUPPORTED_REVIEWER_FRAMEWORKS) {
228
+ const result = framework.detect(inputs);
229
+ if (result.available)
230
+ return result;
231
+ }
232
+ // Nothing available. Surface the single framework's specific reason when
233
+ // there's only one entry (codex today); otherwise the generic reason.
234
+ if (SUPPORTED_REVIEWER_FRAMEWORKS.length === 1) {
235
+ return SUPPORTED_REVIEWER_FRAMEWORKS[0].detect(inputs);
236
+ }
237
+ return { available: false, reason: 'no-supported-framework' };
238
+ }
239
+ // ── Failure classification ──────────────────────────────────────────────
240
+ /**
241
+ * Map a provider rejection into a coarse `degraded` reason. The provider
242
+ * surfaces timeouts, non-zero exits, and (via the circuit breaker) rate
243
+ * limits as thrown Errors; we classify on the message text the same way the
244
+ * rate-limit classifier does.
245
+ */
246
+ export function classifyReviewFailure(err) {
247
+ const msg = err instanceof Error ? err.message : String(err);
248
+ const lower = msg.toLowerCase();
249
+ if (/circuit breaker|rate.?limit|usage limit|quota|429|too many requests/.test(lower)) {
250
+ return 'rate-limited';
251
+ }
252
+ if (/timed out|timeout|etimedout|killed/.test(lower)) {
253
+ return 'timeout';
254
+ }
255
+ return 'error';
256
+ }
257
+ // ── Reviewer reply parsing ──────────────────────────────────────────────
258
+ /**
259
+ * Parse a reviewer reply into a structured finding. The prompt mandates a
260
+ * `Verdict: CLEAN | MINOR ISSUES | SERIOUS ISSUES` line + a findings list. If
261
+ * the verdict line is unparseable (or the reply is blank), the whole reply is
262
+ * captured as one raw "unstructured external review — read manually" finding
263
+ * (never dropped, never thrown, never zero).
264
+ */
265
+ export function parseReviewerReply(raw, reviewerTag) {
266
+ const text = (raw ?? '').trim();
267
+ if (!text) {
268
+ return {
269
+ reviewer: reviewerTag,
270
+ verdict: 'UNKNOWN',
271
+ body: '(empty reviewer reply — codex returned no output; read manually)',
272
+ unstructured: true,
273
+ };
274
+ }
275
+ const verdict = extractVerdict(text);
276
+ if (verdict === 'UNKNOWN') {
277
+ return {
278
+ reviewer: reviewerTag,
279
+ verdict: 'UNKNOWN',
280
+ body: `unstructured external review — read manually:\n${text}`,
281
+ unstructured: true,
282
+ };
283
+ }
284
+ return {
285
+ reviewer: reviewerTag,
286
+ verdict,
287
+ body: text,
288
+ };
289
+ }
290
+ /**
291
+ * Extract the verdict from a reviewer reply. Looks for a `Verdict:` line and
292
+ * matches one of the three canonical values (case-insensitive, tolerant of
293
+ * surrounding markdown like `**Verdict: SERIOUS ISSUES**`). Returns 'UNKNOWN'
294
+ * when none is found.
295
+ */
296
+ function extractVerdict(text) {
297
+ // Find a line mentioning "Verdict" and inspect its content.
298
+ const verdictLine = text
299
+ .split('\n')
300
+ .find((l) => /verdict/i.test(l));
301
+ const haystack = (verdictLine ?? text).toUpperCase();
302
+ // Order matters: check the most specific multi-word verdicts first.
303
+ if (haystack.includes('SERIOUS ISSUES'))
304
+ return 'SERIOUS ISSUES';
305
+ if (haystack.includes('MINOR ISSUES'))
306
+ return 'MINOR ISSUES';
307
+ if (haystack.includes('CLEAN'))
308
+ return 'CLEAN';
309
+ return 'UNKNOWN';
310
+ }
311
+ /**
312
+ * Assemble the cross-model reviewer prompt: the reviewer template (with
313
+ * `{SPEC_PATH}` substituted) + the full spec + as much referenced context as
314
+ * fits the budget. codex runs with NO repo access, so context is inlined under
315
+ * `--- CONTEXT: <path> ---` headers. The spec is ALWAYS included in full; if
316
+ * referenced docs overflow the budget, they are truncated and a loud
317
+ * truncation note is added so the reviewer knows its view was partial (a
318
+ * silently-truncated review is a trap; a disclosed-partial one is still signal).
319
+ *
320
+ * Truncation is DETERMINISTIC (spec §2, F4): referenced docs are ordered by
321
+ * `orderContextDeterministically` (constitutional/lessons docs first, then the
322
+ * spec-declared link order) BEFORE the budget walk, so the same spec + docs
323
+ * always drop the same docs. When a doc is fully or partially dropped, the
324
+ * truncation note NAMES the affected docs (which were partial, which were fully
325
+ * omitted) — a reviewer must know exactly which context it could not see, not
326
+ * just that "something" was cut.
327
+ */
328
+ export function assembleReviewerPrompt(inputs) {
329
+ const budget = inputs.budgetBytes ?? CONTEXT_BUDGET_BYTES;
330
+ const template = inputs.reviewerTemplate.replace(/\{SPEC_PATH\}/g, inputs.specPath);
331
+ const header = `${template}\n\n--- SPEC UNDER REVIEW: ${inputs.specPath} ---\n${inputs.specMarkdown}\n`;
332
+ const parts = [header];
333
+ let used = Buffer.byteLength(header, 'utf-8');
334
+ let truncated = false;
335
+ // Deterministic priority order: constitutional/lessons docs first, then the
336
+ // spec-declared link order. Same inputs always drop the same docs.
337
+ const context = orderContextDeterministically(inputs.context ?? []);
338
+ // Track exactly which docs were partially included vs fully dropped so the
339
+ // truncation note can NAME them (F4 — a named-partial review is signal; a
340
+ // "something was cut" review is a trap).
341
+ let partialDoc = null;
342
+ const droppedDocs = [];
343
+ for (let i = 0; i < context.length; i++) {
344
+ const doc = context[i];
345
+ const docHeader = `\n--- CONTEXT: ${doc.path} ---\n`;
346
+ const docBlock = `${docHeader}${doc.content}\n`;
347
+ const docBytes = Buffer.byteLength(docBlock, 'utf-8');
348
+ if (!truncated && used + docBytes <= budget) {
349
+ parts.push(docBlock);
350
+ used += docBytes;
351
+ continue;
352
+ }
353
+ // Budget exceeded at this doc. Include as much of THIS doc as the remaining
354
+ // budget allows (header always; body sliced), record it as PARTIAL, then
355
+ // mark every remaining doc as fully DROPPED. We do NOT break — we keep
356
+ // walking so the note can name all the dropped docs, not just the first.
357
+ if (!truncated) {
358
+ const remaining = budget - used - Buffer.byteLength(docHeader, 'utf-8');
359
+ if (remaining > 0) {
360
+ // Slice by bytes safely (avoid splitting a multibyte char by slicing
361
+ // the buffer then decoding with replacement tolerated).
362
+ const sliced = Buffer.from(doc.content, 'utf-8').subarray(0, remaining).toString('utf-8');
363
+ parts.push(`${docHeader}${sliced}`);
364
+ partialDoc = doc.path;
365
+ }
366
+ else {
367
+ // Not even the header fits — this doc is fully dropped too.
368
+ droppedDocs.push(doc.path);
369
+ }
370
+ truncated = true;
371
+ continue;
372
+ }
373
+ // Already truncated — every subsequent doc is fully omitted.
374
+ droppedDocs.push(doc.path);
375
+ }
376
+ if (truncated) {
377
+ const detail = [];
378
+ if (partialDoc)
379
+ detail.push(`PARTIAL (cut mid-document): ${partialDoc}`);
380
+ if (droppedDocs.length > 0)
381
+ detail.push(`FULLY OMITTED: ${droppedDocs.join(', ')}`);
382
+ const named = detail.length > 0 ? ` ${detail.join('. ')}.` : '';
383
+ parts.push('\n\n--- NOTE: referenced context was TRUNCATED to fit the review budget.' +
384
+ named +
385
+ ' Your view of the supporting docs is PARTIAL — flag any finding that ' +
386
+ 'depends on context you could not see. ---\n');
387
+ }
388
+ const promptText = parts.join('');
389
+ return {
390
+ promptText,
391
+ truncated,
392
+ bytes: Buffer.byteLength(promptText, 'utf-8'),
393
+ };
394
+ }
395
+ /**
396
+ * Build the fallback flag for the unavailable / skipped / degraded-all-rounds
397
+ * states. (The available and per-round degraded flags come back on the
398
+ * ReviewerResult.) Centralizes the exact strings the frontmatter writer +
399
+ * report banner consume.
400
+ *
401
+ * `degraded-all-rounds` is the spec-level aggregate the skill writes when a
402
+ * framework was present but no round ever succeeded (see
403
+ * `aggregateRoundOutcomes`).
404
+ */
405
+ export function buildCrossModelFlag(status, reason) {
406
+ if (status === 'unavailable') {
407
+ return { status, flag: 'cross-model-review: unavailable', reason };
408
+ }
409
+ if (status === 'degraded-all-rounds') {
410
+ return { status, flag: 'cross-model-review: degraded-all-rounds', reason };
411
+ }
412
+ return { status, flag: 'cross-model-review: skipped-abbreviated', reason };
413
+ }
414
+ /**
415
+ * Aggregate per-round cross-model outcomes into the ONE final spec-level flag
416
+ * (spec §2/§4, F2). Convergence runs multiple rounds; each round yields a
417
+ * `ReviewerResult` (`ok` / `degraded` / `unavailable`). The skill collects the
418
+ * per-round statuses and calls this to decide what `write-convergence-tag.mjs`
419
+ * stamps:
420
+ *
421
+ * - `skipped-abbreviated` if the author opted out (passed explicitly) — wins
422
+ * over everything, since no external pass was attempted by choice.
423
+ * - `codex-cli:<model>` (the LAST successful round's flag) if ANY round got a
424
+ * real external pass — one genuine outside opinion is enough to say the spec
425
+ * received cross-model review.
426
+ * - `degraded-all-rounds` if a framework was present every round but ZERO
427
+ * rounds succeeded (all degraded) — as loud as `unavailable`.
428
+ * - `unavailable` if no framework was ever available (all rounds unavailable).
429
+ *
430
+ * Returns the `{ flag, reason }` the tag writer + report banner consume.
431
+ */
432
+ export function aggregateRoundOutcomes(rounds, opts = {}) {
433
+ if (opts.skippedAbbreviated) {
434
+ return buildCrossModelFlag('skipped-abbreviated');
435
+ }
436
+ if (rounds.length === 0) {
437
+ // No rounds recorded at all — treat as no external reviewer available.
438
+ return buildCrossModelFlag('unavailable', 'no-rounds-recorded');
439
+ }
440
+ // Any successful round → the spec received a real external opinion. Use the
441
+ // LAST successful round's flag (the freshest pass on the most-converged spec).
442
+ const successful = rounds.filter((r) => r.status === 'ok');
443
+ if (successful.length > 0) {
444
+ const last = successful[successful.length - 1];
445
+ return { status: 'available', flag: last.flag, ...(last.reason ? { reason: last.reason } : {}) };
446
+ }
447
+ // No successes. Was a framework ever present? If ANY round degraded (vs
448
+ // unavailable), the framework was there but never delivered → all-rounds.
449
+ const anyDegraded = rounds.some((r) => r.status === 'degraded');
450
+ if (anyDegraded) {
451
+ // Surface the most recent degraded reason for the `-reason` field.
452
+ const lastDegraded = [...rounds].reverse().find((r) => r.status === 'degraded');
453
+ return buildCrossModelFlag('degraded-all-rounds', lastDegraded?.reason);
454
+ }
455
+ // Every round was unavailable (no framework, ever).
456
+ const lastUnavailable = [...rounds].reverse().find((r) => r.status === 'unavailable');
457
+ return buildCrossModelFlag('unavailable', lastUnavailable?.reason);
458
+ }
459
+ /**
460
+ * The high-level entry the skill driver calls: detect, and if available run
461
+ * the first available framework's reviewer with the assembled prompt;
462
+ * otherwise return the `unavailable` flag. NEVER throws, NEVER blocks.
463
+ *
464
+ * `assembled` is produced by `assembleReviewerPrompt`. `detectInputs` and
465
+ * `providerOverride` exist for tests; production omits them.
466
+ */
467
+ export async function runCrossModelReview(args) {
468
+ const detection = detectCrossModelReviewer(args.detectInputs);
469
+ if (!detection.available) {
470
+ const flag = buildCrossModelFlag('unavailable', detection.reason);
471
+ return {
472
+ status: 'unavailable',
473
+ reason: detection.reason,
474
+ flag: flag.flag,
475
+ };
476
+ }
477
+ const framework = SUPPORTED_REVIEWER_FRAMEWORKS.find((f) => f.id === detection.framework);
478
+ if (!framework) {
479
+ // Defensive: detection named a framework with no registry entry.
480
+ const flag = buildCrossModelFlag('unavailable', 'no-supported-framework');
481
+ return { status: 'unavailable', reason: 'no-supported-framework', flag: flag.flag };
482
+ }
483
+ return framework.review({
484
+ promptText: args.assembled.promptText,
485
+ timeoutMs: args.timeoutMs ?? REVIEW_TIMEOUT_MS,
486
+ ...(args.providerOverride ? { providerOverride: args.providerOverride } : {}),
487
+ });
488
+ }
489
+ //# sourceMappingURL=crossModelReviewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crossModelReviewer.js","sourceRoot":"","sources":["../../src/core/crossModelReviewer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.191",
3
+ "version": "1.3.192",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-01T22:39:21.302Z",
5
- "instarVersion": "1.3.191",
4
+ "generatedAt": "2026-06-02T00:19:44.300Z",
5
+ "instarVersion": "1.3.192",
6
6
  "entryCount": 193,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,37 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Spec-review-convergence's external **cross-model reviewer** is now real and framework-native:
9
+ instead of a placeholder API path, it detects the installed **codex CLI** and runs a
10
+ GPT-tier second opinion through the existing hardened codex provider — so any instar agent
11
+ with codex gets cross-model review for free, no API keys. When no supported reviewer is
12
+ installed (or a call fails, or convergence is abbreviated) the spec carries a loud,
13
+ honest `cross-model-review:` flag; the review is **signal, never a gate**. Step B of the
14
+ tiered-development project.
15
+
16
+ ## What to Tell Your User
17
+
18
+ Nothing to do — this only affects how the agent reviews its own instar specs. Specs now get
19
+ a real outside-model opinion when codex is present, and a clearly-marked note when they
20
+ don't, so a spec that never got an external review can't quietly look like it did.
21
+
22
+ ## Summary of New Capabilities
23
+
24
+ - `src/core/crossModelReviewer.ts` — codex-CLI reviewer detection + a supported-reviewer
25
+ registry + the reviewer driver (via the existing codex provider) + the honest flag
26
+ machinery (`codex-cli:<model>` / `degraded` / `degraded-all-rounds` / `unavailable` /
27
+ `skipped-abbreviated`), with deterministic, named context truncation.
28
+ - `skills/spec-converge/scripts/cross-model-review.mjs` — the CLI the convergence skill
29
+ calls; `write-convergence-tag.mjs` gains `--cross-model-review`; `SKILL.md` + the reviewer
30
+ prompt template now describe the real, inlined-context, single-codex-pass mechanism.
31
+
32
+ ## Evidence
33
+
34
+ - `npx tsc --noEmit` exit 0; Step-B tests 53 green (45 + 5 unit, 3 integration); `npm run
35
+ lint` green. Detection live-validated on this host: `{"available":true,
36
+ "framework":"codex-cli","model":"gpt-5.5"}`.
37
+ - Dev-only tooling: `/spec-converge` is not shipped to end-agent homes, so no migration.
@@ -0,0 +1,83 @@
1
+ # Side-Effects Review — Cross-model review on the codex CLI (Step B)
2
+
3
+ **Slug:** `codex-crossreview-stepB`
4
+ **Date:** 2026-06-01
5
+ **Author:** echo
6
+ **Spec:** `docs/specs/codex-crossreview-stepB-spec.md` (approved by Justin + abbreviated convergence, 5 findings folded)
7
+ **Project:** Step B of the Tiered Development Process (`docs/projects/tiered-dev-process/PROJECT.md`)
8
+
9
+ ## Summary of the change
10
+
11
+ Re-platforms the external **cross-model reviewer** in spec-review-convergence from the
12
+ (placeholder) `/crossreview` API path onto the **installed codex CLI**, so every instar
13
+ agent with codex gets a GPT-tier second opinion for free, no API keys. Honors the
14
+ constitution's **Signal vs. Authority** — the external review is *signal*, never a
15
+ blocking authority; an absent/failed reviewer is disclosed loudly, never faked.
16
+
17
+ **Files changed (in-scope):**
18
+ - `src/core/crossModelReviewer.ts` (NEW) — detection (`detectCodexReviewer` /
19
+ `detectCrossModelReviewer`: codex binary present AND OAuth `tokens.access_token` AND
20
+ Rule-1 clean), the `SUPPORTED_REVIEWER_FRAMEWORKS` registry (codex first; one entry per
21
+ future framework), the reviewer driver (routes the cross-model prompt through the
22
+ existing `buildIntelligenceProvider({framework:'codex-cli'})` → gpt-5.5, 120s, reusing
23
+ the provider's env allowlist + the factory circuit breaker), `assembleReviewerPrompt`
24
+ (inlines the spec in full + referenced context under a 60KB budget with **deterministic**
25
+ priority order — constitutional docs first — and a truncation note that **names** the
26
+ partial + omitted docs), `parseReviewerReply` (never throws/zeroes), and the flag
27
+ machinery: `buildCrossModelFlag` + `aggregateRoundOutcomes` producing one of
28
+ `codex-cli:<model>` (ran) / `degraded` / `degraded-all-rounds` / `unavailable` /
29
+ `skipped-abbreviated`. Never throws, never blocks.
30
+ - `skills/spec-converge/scripts/cross-model-review.mjs` (NEW) — the thin CLI the skill
31
+ shells out to (does only repo file-I/O — codex is sandboxed with no repo access —
32
+ delegating logic to compiled `dist/core/crossModelReviewer.js`; always exits 0).
33
+ - `skills/spec-converge/scripts/write-convergence-tag.mjs` — `--cross-model-review` /
34
+ `--cross-model-reason` args writing the `cross-model-review:` frontmatter field
35
+ (idempotent, YAML-safe quoting).
36
+ - `skills/spec-converge/SKILL.md` — replaced the `/crossreview` placeholder with the real
37
+ invocation; Phase 3 documents per-round aggregation (→ `degraded-all-rounds` when a
38
+ framework was present but **zero** rounds got a successful pass); Phase 4 renders a loud
39
+ `⚠` banner for **every** non-ran state (a real pass is the only unmarked one); Phase 5
40
+ the valid flag values. Internal reviewers + the Standards-Conformance Gate unchanged.
41
+
42
+ **Files changed (not in-scope):** `skills/spec-converge/templates/reviewer-cross-model.md`
43
+ (patched: spec + context are "inlined below" — no file-read instruction codex can't
44
+ follow — and dropped the three-phantom-model GPT/Gemini/Grok framing → one codex pass);
45
+ the spec + ELI16; tests (`tests/unit/crossModelReviewer.test.ts` 45,
46
+ `tests/unit/write-convergence-tag-crossmodel.test.ts` 5,
47
+ `tests/integration/cross-model-review-flow.test.ts` 3).
48
+
49
+ ## Blast radius
50
+
51
+ Additive. The new module is pure/injectable except the one provider call (reusing the
52
+ hardened codex provider). The convergence flow gains a real external reviewer where it had
53
+ a placeholder; the internal reviewers, the lessons-aware (non-skippable) reviewer, and the
54
+ Standards-Conformance gate are untouched. The reviewer never throws and never blocks — the
55
+ worst case is a loudly-disclosed `unavailable` / `degraded` / `degraded-all-rounds` flag.
56
+ The `cross-model-review:` flag is **disclosure, not a gate**: it does NOT touch the
57
+ `review-convergence` + `approved` enforcement in `instar-dev-precommit.js`.
58
+
59
+ ## Risks considered
60
+
61
+ - **Could a failed/absent review read as a clean pass?** No (convergence Finding 1/2): every
62
+ non-ran state carries a loud `⚠` banner, and a convergence where a framework was present
63
+ but *no* round ever succeeded collapses to a spec-level `degraded-all-rounds` (as loud as
64
+ `unavailable`) — "never actually got an external opinion" surfaces at spec level.
65
+ - **Silent partial context?** No (Finding 4): truncation is deterministic (constitutional
66
+ docs first) and the note names the partial + fully-omitted docs.
67
+ - **API-key leak through codex?** No — reuses `buildCodexChildEnv()`'s allowlist; Rule-1
68
+ (`validateRule1`) forbids an API-key-shaped credential; detection requires OAuth.
69
+ - **Cost?** Worst case ~10 capable-tier calls per spec (the 10-round convergence cap × 1
70
+ call/round), bounded by the account circuit breaker. Stated in the spec.
71
+
72
+ ## Migration parity
73
+
74
+ **None needed — verified.** `/spec-converge` is dev-only tooling: it is NOT in
75
+ `package.json` `files[]` and NOT installed by `installBuiltinSkills()`, so it never lands
76
+ in an end-agent home. No `PostUpdateMigrator` change. (The agents that *develop* instar get
77
+ it from the repo.)
78
+
79
+ ## Tests / lint
80
+
81
+ `npx tsc --noEmit` exit 0; Step-B tests 53 green (45+5 unit, 3 integration); `npm run lint`
82
+ (tsc + destructive/LLM-http/url-log/codex-drift) green. (The worktree's ~28 pre-existing
83
+ local failures — node v25.6.1 + real-config + tunnel — are independent and CI-green.)