aiki-cli 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -5
- package/README.md +73 -15
- package/dist/bench/idea-v3-bench.js +104 -5
- package/dist/bench/idea-v3-rating.js +159 -4
- package/dist/cli/bench.js +5 -5
- package/dist/cli/index.js +12 -2
- package/dist/cli/resume.js +20 -0
- package/dist/cli/run.js +76 -5
- package/dist/cli/serve.js +48 -0
- package/dist/config/config.js +7 -2
- package/dist/council/view.js +64 -15
- package/dist/orchestration/auto-profile.js +97 -0
- package/dist/orchestration/claim-groups.js +56 -0
- package/dist/orchestration/context.js +69 -6
- package/dist/orchestration/decision-dossier.js +450 -89
- package/dist/orchestration/decision-graph.js +33 -2
- package/dist/orchestration/engine.js +8 -4
- package/dist/orchestration/evidence-origin.js +17 -0
- package/dist/orchestration/jsonStage.js +47 -5
- package/dist/orchestration/legacy-idea-adapter.js +3 -0
- package/dist/orchestration/modes.js +18 -10
- package/dist/orchestration/preflight.js +36 -3
- package/dist/orchestration/quick-analysis.js +29 -7
- package/dist/orchestration/sanitize-paths.js +10 -0
- package/dist/orchestration/stages/s10-render.js +196 -84
- package/dist/orchestration/stages/s4-analyze.js +10 -2
- package/dist/orchestration/stages/s4b-challenge.js +97 -0
- package/dist/orchestration/stages/s5-drift.js +13 -3
- package/dist/orchestration/stages/s6-positions.js +18 -0
- package/dist/orchestration/stages/s7-decision-graph.js +3 -0
- package/dist/orchestration/stages/s8-verify.js +66 -12
- package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
- package/dist/orchestration/stages/s9-judge.js +52 -14
- package/dist/orchestration/stages/s9b-plan.js +227 -48
- package/dist/orchestration/url-sources.js +21 -0
- package/dist/providers/adapter-core.js +1 -1
- package/dist/providers/claude.js +18 -0
- package/dist/schemas/index.js +112 -3
- package/dist/serve/flight-deck.js +830 -0
- package/dist/serve/followup.js +50 -0
- package/dist/serve/frames.js +168 -0
- package/dist/serve/gates.js +72 -0
- package/dist/serve/projections.js +283 -0
- package/dist/serve/server.js +219 -0
- package/dist/serve/threads.js +145 -0
- package/dist/serve-ui/Five.png +0 -0
- package/dist/serve-ui/app.js +820 -0
- package/dist/serve-ui/index.html +171 -0
- package/dist/serve-ui/workspace.css +662 -0
- package/dist/skills/idea-refinement/analyst.md +5 -5
- package/dist/skills/idea-refinement/chair.md +18 -0
- package/dist/skills/idea-refinement/economics-delivery.md +11 -0
- package/dist/skills/idea-refinement/market-adoption.md +12 -0
- package/dist/skills/idea-refinement/planner.md +25 -19
- package/dist/skills/idea-refinement/rebuttal.md +15 -0
- package/dist/skills/idea-refinement/verifier.md +17 -0
- package/dist/storage/runs.js +2 -1
- package/dist/workflows/idea-refinement.js +130 -24
- package/package.json +2 -2
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
// S8 — independently verify graph-selected claims from stored evidence/calculation references.
|
|
2
2
|
// Provider identity stays hidden; model output is translated back to graph evidence IDs before S9.
|
|
3
3
|
import { selectEscalations } from '../decision-graph.js';
|
|
4
|
+
import { sanitizeClaimGroups } from '../claim-groups.js';
|
|
4
5
|
import { ClaimVerificationSet } from '../../schemas/index.js';
|
|
5
6
|
import { isFatal, StageError } from '../context.js';
|
|
6
7
|
import { jsonCall } from '../jsonStage.js';
|
|
8
|
+
import { loadSkill } from '../skills.js';
|
|
7
9
|
const S8_PROMPT = `ROLE: Independent verifier. Check each anonymous decision claim below against the
|
|
8
10
|
provided evidence cards and deterministic calculation checks. Output ONLY JSON:
|
|
9
11
|
{"verifications": [{"claim_id": "<G-id>", "status": "VERIFIED|PARTIAL|CONTRADICTED|UNVERIFIABLE",
|
|
10
12
|
"reasoning": "<concise reason>", "evidence_ids": ["<E-id>"],
|
|
11
|
-
"calculation_check": "PASS|FAIL|NOT_APPLICABLE", "missing_evidence": ["<gap>"]}]
|
|
13
|
+
"calculation_check": "PASS|FAIL|NOT_APPLICABLE", "missing_evidence": ["<gap>"]}],
|
|
14
|
+
"claim_groups": [{"ids": ["<G-id>", "<G-id>"], "relation": "SAME|OPPOSES"}]}
|
|
12
15
|
VERIFIED = the proposition is supported. CONTRADICTED = it is refuted. PARTIAL = evidence is mixed or
|
|
13
16
|
incomplete. UNVERIFIABLE = the supplied sources cannot decide. Cite only supplied evidence IDs. Model
|
|
14
17
|
knowledge cannot verify a current, numeric, legal, medical, financial, market, or regulatory fact.
|
|
15
|
-
Judge each item independently; no verdict quota.
|
|
16
|
-
|
|
18
|
+
Judge each item independently; no verdict quota.
|
|
19
|
+
claim_groups: scan ALL CLAIMS below (not only ITEMS). Group ids that assert the SAME proposition in
|
|
20
|
+
different words (relation "SAME") and pair ids that directly contradict each other (relation
|
|
21
|
+
"OPPOSES"). Only group claims from different seats; use existing claim ids only; omit the field when
|
|
22
|
+
there are none. JSON only.{{SKILL}}
|
|
23
|
+
ITEMS: {{ITEMS_JSON}}
|
|
24
|
+
ALL CLAIMS (id, proposition, seat — for claim_groups only): {{CLAIMS_JSON}}`;
|
|
25
|
+
export function selectVerificationTargets(claims, max) {
|
|
26
|
+
return claims
|
|
27
|
+
.map((claim, index) => ({ claim, index }))
|
|
28
|
+
.sort((left, right) => Number(right.claim.nature === 'FACTUAL') - Number(left.claim.nature === 'FACTUAL') || left.index - right.index)
|
|
29
|
+
.slice(0, max)
|
|
30
|
+
.map(({ claim }) => claim);
|
|
31
|
+
}
|
|
32
|
+
export function selectVerificationEscalations(graph, max = 8) {
|
|
33
|
+
const candidates = selectEscalations(graph, { max: graph.claims.length });
|
|
34
|
+
const byId = new Map(candidates.map((candidate) => [candidate.claim_id, candidate]));
|
|
35
|
+
const claims = candidates.map((candidate) => graph.claims.find((claim) => claim.id === candidate.claim_id));
|
|
36
|
+
return selectVerificationTargets(claims, max).map((claim) => byId.get(claim.id));
|
|
37
|
+
}
|
|
17
38
|
function evidenceIdsForClaim(graph, claimId) {
|
|
18
39
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
19
40
|
const claim = graph.claims.find((item) => item.id === claimId);
|
|
@@ -25,14 +46,18 @@ function evidenceIdsForClaim(graph, claimId) {
|
|
|
25
46
|
.flatMap((calculation) => calculation.inputs.flatMap((input) => input.evidence_ids));
|
|
26
47
|
return new Set([...positionEvidence, ...calculationEvidence]);
|
|
27
48
|
}
|
|
28
|
-
|
|
49
|
+
/** Slim, reusable packet seam: selected claims plus only their linked evidence/calculations. */
|
|
50
|
+
export function buildClaimPackets(graph, targets = selectVerificationEscalations(graph)) {
|
|
29
51
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
30
52
|
const evidenceRefs = new Map(graph.evidence.map((evidence, index) => [`E${index + 1}`, evidence.id]));
|
|
31
53
|
const aliasByEvidence = new Map([...evidenceRefs].map(([alias, id]) => [id, alias]));
|
|
32
54
|
const evidenceById = new Map(graph.evidence.map((evidence) => [evidence.id, evidence]));
|
|
33
|
-
const
|
|
55
|
+
const allowedEvidence = new Map();
|
|
56
|
+
const packets = targets.map((escalation) => {
|
|
34
57
|
const claim = graph.claims.find((item) => item.id === escalation.claim_id);
|
|
35
58
|
const linkedEvidenceIds = evidenceIdsForClaim(graph, claim.id);
|
|
59
|
+
allowedEvidence.set(claim.id, new Set([...linkedEvidenceIds]
|
|
60
|
+
.map((id) => aliasByEvidence.get(id)).filter((id) => id !== undefined)));
|
|
36
61
|
return {
|
|
37
62
|
id: claim.id,
|
|
38
63
|
kind: escalation.kind,
|
|
@@ -64,20 +89,47 @@ function verifierInput(graph) {
|
|
|
64
89
|
evidence_hole: graph.holes.evidence.find((hole) => hole.claim_id === claim.id)?.reason,
|
|
65
90
|
};
|
|
66
91
|
});
|
|
67
|
-
return {
|
|
92
|
+
return { packets, evidenceRefs, allowedEvidence };
|
|
93
|
+
}
|
|
94
|
+
function verifierInput(graph, skill = '') {
|
|
95
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
96
|
+
const { packets, evidenceRefs } = buildClaimPackets(graph);
|
|
97
|
+
// Anonymous seat aliases (provider identity stays hidden): S1, S2, … by first appearance.
|
|
98
|
+
const seatAlias = new Map();
|
|
99
|
+
for (const position of graph.positions) {
|
|
100
|
+
if (!seatAlias.has(position.provider))
|
|
101
|
+
seatAlias.set(position.provider, `S${seatAlias.size + 1}`);
|
|
102
|
+
}
|
|
103
|
+
const claimsIndex = graph.claims.map((claim) => ({
|
|
104
|
+
id: claim.id,
|
|
105
|
+
proposition: claim.proposition,
|
|
106
|
+
seats: [...new Set(claim.position_ids.map((id) => seatAlias.get(positionById.get(id)?.provider ?? '') ?? '?'))],
|
|
107
|
+
}));
|
|
108
|
+
const prompt = S8_PROMPT
|
|
109
|
+
.replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
|
|
110
|
+
.replace('{{ITEMS_JSON}}', JSON.stringify(packets, null, 2))
|
|
111
|
+
.replace('{{CLAIMS_JSON}}', JSON.stringify(claimsIndex, null, 2));
|
|
112
|
+
return { prompt, evidenceRefs };
|
|
68
113
|
}
|
|
69
|
-
export function buildVerifierPrompt(graph) {
|
|
70
|
-
return verifierInput(graph).prompt;
|
|
114
|
+
export function buildVerifierPrompt(graph, skill = '') {
|
|
115
|
+
return verifierInput(graph, skill).prompt;
|
|
71
116
|
}
|
|
72
117
|
/** Validate every S8 claim/evidence reference before the chair can see it. */
|
|
73
|
-
export function claimVerificationRefIssues(graph, verifications, expectedIds =
|
|
118
|
+
export function claimVerificationRefIssues(graph, verifications, expectedIds = selectVerificationEscalations(graph).map((item) => item.claim_id)) {
|
|
74
119
|
const expected = new Set(expectedIds);
|
|
120
|
+
const known = new Set(graph.claims.map((claim) => claim.id));
|
|
75
121
|
const evidence = new Map(graph.evidence.map((item) => [item.id, item]));
|
|
76
122
|
const seen = new Set();
|
|
77
123
|
const issues = [];
|
|
78
124
|
for (const verification of verifications.verifications) {
|
|
79
|
-
|
|
125
|
+
// A claim id absent from the graph is a dangling reference (fatal even when the caller's
|
|
126
|
+
// expected set is derived from the verifications themselves, as S9's is).
|
|
127
|
+
if (!known.has(verification.claim_id)) {
|
|
80
128
|
issues.push(`unknown claim id: ${verification.claim_id}`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!expected.has(verification.claim_id))
|
|
132
|
+
issues.push(`unexpected claim verification: ${verification.claim_id}`);
|
|
81
133
|
if (seen.has(verification.claim_id))
|
|
82
134
|
issues.push(`duplicate claim verification: ${verification.claim_id}`);
|
|
83
135
|
seen.add(verification.claim_id);
|
|
@@ -111,7 +163,7 @@ export function claimVerificationRefIssues(graph, verifications, expectedIds = s
|
|
|
111
163
|
return issues;
|
|
112
164
|
}
|
|
113
165
|
export async function s8Verify(ctx, graph) {
|
|
114
|
-
const escalations =
|
|
166
|
+
const escalations = selectVerificationEscalations(graph);
|
|
115
167
|
if (escalations.length === 0) {
|
|
116
168
|
const empty = { verifications: [] };
|
|
117
169
|
await ctx.writer.writeJson('verifications', empty);
|
|
@@ -138,14 +190,16 @@ export async function s8Verify(ctx, graph) {
|
|
|
138
190
|
return skipped;
|
|
139
191
|
}
|
|
140
192
|
try {
|
|
141
|
-
const input = verifierInput(graph);
|
|
193
|
+
const input = verifierInput(graph, loadSkill('idea-refinement', 'verifier'));
|
|
142
194
|
// Optional work gets no model repair: one logical optional call must remain one call.
|
|
143
195
|
const result = await jsonCall(ctx, ctx.handle(ctx.roles.verifier), 'S8', input.prompt, ClaimVerificationSet, { repair: false });
|
|
196
|
+
const claimGroups = sanitizeClaimGroups(graph, result.claim_groups);
|
|
144
197
|
const translated = {
|
|
145
198
|
verifications: result.verifications.map((verification) => ({
|
|
146
199
|
...verification,
|
|
147
200
|
evidence_ids: verification.evidence_ids.map((id) => input.evidenceRefs.get(id) ?? id),
|
|
148
201
|
})),
|
|
202
|
+
...(claimGroups.length ? { claim_groups: claimGroups } : {}),
|
|
149
203
|
};
|
|
150
204
|
const issues = claimVerificationRefIssues(graph, translated, escalations.map((item) => item.claim_id));
|
|
151
205
|
if (issues.length)
|
|
@@ -5,6 +5,7 @@ import { selectRebuttalEscalations, } from '../decision-graph.js';
|
|
|
5
5
|
import { isFatal, StageError } from '../context.js';
|
|
6
6
|
import { jsonCall } from '../jsonStage.js';
|
|
7
7
|
import { IDEA_MODE_PLANS } from '../modes.js';
|
|
8
|
+
import { loadSkill } from '../skills.js';
|
|
8
9
|
/** Internal protocol caps only. R6 owns exposing modes and changing the surrounding topology. */
|
|
9
10
|
export const REBUTTAL_LIMITS = {
|
|
10
11
|
quick: { maxNodes: 0, optionalCalls: 0 },
|
|
@@ -22,7 +23,7 @@ Output ONLY JSON:
|
|
|
22
23
|
CONCEDE = your position changes. COUNTER = retain it using a cited supplied card or a precise logical
|
|
23
24
|
rebuttal. NARROW = state the smaller proposition actually supported. UNRESOLVED = evidence cannot decide.
|
|
24
25
|
Return exactly one event per assigned claim. Cite only evidence IDs shown for that claim. Do not invent
|
|
25
|
-
sources, URLs, claim IDs, or evidence IDs. JSON only.
|
|
26
|
+
sources, URLs, claim IDs, or evidence IDs. JSON only.{{SKILL}}
|
|
26
27
|
NODES: {{NODES_JSON}}`;
|
|
27
28
|
function claimAuthors(graph, claimId) {
|
|
28
29
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
@@ -70,7 +71,7 @@ export function planRebuttalCalls(graph, escalations, scouts, judge) {
|
|
|
70
71
|
return claim_ids?.length ? [{ provider, claim_ids }] : [];
|
|
71
72
|
});
|
|
72
73
|
}
|
|
73
|
-
function rebuttalInput(graph, verifications, escalationById, assignment) {
|
|
74
|
+
function rebuttalInput(graph, verifications, escalationById, assignment, skill = '') {
|
|
74
75
|
const claimIds = new Set(assignment.claim_ids);
|
|
75
76
|
const positionRefs = new Map();
|
|
76
77
|
const evidenceRefs = new Map();
|
|
@@ -130,11 +131,16 @@ function rebuttalInput(graph, verifications, escalationById, assignment) {
|
|
|
130
131
|
};
|
|
131
132
|
});
|
|
132
133
|
return {
|
|
133
|
-
prompt:
|
|
134
|
+
prompt: buildRebuttalPrompt(nodes, skill),
|
|
134
135
|
evidenceRefs,
|
|
135
136
|
allowedEvidence,
|
|
136
137
|
};
|
|
137
138
|
}
|
|
139
|
+
export function buildRebuttalPrompt(nodes, skill = '') {
|
|
140
|
+
return REBUTTAL_PROMPT
|
|
141
|
+
.replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
|
|
142
|
+
.replace('{{NODES_JSON}}', JSON.stringify(nodes, null, 2));
|
|
143
|
+
}
|
|
138
144
|
function translateEvents(graph, assignment, input, output, startIndex) {
|
|
139
145
|
const expected = new Set(assignment.claim_ids);
|
|
140
146
|
const seen = new Set();
|
|
@@ -189,8 +195,9 @@ export async function s8bRebuttal(ctx, graph, verifications, mode = ctx.mode) {
|
|
|
189
195
|
const assignments = planned.slice(0, optionalCalls);
|
|
190
196
|
const escalationById = new Map(escalations.map((item) => [item.claim_id, item]));
|
|
191
197
|
const events = [];
|
|
198
|
+
const skill = loadSkill('idea-refinement', 'rebuttal');
|
|
192
199
|
for (const assignment of assignments) {
|
|
193
|
-
const input = rebuttalInput(graph, verifications, escalationById, assignment);
|
|
200
|
+
const input = rebuttalInput(graph, verifications, escalationById, assignment, skill);
|
|
194
201
|
try {
|
|
195
202
|
const output = await jsonCall(ctx, ctx.handle(assignment.provider), `S8b-${assignment.provider}`, input.prompt, RebuttalResponseSet, { repair: false });
|
|
196
203
|
events.push(...translateEvents(graph, assignment, input, output, events.length));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// S9 — chair adjudication over graph-selected escalations. Consensus and shared concerns are
|
|
2
2
|
// read-only context; anonymous position text and the verifier record cross the boundary unchanged.
|
|
3
|
-
import { selectEscalations } from '../decision-graph.js';
|
|
4
3
|
import { IdeaChairReportModel } from '../../schemas/index.js';
|
|
5
4
|
import { isFatal, StageError } from '../context.js';
|
|
6
5
|
import { jsonCall } from '../jsonStage.js';
|
|
6
|
+
import { claimShortLabel } from '../decision-dossier.js';
|
|
7
|
+
import { loadSkill } from '../skills.js';
|
|
7
8
|
import { claimVerificationRefIssues } from './s8-verify.js';
|
|
8
9
|
const EMPTY_REBUTTALS = {
|
|
9
10
|
round: 1,
|
|
@@ -39,7 +40,7 @@ Output ONLY JSON matching the judge schema:
|
|
|
39
40
|
- key_points: 4-8 standalone decision-relevant bullets.
|
|
40
41
|
- dissent: a JSON array of strings (an array even when there is only one) — the strongest arguments
|
|
41
42
|
against your verdict.
|
|
42
|
-
- confidence_notes: explain calibrated confidence.
|
|
43
|
+
- confidence_notes: explain calibrated confidence.{{SKILL}}
|
|
43
44
|
ESCALATED CLAIMS + VERIFICATION: {{ESCALATIONS_JSON}}
|
|
44
45
|
APPEND-ONLY REBUTTAL EVENTS: {{REBUTTALS_JSON}}
|
|
45
46
|
SETTLED/UNRESOLVED CONTEXT: {{CONTEXT_JSON}}`;
|
|
@@ -147,10 +148,14 @@ export function adjudicationEvidenceViolations(report, verifications) {
|
|
|
147
148
|
return [`${item.id}: missing evidence_ids`];
|
|
148
149
|
const allowed = new Set(verification.evidence_ids);
|
|
149
150
|
const bad = item.evidence_ids.filter((id) => !allowed.has(id));
|
|
150
|
-
|
|
151
|
+
// v6: PARTIAL/UNVERIFIABLE no longer forces UNRESOLVED — the chair may rule on judgment with
|
|
152
|
+
// the caveat carried in its reasoning (run f740 lost 5 nuanced HOLDS rulings and a 125s Opus
|
|
153
|
+
// repair to the old rule). The one indefensible combination stays fatal: ruling HOLDS on
|
|
154
|
+
// positively CONTRADICTED evidence.
|
|
155
|
+
const contradicted = verification.status === 'CONTRADICTED' && (item.ruling === 'HOLDS' || item.ruling === 'UPHOLD');
|
|
151
156
|
return [
|
|
152
157
|
...(bad.length ? [`${item.id}: invalid evidence ids ${bad.join(', ')}`] : []),
|
|
153
|
-
...(
|
|
158
|
+
...(contradicted ? [`${item.id}: CONTRADICTED verification cannot rule HOLDS`] : []),
|
|
154
159
|
];
|
|
155
160
|
});
|
|
156
161
|
}
|
|
@@ -173,19 +178,40 @@ export function adjudicableClaimIds(graph, ids, judgeProvider) {
|
|
|
173
178
|
return [...ids].filter((id) => !claimById.get(id)?.position_ids
|
|
174
179
|
.some((positionId) => positionById.get(positionId)?.provider === judgeProvider));
|
|
175
180
|
}
|
|
181
|
+
/** One condition per distinct claim behind an evidence hole, claim-labeled instead of a generic
|
|
182
|
+
* placeholder reason. Deduped by claim_id and capped at 4 so the fallback conditions list doesn't
|
|
183
|
+
* drown in near-identical entries. */
|
|
184
|
+
export function evidenceHoleConditions(graph) {
|
|
185
|
+
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
const out = [];
|
|
188
|
+
for (const hole of graph.holes.evidence) {
|
|
189
|
+
if (seen.has(hole.claim_id))
|
|
190
|
+
continue;
|
|
191
|
+
seen.add(hole.claim_id);
|
|
192
|
+
const claim = claimById.get(hole.claim_id);
|
|
193
|
+
out.push(`Obtain independent evidence for: "${claimShortLabel(claim?.proposition ?? 'a load-bearing claim')}".`);
|
|
194
|
+
if (out.length === 4)
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
176
199
|
function fallbackConditions(graph, adjudications) {
|
|
177
200
|
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
178
201
|
const upheld = adjudications
|
|
179
202
|
.filter((item) => item.ruling === 'UPHOLD')
|
|
180
203
|
.map((item) => `Proceed only if you can resolve: ${claimById.get(item.id)?.proposition ?? item.id}.`);
|
|
181
204
|
const holes = graph.holes.coverage.map((hole) => `Proceed only after examining the ${hole.label} gap.`);
|
|
182
|
-
const evidenceHoles = graph
|
|
205
|
+
const evidenceHoles = evidenceHoleConditions(graph);
|
|
183
206
|
return [...upheld, ...holes, ...evidenceHoles, 'Proceed only after one cheap test confirms the core user need.'].slice(0, 6);
|
|
184
207
|
}
|
|
185
|
-
export function buildJudgePrompt(contract, graph, verifications, rubric, rebuttals = EMPTY_REBUTTALS, judgeProvider) {
|
|
186
|
-
return judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider).prompt;
|
|
208
|
+
export function buildJudgePrompt(contract, graph, verifications, rubric, rebuttals = EMPTY_REBUTTALS, judgeProvider, skill = '') {
|
|
209
|
+
return judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider, skill).prompt;
|
|
210
|
+
}
|
|
211
|
+
export function buildChairRepairPrompt(basePrompt, corrections) {
|
|
212
|
+
return `${basePrompt}\n\n---\nCorrect these problems:\n${corrections}Output ONLY corrected JSON.`;
|
|
187
213
|
}
|
|
188
|
-
function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider) {
|
|
214
|
+
function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider, skill = '') {
|
|
189
215
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
190
216
|
const verificationById = new Map(verifications.verifications.map((item) => [item.claim_id, item]));
|
|
191
217
|
const citedEvidence = [...new Set([
|
|
@@ -194,7 +220,10 @@ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProv
|
|
|
194
220
|
])];
|
|
195
221
|
const evidenceRefs = new Map(citedEvidence.map((id, index) => [`E${index + 1}`, id]));
|
|
196
222
|
const aliasByEvidence = new Map([...evidenceRefs].map(([alias, id]) => [id, alias]));
|
|
197
|
-
|
|
223
|
+
// Chair scope = the claims S8 actually verified (see s9Judge) — recomputing the escalation
|
|
224
|
+
// selection here on the overlaid graph would ask the chair to rule on claims that have no
|
|
225
|
+
// verifier record, which adjudicationEvidenceViolations then rejects after a paid call.
|
|
226
|
+
const selectedIds = verifications.verifications.map((item) => item.claim_id);
|
|
198
227
|
const eligibleIds = judgeProvider ? adjudicableClaimIds(graph, selectedIds, judgeProvider) : selectedIds;
|
|
199
228
|
const escalationIds = new Set(eligibleIds);
|
|
200
229
|
const escalations = graph.claims.filter((claim) => escalationIds.has(claim.id)).map((claim) => {
|
|
@@ -234,6 +263,7 @@ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProv
|
|
|
234
263
|
...(event.narrowed_proposition ? { narrowed_proposition: event.narrowed_proposition } : {}),
|
|
235
264
|
}));
|
|
236
265
|
const prompt = S9_PROMPT
|
|
266
|
+
.replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
|
|
237
267
|
.replace('{{RUBRIC_JSON}}', JSON.stringify(rubric.map((item) => item.label)))
|
|
238
268
|
.replace('{{ESCALATIONS_JSON}}', JSON.stringify(escalations, null, 2))
|
|
239
269
|
.replace('{{REBUTTALS_JSON}}', JSON.stringify(rebuttalContext, null, 2))
|
|
@@ -242,13 +272,21 @@ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProv
|
|
|
242
272
|
return { prompt, evidenceRefs };
|
|
243
273
|
}
|
|
244
274
|
export async function s9Judge(ctx, contract, graph, verifications, rubric, rebuttals = EMPTY_REBUTTALS) {
|
|
245
|
-
|
|
275
|
+
// The chair adjudicates what S8 ACTUALLY verified (persisted in 08, replay-safe) — never a
|
|
276
|
+
// re-ranked selection. S8 picks targets from the pre-overlay graph and validates its own output
|
|
277
|
+
// against them; overlayClaimGroups (built from S8's OWN claim_groups) then shifts claim states,
|
|
278
|
+
// so re-running selectVerificationEscalations on the joined graph can disagree with a selection
|
|
279
|
+
// S8 could not have predicted. Run e1ce (2026-07-18): the re-selection dropped G2 / added G7 and
|
|
280
|
+
// rejected a fully-compliant verification set. The chair also structurally requires a
|
|
281
|
+
// verification per adjudicated claim (adjudicationEvidenceViolations), so the verified set IS
|
|
282
|
+
// the chair's scope; dangling/duplicate/evidence-linkage guards below still apply.
|
|
283
|
+
const selectedIds = verifications.verifications.map((item) => item.claim_id);
|
|
246
284
|
const verificationIssues = claimVerificationRefIssues(graph, verifications, selectedIds);
|
|
247
285
|
if (verificationIssues.length)
|
|
248
286
|
throw new StageError('S9', 'BAD_OUTPUT', `invalid verification references: ${verificationIssues.join('; ')}`);
|
|
249
287
|
const ids = adjudicableClaimIds(graph, selectedIds, ctx.roles.judge);
|
|
250
288
|
const selfAuthored = new Set(selectedIds.filter((id) => !ids.includes(id)));
|
|
251
|
-
const input = judgeInput(contract, graph, verifications, rubric, rebuttals, ctx.roles.judge);
|
|
289
|
+
const input = judgeInput(contract, graph, verifications, rubric, rebuttals, ctx.roles.judge, loadSkill('idea-refinement', 'chair'));
|
|
252
290
|
const basePrompt = input.prompt;
|
|
253
291
|
const judge = ctx.handle(ctx.roles.judge);
|
|
254
292
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
@@ -291,13 +329,13 @@ export async function s9Judge(ctx, contract, graph, verifications, rubric, rebut
|
|
|
291
329
|
];
|
|
292
330
|
if ((violations.length || evidenceViolations.length || report.dissent.length === 0 || recIssues.length || chairIssues.length)
|
|
293
331
|
&& ctx.budget.limit - ctx.budget.used > 1) {
|
|
294
|
-
const
|
|
332
|
+
const corrections = ''
|
|
295
333
|
+ (violations.length ? `- adjudications may reference only [${ids.join(', ')}], not ${violations.join(', ')}\n` : '')
|
|
296
334
|
+ (evidenceViolations.length ? `- evidence reference errors: ${evidenceViolations.join('; ')}\n` : '')
|
|
297
335
|
+ (report.dissent.length === 0 ? '- dissent must contain at least one item\n' : '')
|
|
298
336
|
+ (recIssues.length ? `- ${recIssues.join('; ')}\n` : '')
|
|
299
|
-
+ (chairIssues.length ? `- chair contract errors: ${chairIssues.join('; ')}\n` : '')
|
|
300
|
-
|
|
337
|
+
+ (chairIssues.length ? `- chair contract errors: ${chairIssues.join('; ')}\n` : '');
|
|
338
|
+
const repair = buildChairRepairPrompt(basePrompt, corrections);
|
|
301
339
|
try {
|
|
302
340
|
report = translateChairReport(await jsonCall(ctx, judge, 'S9-repair', repair, IdeaChairReportModel, {
|
|
303
341
|
repair: ctx.budget.limit - ctx.budget.used > 2,
|