aiki-cli 0.3.0 → 0.3.2
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 +23 -0
- package/README.md +16 -8
- package/dist/bench/idea-v3-rating.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/run.js +10 -7
- package/dist/council/view.js +146 -46
- package/dist/orchestration/context.js +3 -3
- package/dist/orchestration/decision-dossier.js +467 -80
- package/dist/orchestration/decision-graph.js +31 -0
- package/dist/orchestration/legacy-idea-adapter.js +3 -0
- package/dist/orchestration/modes.js +16 -4
- package/dist/orchestration/preflight.js +43 -9
- package/dist/orchestration/quick-analysis.js +35 -6
- package/dist/orchestration/stages/s10-render.js +179 -79
- package/dist/orchestration/stages/s4-analyze.js +3 -1
- 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 +26 -9
- package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
- package/dist/orchestration/stages/s9-judge.js +36 -13
- package/dist/orchestration/stages/s9b-plan.js +208 -35
- package/dist/orchestration/url-sources.js +200 -0
- package/dist/schemas/index.js +134 -6
- 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/tui/app.js +19 -4
- package/dist/workflows/idea-refinement.js +51 -15
- package/package.json +1 -1
|
@@ -3,37 +3,25 @@
|
|
|
3
3
|
// sensitivity, coverage, contribution, and confidence are DERIVED here rather than invented by the judge.
|
|
4
4
|
// A truly missing required field is a template bug (fail loudly); degraded-but-valid
|
|
5
5
|
// states (S8 skipped, items UNVERIFIED, empty consensus) render normally. User-facing → DISPLAY_NAME.
|
|
6
|
+
import { ReaderBrief as ReaderBriefSchema, readerBriefIssues } from '../../schemas/index.js';
|
|
6
7
|
import { DISPLAY_NAME } from '../../providers/types.js';
|
|
7
8
|
import { overlap, tokenize } from '../cluster.js';
|
|
8
|
-
import {
|
|
9
|
+
import { interpretClaimOutcome } from '../decision-graph.js';
|
|
10
|
+
import { buildReaderProjection, renderDecisionDossierMarkdown, sanitizeReaderText } from '../decision-dossier.js';
|
|
9
11
|
import { callCategory } from '../modes.js';
|
|
10
12
|
/** Pure graph-backed decision audit. */
|
|
11
13
|
export function deriveAudit(graph, judgeReport) {
|
|
12
|
-
const ruling = new Map(judgeReport.adjudications.map((a) => [a.id, a
|
|
14
|
+
const ruling = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
13
15
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
14
16
|
return graph.claims.map((claim) => {
|
|
15
17
|
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
16
18
|
const providers = [...new Set(positions.map((position) => position.provider))];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
[status, confidence] = ['failed', providers.length >= 2 ? 'HIGH' : 'MEDIUM'];
|
|
24
|
-
}
|
|
25
|
-
else if (claim.state === 'DISAGREEMENT') {
|
|
26
|
-
const result = ruling.get(claim.id);
|
|
27
|
-
if (result === 'UPHOLD')
|
|
28
|
-
[status, confidence] = ['failed', 'MEDIUM'];
|
|
29
|
-
else if (result === 'REJECT')
|
|
30
|
-
[status, confidence] = ['held', 'MEDIUM'];
|
|
31
|
-
else
|
|
32
|
-
[status, confidence] = ['unverified', 'LOW'];
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
[status, confidence] = ['held', providers.length >= 2 ? 'HIGH' : 'MEDIUM'];
|
|
36
|
-
}
|
|
19
|
+
const outcome = interpretClaimOutcome(graph, claim, ruling.get(claim.id));
|
|
20
|
+
const status = outcome.decisionEffect === 'HELD' ? 'held'
|
|
21
|
+
: outcome.decisionEffect === 'FAILED' ? 'failed' : 'unverified';
|
|
22
|
+
const confidence = status === 'unverified' ? 'LOW'
|
|
23
|
+
: claim.state === 'DISAGREEMENT' ? 'MEDIUM'
|
|
24
|
+
: providers.length >= 2 ? 'HIGH' : 'MEDIUM';
|
|
37
25
|
return { id: claim.id, statement: claim.proposition, providers, status, confidence };
|
|
38
26
|
});
|
|
39
27
|
}
|
|
@@ -96,8 +84,10 @@ const DEGRADATION_FLAGS = ['low_diversity', 'synthesis_suspect', 'plan_fallback'
|
|
|
96
84
|
* self-confidence never enters it, and consensus alone can never reach the High band. */
|
|
97
85
|
export function computeConfidence(graph, flags) {
|
|
98
86
|
const loadBearing = graph.claims.filter((claim) => claim.load_bearing);
|
|
99
|
-
const
|
|
100
|
-
|
|
87
|
+
const factual = loadBearing.filter((claim) => claim.nature === 'FACTUAL');
|
|
88
|
+
const verificationClaims = factual.length ? factual : loadBearing;
|
|
89
|
+
const verificationCoverage = verificationClaims.length
|
|
90
|
+
? verificationClaims.filter((claim) => claim.evidence_state === 'SUPPORTED').length / verificationClaims.length : 0;
|
|
101
91
|
const independentConvergence = graph.claims.length
|
|
102
92
|
? graph.claims.filter((claim) => claim.state === 'CONSENSUS' || claim.state === 'SHARED_CONCERN').length / graph.claims.length : 0;
|
|
103
93
|
const evidenceQuality = graph.evidence.length
|
|
@@ -109,22 +99,93 @@ export function computeConfidence(graph, flags) {
|
|
|
109
99
|
score = Math.min(score, 79); // consensus alone never yields High
|
|
110
100
|
score = Math.max(0, Math.min(100, score));
|
|
111
101
|
const label = score >= 80 ? 'High' : score >= 60 ? 'Medium' : 'Low';
|
|
112
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
score,
|
|
104
|
+
label,
|
|
105
|
+
verificationCoverage,
|
|
106
|
+
...(factual.length ? { verificationScope: 'FACTUAL' } : {}),
|
|
107
|
+
independentConvergence,
|
|
108
|
+
evidenceQuality,
|
|
109
|
+
stability,
|
|
110
|
+
criticalRiskPenalty,
|
|
111
|
+
};
|
|
113
112
|
}
|
|
114
113
|
const STANCE_MAP = { SUPPORT: 'AGREE', OPPOSE: 'DISAGREE', MIXED: 'CONDITIONAL', UNKNOWN: 'UNKNOWN' };
|
|
115
114
|
const VERIFICATION_MAP = { SUPPORTED: 'VERIFIED', CONFLICTED: 'PARTIAL', UNVERIFIED: 'UNVERIFIED' };
|
|
116
115
|
const SEVERITY_MAP = { DECISIVE: 'High', MATERIAL: 'Medium', LOW: 'Low' };
|
|
117
|
-
function claimRuling(claim, adjudication) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
function claimRuling(graph, claim, adjudication) {
|
|
117
|
+
const truth = interpretClaimOutcome(graph, claim, adjudication).propositionTruth;
|
|
118
|
+
return truth === 'HOLDS' ? 'ACCEPTED' : truth === 'FAILS' ? 'REJECTED' : 'UNRESOLVED';
|
|
119
|
+
}
|
|
120
|
+
function fallbackReaderBrief(args) {
|
|
121
|
+
const labelFor = (id) => args.graph.claims.find((claim) => claim.id === id)?.proposition ?? null;
|
|
122
|
+
const clean = (value, max) => {
|
|
123
|
+
const text = sanitizeReaderText(value, labelFor);
|
|
124
|
+
if (text.length <= max)
|
|
125
|
+
return text;
|
|
126
|
+
const clipped = text.slice(0, max - 1);
|
|
127
|
+
const boundary = clipped.lastIndexOf(' ');
|
|
128
|
+
return `${clipped.slice(0, boundary > max * 0.6 ? boundary : max - 1).trimEnd()}…`;
|
|
129
|
+
};
|
|
130
|
+
const keyPoints = args.judgeReport.key_points ?? [];
|
|
131
|
+
const missing = args.missingRequestedOutputs.length
|
|
132
|
+
? `Requested deliverables unavailable: ${args.missingRequestedOutputs.join(', ')}.`
|
|
133
|
+
: 'No additional requested deliverable was synthesized.';
|
|
134
|
+
const firstAction = args.actionPlan && !('kind' in args.actionPlan) ? args.actionPlan.actions[0]?.action : undefined;
|
|
135
|
+
const nextStep = firstAction
|
|
136
|
+
?? args.openQuestions[0]
|
|
137
|
+
?? args.judgeReport.strongest_counter_case?.reasoning
|
|
138
|
+
?? 'Review the unresolved decision evidence before committing.';
|
|
139
|
+
const adjudicationById = new Map(args.judgeReport.adjudications.map((item) => [item.id, item]));
|
|
140
|
+
const acceptedClaims = new Set(args.graph.claims
|
|
141
|
+
.filter((claim) => claim.evidence_state === 'SUPPORTED'
|
|
142
|
+
&& interpretClaimOutcome(args.graph, claim, adjudicationById.get(claim.id)).propositionTruth === 'HOLDS')
|
|
143
|
+
.map((claim) => claim.id));
|
|
144
|
+
const sourceIds = args.graph.evidence
|
|
145
|
+
.filter((evidence) => {
|
|
146
|
+
if (evidence.source_kind !== 'PRIMARY' && evidence.source_kind !== 'SECONDARY')
|
|
147
|
+
return false;
|
|
148
|
+
try {
|
|
149
|
+
const url = new URL(evidence.url ?? '');
|
|
150
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return args.graph.edges.some((edge) => edge.from === evidence.id && edge.type === 'SUPPORTS' && acceptedClaims.has(edge.to));
|
|
157
|
+
})
|
|
158
|
+
.map((evidence) => evidence.id)
|
|
159
|
+
.slice(0, 8);
|
|
160
|
+
const raw = {
|
|
161
|
+
headline: clean(args.judgeReport.verdict, 160),
|
|
162
|
+
bottom_line: clean(args.judgeReport.verdict, 1200),
|
|
163
|
+
sections: [
|
|
164
|
+
{
|
|
165
|
+
heading: 'Why',
|
|
166
|
+
summary: clean(keyPoints[0] ?? args.judgeReport.verdict, 1000),
|
|
167
|
+
bullets: [...keyPoints.slice(1, 3), ...(args.judgeReport.conditions ?? []).slice(0, 2)].map((item) => clean(item, 500)),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
heading: 'What remains',
|
|
171
|
+
summary: missing,
|
|
172
|
+
bullets: [args.openQuestions[0], args.judgeReport.strongest_counter_case?.reasoning]
|
|
173
|
+
.filter((item) => Boolean(item)).map((item) => clean(item, 500)),
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
next_step: clean(nextStep, 600),
|
|
177
|
+
caveats: [
|
|
178
|
+
...(args.missingRequestedOutputs.length ? [missing] : []),
|
|
179
|
+
...(args.flags.has('plan_skipped') || args.flags.has('plan_fallback')
|
|
180
|
+
? ['The requested synthesis was unavailable; this concise summary uses the recorded chair decision only.'] : []),
|
|
181
|
+
],
|
|
182
|
+
source_ids: sourceIds,
|
|
183
|
+
};
|
|
184
|
+
const brief = ReaderBriefSchema.parse(raw);
|
|
185
|
+
const issues = readerBriefIssues(brief, args.graph.claims.map((claim) => claim.id));
|
|
186
|
+
if (issues.length)
|
|
187
|
+
throw new Error(`fallback reader brief leaked graph ids: ${issues.join(', ')}`);
|
|
188
|
+
return brief;
|
|
128
189
|
}
|
|
129
190
|
function verificationStatusByClaim(verifications) {
|
|
130
191
|
const statuses = new Map();
|
|
@@ -154,6 +215,18 @@ function buildDossier(args) {
|
|
|
154
215
|
}
|
|
155
216
|
const recommendationIds = (judgeReport.recommendation_claim_ids ?? [])
|
|
156
217
|
.filter((id) => claimById.has(id));
|
|
218
|
+
const recommendationPositionIds = new Set(recommendationIds.flatMap((id) => claimById.get(id)?.position_ids ?? []));
|
|
219
|
+
const seatStats = [...graph.positions.reduce((stats, position) => {
|
|
220
|
+
const provider = position.id.split('/')[0].replace(/-coverage-fill$/, '');
|
|
221
|
+
const entry = stats.get(provider) ?? { provider, positions: 0, evidenced: 0, survivedIntoDecision: 0 };
|
|
222
|
+
entry.positions += 1;
|
|
223
|
+
if (position.evidence_ids.length)
|
|
224
|
+
entry.evidenced += 1;
|
|
225
|
+
if (recommendationPositionIds.has(position.id))
|
|
226
|
+
entry.survivedIntoDecision += 1;
|
|
227
|
+
stats.set(provider, entry);
|
|
228
|
+
return stats;
|
|
229
|
+
}, new Map()).values()];
|
|
157
230
|
const anchors = recommendationIds.length
|
|
158
231
|
? recommendationIds
|
|
159
232
|
: graph.claims.filter((claim) => claim.load_bearing).slice(0, 8).map((claim) => claim.id);
|
|
@@ -191,6 +264,8 @@ function buildDossier(args) {
|
|
|
191
264
|
id: item.id,
|
|
192
265
|
source: item.locator ?? (item.source_kind === 'MODEL_KNOWLEDGE' ? `${disp(item.provider)} model knowledge` : item.source_kind),
|
|
193
266
|
sourceKind: item.source_kind,
|
|
267
|
+
...(item.title ? { title: item.title } : {}),
|
|
268
|
+
...(item.url ? { url: item.url } : {}),
|
|
194
269
|
date: item.accessed_at ?? 'Not recorded',
|
|
195
270
|
freshness: item.freshness,
|
|
196
271
|
verificationStatus,
|
|
@@ -264,7 +339,7 @@ function buildDossier(args) {
|
|
|
264
339
|
linkedClaimIds: [...new Set(related)],
|
|
265
340
|
};
|
|
266
341
|
});
|
|
267
|
-
const experiments = actionPlan && !('kind' in actionPlan)
|
|
342
|
+
const experiments = actionPlan && !('kind' in actionPlan) && actionPlan.actions.length > 0
|
|
268
343
|
? {
|
|
269
344
|
status: 'AVAILABLE',
|
|
270
345
|
note: actionPlan.sequencing_note,
|
|
@@ -279,11 +354,28 @@ function buildDossier(args) {
|
|
|
279
354
|
}
|
|
280
355
|
: {
|
|
281
356
|
status: 'DEGRADED',
|
|
282
|
-
note: actionPlan && 'kind' in actionPlan
|
|
283
|
-
?
|
|
284
|
-
:
|
|
357
|
+
note: actionPlan && !('kind' in actionPlan)
|
|
358
|
+
? 'The answer is available, but no validation action survived anchor checks.'
|
|
359
|
+
: actionPlan && 'kind' in actionPlan
|
|
360
|
+
? `Planner unavailable: ${actionPlan.reason}. Unresolved: ${actionPlan.unresolved_questions.join('; ')}`
|
|
361
|
+
: 'No planner artifact was recorded.',
|
|
285
362
|
actions: [],
|
|
286
363
|
};
|
|
364
|
+
const featureBacklog = actionPlan && !('kind' in actionPlan) ? actionPlan.feature_backlog : undefined;
|
|
365
|
+
const implementationPlan = actionPlan && !('kind' in actionPlan) ? actionPlan.implementation_plan : undefined;
|
|
366
|
+
const missingRequestedOutputs = [
|
|
367
|
+
...(args.requestedOutputs.includes('FEATURE_BACKLOG') && !featureBacklog ? ['FEATURE_BACKLOG'] : []),
|
|
368
|
+
...(args.requestedOutputs.includes('IMPLEMENTATION_PLAN') && !implementationPlan ? ['IMPLEMENTATION_PLAN'] : []),
|
|
369
|
+
];
|
|
370
|
+
const persistedReaderBrief = actionPlan && !('kind' in actionPlan) ? actionPlan.reader_brief : undefined;
|
|
371
|
+
const readerBrief = persistedReaderBrief ?? (args.newDecisionContract ? fallbackReaderBrief({
|
|
372
|
+
graph,
|
|
373
|
+
judgeReport,
|
|
374
|
+
actionPlan,
|
|
375
|
+
openQuestions: mergeOpenQuestions(seats),
|
|
376
|
+
missingRequestedOutputs,
|
|
377
|
+
flags: args.flags,
|
|
378
|
+
}) : undefined);
|
|
287
379
|
const counter = judgeReport.strongest_counter_case;
|
|
288
380
|
const contributions = args.models.map((model) => ({
|
|
289
381
|
provider: model.provider,
|
|
@@ -322,10 +414,15 @@ function buildDossier(args) {
|
|
|
322
414
|
coverage,
|
|
323
415
|
sensitivity,
|
|
324
416
|
experiments,
|
|
417
|
+
...(featureBacklog ? { featureBacklog } : {}),
|
|
418
|
+
...(implementationPlan ? { implementationPlan } : {}),
|
|
419
|
+
...(readerBrief ? { readerBrief } : {}),
|
|
420
|
+
missingRequestedOutputs,
|
|
325
421
|
counterCase: counter
|
|
326
422
|
? { available: true, reasoning: counter.reasoning, claimIds: counter.claim_ids.filter((id) => claimById.has(id)) }
|
|
327
423
|
: { available: false, reasoning: 'No graph-anchored counter-case was recorded.', claimIds: [] },
|
|
328
424
|
contributions,
|
|
425
|
+
seatStats,
|
|
329
426
|
technical: {
|
|
330
427
|
submissions: seats.map((seat) => ({
|
|
331
428
|
provider: seat.provider,
|
|
@@ -344,12 +441,19 @@ export function buildDecisionReport(ctx, args) {
|
|
|
344
441
|
const { contract, seats, graph, verifications, judgeReport, actionPlan } = args;
|
|
345
442
|
const mode = ctx.mode ?? 'council';
|
|
346
443
|
const flags = new Set(ctx.flags);
|
|
444
|
+
const newDecisionContract = 'success_bar' in contract;
|
|
445
|
+
const hasReaderBrief = Boolean(actionPlan && !('kind' in actionPlan) && actionPlan.reader_brief);
|
|
446
|
+
if (newDecisionContract && !hasReaderBrief && !flags.has('plan_skipped') && !flags.has('plan_fallback')) {
|
|
447
|
+
flags.add('plan_fallback');
|
|
448
|
+
}
|
|
347
449
|
const confidence = computeConfidence(graph, flags);
|
|
348
450
|
const status = statusFrom(judgeReport);
|
|
349
451
|
const rulingById = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
350
452
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
351
453
|
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
352
454
|
const openQuestions = mergeOpenQuestions(seats);
|
|
455
|
+
const requestedOutputs = contract.requested_outputs
|
|
456
|
+
?? ['DECISION'];
|
|
353
457
|
const claims = graph.claims.map((claim) => {
|
|
354
458
|
const stances = {};
|
|
355
459
|
for (const id of claim.position_ids) {
|
|
@@ -361,7 +465,7 @@ export function buildDecisionReport(ctx, args) {
|
|
|
361
465
|
text: claim.proposition,
|
|
362
466
|
stances,
|
|
363
467
|
verification: VERIFICATION_MAP[claim.evidence_state],
|
|
364
|
-
ruling: claimRuling(claim, rulingById.get(claim.id)),
|
|
468
|
+
ruling: claimRuling(graph, claim, rulingById.get(claim.id)),
|
|
365
469
|
loadBearing: claim.load_bearing,
|
|
366
470
|
sensitivity: claim.sensitivity,
|
|
367
471
|
};
|
|
@@ -393,9 +497,6 @@ export function buildDecisionReport(ctx, args) {
|
|
|
393
497
|
const consensusType = mode === 'quick' ? 'single_analyst' : disagreements.length === 0
|
|
394
498
|
? (claims.some((claim) => claim.ruling === 'UNRESOLVED') ? 'convergent_with_unresolved_claims' : 'unanimous')
|
|
395
499
|
: disagreements.every((d) => d.status === 'RESOLVED') ? 'majority_with_dissent' : 'contested';
|
|
396
|
-
const risks = graph.claims
|
|
397
|
-
.filter((claim) => claim.load_bearing && claim.evidence_state !== 'SUPPORTED')
|
|
398
|
-
.map((claim) => ({ risk: claim.proposition, severity: SEVERITY_MAP[claim.sensitivity] ?? 'Medium' }));
|
|
399
500
|
// Frame the slot as what it is — a decisive assumption that is NOT proven. Echoing the bare
|
|
400
501
|
// proposition inverts the meaning when the claim is phrased affirmatively (run 20260714-2321:
|
|
401
502
|
// "a cutover can preserve compliance" read as reassurance, the opposite of the verdict).
|
|
@@ -403,6 +504,18 @@ export function buildDecisionReport(ctx, args) {
|
|
|
403
504
|
const criticalWarning = criticalClaim
|
|
404
505
|
? `Unverified decisive assumption (if false, STOP): ${criticalClaim.proposition} — evidence ${criticalClaim.evidence_state}.`
|
|
405
506
|
: null;
|
|
507
|
+
// Same framing problem as the critical warning above: an unsettled load-bearing claim rendered as
|
|
508
|
+
// its bare (often affirmatively-phrased) proposition reads as an endorsement, not a risk. Frame
|
|
509
|
+
// each explicitly, sort worst-first, and skip the claim already surfaced as the critical warning
|
|
510
|
+
// so it isn't double-counted.
|
|
511
|
+
const severityRank = { High: 0, Medium: 1, Low: 2 };
|
|
512
|
+
const risks = graph.claims
|
|
513
|
+
.filter((claim) => claim.load_bearing && claim.evidence_state !== 'SUPPORTED' && claim.id !== criticalClaim?.id)
|
|
514
|
+
.map((claim) => ({
|
|
515
|
+
risk: `Rests on unsettled claim: ${claim.proposition} (evidence ${claim.evidence_state}).`,
|
|
516
|
+
severity: SEVERITY_MAP[claim.sensitivity] ?? 'Medium',
|
|
517
|
+
}))
|
|
518
|
+
.sort((a, b) => severityRank[a.severity] - severityRank[b.severity]);
|
|
406
519
|
const verificationResults = verifications.verifications.map((v) => {
|
|
407
520
|
if ('claim_id' in v) {
|
|
408
521
|
return {
|
|
@@ -467,6 +580,9 @@ export function buildDecisionReport(ctx, args) {
|
|
|
467
580
|
actionPlan,
|
|
468
581
|
rebuttals: args.rebuttals,
|
|
469
582
|
rubric: args.rubric ?? [],
|
|
583
|
+
requestedOutputs,
|
|
584
|
+
newDecisionContract,
|
|
585
|
+
flags,
|
|
470
586
|
});
|
|
471
587
|
const decisionSnapshot = judgeReport.decision_snapshot ? {
|
|
472
588
|
decisiveNumbers: judgeReport.decision_snapshot.decisive_numbers.map((item) => ({
|
|
@@ -552,55 +668,39 @@ export function buildDecisionReport(ctx, args) {
|
|
|
552
668
|
? actionPlan.actions.map((action) => ({ order: action.order, action: action.action, why: action.why, effort: action.effort, killSignal: action.kill_signal }))
|
|
553
669
|
: [],
|
|
554
670
|
openQuestions,
|
|
555
|
-
flags: [...
|
|
671
|
+
flags: [...flags],
|
|
556
672
|
receipt: { calls: ctx.calls.length, budget: ctx.budget.limit, byProvider, modelTimeMs, categories: receiptCategories(ctx) },
|
|
557
673
|
dossier,
|
|
558
674
|
};
|
|
559
675
|
}
|
|
560
|
-
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
561
676
|
export { renderDecisionDossierMarkdown };
|
|
562
677
|
export function renderReport(ctx, args) {
|
|
563
678
|
return renderDecisionDossierMarkdown(buildDecisionReport(ctx, args));
|
|
564
679
|
}
|
|
565
680
|
// ── Terminal summary (level 1) ───────────────────────────────────────────────
|
|
566
|
-
const RULE = '─'.repeat(
|
|
681
|
+
const RULE = '─'.repeat(48);
|
|
682
|
+
function terminalLine(value) {
|
|
683
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
684
|
+
}
|
|
567
685
|
export function renderTerminalSummary(report, paths) {
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
...(snapshot.payback ? [`Payback (${snapshot.payback.status.replaceAll('_', ' ').toLowerCase()}): ${snapshot.payback.result}`] : []),
|
|
575
|
-
`Options: ${snapshot.options.map((option) => `${option.label} — ${option.commitment} (${option.commitmentKind.replace('_', ' ').toLowerCase()})`).join(' · ')}`,
|
|
576
|
-
] : ['Decisive result:', report.verdict.primaryReason];
|
|
577
|
-
const checks = [
|
|
578
|
-
`[${c.verificationCoverage >= 0.5 ? 'PASS' : 'WARN'}] Verification coverage ${pct(c.verificationCoverage)} of load-bearing claims`,
|
|
579
|
-
`[PASS] Minority opinion preserved (${report.minority.dissent.length} dissent item(s))`,
|
|
580
|
-
`[${report.verification.refuted === 0 ? 'PASS' : 'WARN'}] Verifier refutations: ${report.verification.refuted}`,
|
|
581
|
-
...(report.flags.length ? [`[WARN] Degradation flags: ${report.flags.join(', ')}`] : []),
|
|
582
|
-
`[WARN] Structural score ${c.score}/100 is heuristic — not yet benchmark-calibrated`,
|
|
583
|
-
];
|
|
686
|
+
const projection = report.dossier.readerBrief ? buildReaderProjection(report) : undefined;
|
|
687
|
+
const candidates = projection
|
|
688
|
+
? [...projection.sections.flatMap((section) => [section.summary, ...section.bullets]), ...projection.caveats, projection.nextStep]
|
|
689
|
+
: [...(report.keyFindings ?? []), ...report.verdict.conditions, report.verdict.primaryReason];
|
|
690
|
+
const takeaways = [...new Set(candidates.map(terminalLine).filter(Boolean))].slice(0, 3);
|
|
691
|
+
const nextStep = projection?.nextStep ?? report.recommendedActions[0]?.action ?? 'Open the report and choose the smallest decisive next action.';
|
|
584
692
|
return [
|
|
585
|
-
report.mode === 'quick' ? 'SINGLE-MODEL DECISION
|
|
693
|
+
report.mode === 'quick' ? 'AIKI · SINGLE-MODEL DECISION' : 'AIKI · COUNCIL DECISION',
|
|
586
694
|
RULE,
|
|
587
|
-
`Verdict: ${report.verdict.summary}`,
|
|
588
|
-
|
|
589
|
-
`
|
|
590
|
-
'Coverage note: unchecked inputs remain; this is not a probability of correctness.',
|
|
591
|
-
`${report.mode === 'quick' ? 'Claims' : 'Consensus'}: ${report.verdict.consensusType.replaceAll('_', ' ')} — ${summary.accepted} accepted · ${summary.rejected} rejected · ${summary.unresolved} unresolved`,
|
|
592
|
-
'',
|
|
593
|
-
...decisiveLines,
|
|
695
|
+
`Verdict: ${terminalLine(projection?.headline ?? report.verdict.summary)}`,
|
|
696
|
+
terminalLine(projection?.bottomLine ?? report.verdict.primaryReason),
|
|
697
|
+
...(projection?.warnings[0] ? [`Warning: ${projection.warnings[0].message}`] : []),
|
|
594
698
|
'',
|
|
595
|
-
|
|
596
|
-
...(
|
|
597
|
-
'Verification:',
|
|
598
|
-
...checks,
|
|
699
|
+
'Key takeaways:',
|
|
700
|
+
...takeaways.map((item) => `- ${item}`),
|
|
599
701
|
'',
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
`Full report: ${paths.markdownPath}`,
|
|
603
|
-
`Audit JSON: ${paths.jsonPath}`,
|
|
702
|
+
`Next step: ${terminalLine(nextStep)}`,
|
|
703
|
+
`Report: ${paths.markdownPath}`,
|
|
604
704
|
].join('\n');
|
|
605
705
|
}
|
|
606
706
|
export async function s10Render(ctx, args) {
|
|
@@ -21,10 +21,12 @@ async function runSeat(ctx, seat, label, lane, prompt) {
|
|
|
21
21
|
await ctx.writer.writeRoleOutput(label, output);
|
|
22
22
|
return { provider: seat, sample: label, lane, output };
|
|
23
23
|
}
|
|
24
|
-
export async function s4Analyze(ctx, prompts) {
|
|
24
|
+
export async function s4Analyze(ctx, prompts, needsSourceFallback = false) {
|
|
25
25
|
const seats = ctx.roles.s4;
|
|
26
26
|
const settled = await Promise.allSettled(seats.map((seat, index) => {
|
|
27
27
|
const lane = IDEA_LANES[index % IDEA_LANES.length];
|
|
28
|
+
if (needsSourceFallback && ctx.mode !== 'quick' && seat === 'codex')
|
|
29
|
+
ctx.addFlag('source_fallback_search');
|
|
28
30
|
return runSeat(ctx, seat, seat, lane, prompts[lane]);
|
|
29
31
|
}));
|
|
30
32
|
const survivors = [];
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
// S6 — preserve validated analyst submissions for graph compilation. No lexical merging: positions
|
|
2
2
|
// keep their original text and are grouped only by stable references in S7.
|
|
3
|
+
/** A surviving scout seat with little output or evidence is weak. ponytail: thresholds are blunt on
|
|
4
|
+
* purpose; raise them only with benchmark evidence. */
|
|
5
|
+
export function detectWeakSeat(positions, mode) {
|
|
6
|
+
if (mode === 'quick')
|
|
7
|
+
return [];
|
|
8
|
+
const seats = new Map();
|
|
9
|
+
for (const position of positions) {
|
|
10
|
+
const provider = position.id.split('/')[0].replace(/-coverage-fill$/, '');
|
|
11
|
+
const seat = seats.get(provider) ?? { positions: 0, evidenced: 0 };
|
|
12
|
+
seat.positions += 1;
|
|
13
|
+
if (position.evidence_ids?.length)
|
|
14
|
+
seat.evidenced += 1;
|
|
15
|
+
seats.set(provider, seat);
|
|
16
|
+
}
|
|
17
|
+
if (seats.size < 2)
|
|
18
|
+
return [];
|
|
19
|
+
return [...seats].filter(([, seat]) => seat.positions < 3 || seat.evidenced / seat.positions < 0.5).map(([provider]) => provider);
|
|
20
|
+
}
|
|
3
21
|
export function collectPositions(seats) {
|
|
4
22
|
return seats.map((seat) => ({ provider: seat.provider, source_id: seat.sample ?? seat.provider, submission: seat.output }));
|
|
5
23
|
}
|
|
@@ -4,6 +4,7 @@ import { compileDecisionGraph, coverageHoleQueue, positionId } from '../decision
|
|
|
4
4
|
import { IdeaRoleOutputModel } from '../../schemas/index.js';
|
|
5
5
|
import { isFatal } from '../context.js';
|
|
6
6
|
import { jsonCall } from '../jsonStage.js';
|
|
7
|
+
import { detectWeakSeat } from './s6-positions.js';
|
|
7
8
|
import { overlapCoefficient, tokenize } from '../cluster.js';
|
|
8
9
|
const GROUP_SIMILARITY = 0.8;
|
|
9
10
|
export function buildCoverageFillPrompt(task, holes) {
|
|
@@ -71,6 +72,8 @@ export async function s7DecisionGraph(ctx, submissions, rubric, task = '') {
|
|
|
71
72
|
const completed = await fillCoverage(ctx, submissions, rubric, task);
|
|
72
73
|
const groups = deterministicClaimGroups(completed);
|
|
73
74
|
const graph = compileDecisionGraph(completed, rubric, groups);
|
|
75
|
+
if (detectWeakSeat(graph.positions, ctx.mode ?? 'council').length)
|
|
76
|
+
ctx.addFlag('weak_seat');
|
|
74
77
|
await ctx.writer.writeJson('decision-graph', graph);
|
|
75
78
|
return graph;
|
|
76
79
|
}
|
|
@@ -4,6 +4,7 @@ import { selectEscalations } from '../decision-graph.js';
|
|
|
4
4
|
import { ClaimVerificationSet } from '../../schemas/index.js';
|
|
5
5
|
import { isFatal, StageError } from '../context.js';
|
|
6
6
|
import { jsonCall } from '../jsonStage.js';
|
|
7
|
+
import { loadSkill } from '../skills.js';
|
|
7
8
|
const S8_PROMPT = `ROLE: Independent verifier. Check each anonymous decision claim below against the
|
|
8
9
|
provided evidence cards and deterministic calculation checks. Output ONLY JSON:
|
|
9
10
|
{"verifications": [{"claim_id": "<G-id>", "status": "VERIFIED|PARTIAL|CONTRADICTED|UNVERIFIABLE",
|
|
@@ -12,8 +13,21 @@ provided evidence cards and deterministic calculation checks. Output ONLY JSON:
|
|
|
12
13
|
VERIFIED = the proposition is supported. CONTRADICTED = it is refuted. PARTIAL = evidence is mixed or
|
|
13
14
|
incomplete. UNVERIFIABLE = the supplied sources cannot decide. Cite only supplied evidence IDs. Model
|
|
14
15
|
knowledge cannot verify a current, numeric, legal, medical, financial, market, or regulatory fact.
|
|
15
|
-
Judge each item independently; no verdict quota. JSON only.
|
|
16
|
+
Judge each item independently; no verdict quota. JSON only.{{SKILL}}
|
|
16
17
|
ITEMS: {{ITEMS_JSON}}`;
|
|
18
|
+
export function selectVerificationTargets(claims, max) {
|
|
19
|
+
return claims
|
|
20
|
+
.map((claim, index) => ({ claim, index }))
|
|
21
|
+
.sort((left, right) => Number(right.claim.nature === 'FACTUAL') - Number(left.claim.nature === 'FACTUAL') || left.index - right.index)
|
|
22
|
+
.slice(0, max)
|
|
23
|
+
.map(({ claim }) => claim);
|
|
24
|
+
}
|
|
25
|
+
export function selectVerificationEscalations(graph, max = 8) {
|
|
26
|
+
const candidates = selectEscalations(graph, { max: graph.claims.length });
|
|
27
|
+
const byId = new Map(candidates.map((candidate) => [candidate.claim_id, candidate]));
|
|
28
|
+
const claims = candidates.map((candidate) => graph.claims.find((claim) => claim.id === candidate.claim_id));
|
|
29
|
+
return selectVerificationTargets(claims, max).map((claim) => byId.get(claim.id));
|
|
30
|
+
}
|
|
17
31
|
function evidenceIdsForClaim(graph, claimId) {
|
|
18
32
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
19
33
|
const claim = graph.claims.find((item) => item.id === claimId);
|
|
@@ -25,12 +39,12 @@ function evidenceIdsForClaim(graph, claimId) {
|
|
|
25
39
|
.flatMap((calculation) => calculation.inputs.flatMap((input) => input.evidence_ids));
|
|
26
40
|
return new Set([...positionEvidence, ...calculationEvidence]);
|
|
27
41
|
}
|
|
28
|
-
function verifierInput(graph) {
|
|
42
|
+
function verifierInput(graph, skill = '') {
|
|
29
43
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
30
44
|
const evidenceRefs = new Map(graph.evidence.map((evidence, index) => [`E${index + 1}`, evidence.id]));
|
|
31
45
|
const aliasByEvidence = new Map([...evidenceRefs].map(([alias, id]) => [id, alias]));
|
|
32
46
|
const evidenceById = new Map(graph.evidence.map((evidence) => [evidence.id, evidence]));
|
|
33
|
-
const items =
|
|
47
|
+
const items = selectVerificationEscalations(graph).map((escalation) => {
|
|
34
48
|
const claim = graph.claims.find((item) => item.id === escalation.claim_id);
|
|
35
49
|
const linkedEvidenceIds = evidenceIdsForClaim(graph, claim.id);
|
|
36
50
|
return {
|
|
@@ -64,13 +78,16 @@ function verifierInput(graph) {
|
|
|
64
78
|
evidence_hole: graph.holes.evidence.find((hole) => hole.claim_id === claim.id)?.reason,
|
|
65
79
|
};
|
|
66
80
|
});
|
|
67
|
-
|
|
81
|
+
const prompt = S8_PROMPT
|
|
82
|
+
.replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
|
|
83
|
+
.replace('{{ITEMS_JSON}}', JSON.stringify(items, null, 2));
|
|
84
|
+
return { prompt, evidenceRefs };
|
|
68
85
|
}
|
|
69
|
-
export function buildVerifierPrompt(graph) {
|
|
70
|
-
return verifierInput(graph).prompt;
|
|
86
|
+
export function buildVerifierPrompt(graph, skill = '') {
|
|
87
|
+
return verifierInput(graph, skill).prompt;
|
|
71
88
|
}
|
|
72
89
|
/** Validate every S8 claim/evidence reference before the chair can see it. */
|
|
73
|
-
export function claimVerificationRefIssues(graph, verifications, expectedIds =
|
|
90
|
+
export function claimVerificationRefIssues(graph, verifications, expectedIds = selectVerificationEscalations(graph).map((item) => item.claim_id)) {
|
|
74
91
|
const expected = new Set(expectedIds);
|
|
75
92
|
const evidence = new Map(graph.evidence.map((item) => [item.id, item]));
|
|
76
93
|
const seen = new Set();
|
|
@@ -111,7 +128,7 @@ export function claimVerificationRefIssues(graph, verifications, expectedIds = s
|
|
|
111
128
|
return issues;
|
|
112
129
|
}
|
|
113
130
|
export async function s8Verify(ctx, graph) {
|
|
114
|
-
const escalations =
|
|
131
|
+
const escalations = selectVerificationEscalations(graph);
|
|
115
132
|
if (escalations.length === 0) {
|
|
116
133
|
const empty = { verifications: [] };
|
|
117
134
|
await ctx.writer.writeJson('verifications', empty);
|
|
@@ -138,7 +155,7 @@ export async function s8Verify(ctx, graph) {
|
|
|
138
155
|
return skipped;
|
|
139
156
|
}
|
|
140
157
|
try {
|
|
141
|
-
const input = verifierInput(graph);
|
|
158
|
+
const input = verifierInput(graph, loadSkill('idea-refinement', 'verifier'));
|
|
142
159
|
// Optional work gets no model repair: one logical optional call must remain one call.
|
|
143
160
|
const result = await jsonCall(ctx, ctx.handle(ctx.roles.verifier), 'S8', input.prompt, ClaimVerificationSet, { repair: false });
|
|
144
161
|
const translated = {
|
|
@@ -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));
|