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
|
@@ -3,37 +3,26 @@
|
|
|
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 { evidenceOrigin } from '../evidence-origin.js';
|
|
11
|
+
import { buildReaderProjection, formatTokenLine, renderDecisionDossierMarkdown, sanitizeReaderText } from '../decision-dossier.js';
|
|
9
12
|
import { callCategory } from '../modes.js';
|
|
10
13
|
/** Pure graph-backed decision audit. */
|
|
11
14
|
export function deriveAudit(graph, judgeReport) {
|
|
12
|
-
const ruling = new Map(judgeReport.adjudications.map((a) => [a.id, a
|
|
15
|
+
const ruling = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
13
16
|
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
14
17
|
return graph.claims.map((claim) => {
|
|
15
18
|
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
16
19
|
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
|
-
}
|
|
20
|
+
const outcome = interpretClaimOutcome(graph, claim, ruling.get(claim.id));
|
|
21
|
+
const status = outcome.decisionEffect === 'HELD' ? 'held'
|
|
22
|
+
: outcome.decisionEffect === 'FAILED' ? 'failed' : 'unverified';
|
|
23
|
+
const confidence = status === 'unverified' ? 'LOW'
|
|
24
|
+
: claim.state === 'DISAGREEMENT' ? 'MEDIUM'
|
|
25
|
+
: providers.length >= 2 ? 'HIGH' : 'MEDIUM';
|
|
37
26
|
return { id: claim.id, statement: claim.proposition, providers, status, confidence };
|
|
38
27
|
});
|
|
39
28
|
}
|
|
@@ -96,12 +85,16 @@ const DEGRADATION_FLAGS = ['low_diversity', 'synthesis_suspect', 'plan_fallback'
|
|
|
96
85
|
* self-confidence never enters it, and consensus alone can never reach the High band. */
|
|
97
86
|
export function computeConfidence(graph, flags) {
|
|
98
87
|
const loadBearing = graph.claims.filter((claim) => claim.load_bearing);
|
|
99
|
-
const
|
|
100
|
-
|
|
88
|
+
const factual = loadBearing.filter((claim) => claim.nature === 'FACTUAL');
|
|
89
|
+
const verificationClaims = factual.length ? factual : loadBearing;
|
|
90
|
+
const verificationCoverage = verificationClaims.length
|
|
91
|
+
? verificationClaims.filter((claim) => claim.evidence_state === 'SUPPORTED').length / verificationClaims.length : 0;
|
|
101
92
|
const independentConvergence = graph.claims.length
|
|
102
93
|
? graph.claims.filter((claim) => claim.state === 'CONSENSUS' || claim.state === 'SHARED_CONCERN').length / graph.claims.length : 0;
|
|
94
|
+
// v6: only independent EXTERNAL evidence counts as quality — the user's own material restated
|
|
95
|
+
// as cards inflated f740's coverage to theater (8 of 12 cards were the user's idea-brief).
|
|
103
96
|
const evidenceQuality = graph.evidence.length
|
|
104
|
-
? graph.evidence.filter((card) => card
|
|
97
|
+
? graph.evidence.filter((card) => evidenceOrigin(card) === 'EXTERNAL').length / graph.evidence.length : 0;
|
|
105
98
|
const stability = Math.max(0, 1 - 0.25 * DEGRADATION_FLAGS.filter((flag) => flags.has(flag)).length);
|
|
106
99
|
const criticalRiskPenalty = Math.min(20, 5 * loadBearing.filter((claim) => claim.if_false === 'STOP' && claim.evidence_state !== 'SUPPORTED').length);
|
|
107
100
|
let score = Math.round(verificationCoverage * 40 + independentConvergence * 25 + evidenceQuality * 20 + stability * 15 - criticalRiskPenalty);
|
|
@@ -109,22 +102,93 @@ export function computeConfidence(graph, flags) {
|
|
|
109
102
|
score = Math.min(score, 79); // consensus alone never yields High
|
|
110
103
|
score = Math.max(0, Math.min(100, score));
|
|
111
104
|
const label = score >= 80 ? 'High' : score >= 60 ? 'Medium' : 'Low';
|
|
112
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
score,
|
|
107
|
+
label,
|
|
108
|
+
verificationCoverage,
|
|
109
|
+
...(factual.length ? { verificationScope: 'FACTUAL' } : {}),
|
|
110
|
+
independentConvergence,
|
|
111
|
+
evidenceQuality,
|
|
112
|
+
stability,
|
|
113
|
+
criticalRiskPenalty,
|
|
114
|
+
};
|
|
113
115
|
}
|
|
114
116
|
const STANCE_MAP = { SUPPORT: 'AGREE', OPPOSE: 'DISAGREE', MIXED: 'CONDITIONAL', UNKNOWN: 'UNKNOWN' };
|
|
115
117
|
const VERIFICATION_MAP = { SUPPORTED: 'VERIFIED', CONFLICTED: 'PARTIAL', UNVERIFIED: 'UNVERIFIED' };
|
|
116
118
|
const SEVERITY_MAP = { DECISIVE: 'High', MATERIAL: 'Medium', LOW: 'Low' };
|
|
117
|
-
function claimRuling(claim, adjudication) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
function claimRuling(graph, claim, adjudication) {
|
|
120
|
+
const truth = interpretClaimOutcome(graph, claim, adjudication).propositionTruth;
|
|
121
|
+
return truth === 'HOLDS' ? 'ACCEPTED' : truth === 'FAILS' ? 'REJECTED' : 'UNRESOLVED';
|
|
122
|
+
}
|
|
123
|
+
function fallbackReaderBrief(args) {
|
|
124
|
+
const labelFor = (id) => args.graph.claims.find((claim) => claim.id === id)?.proposition ?? null;
|
|
125
|
+
const clean = (value, max) => {
|
|
126
|
+
const text = sanitizeReaderText(value, labelFor);
|
|
127
|
+
if (text.length <= max)
|
|
128
|
+
return text;
|
|
129
|
+
const clipped = text.slice(0, max - 1);
|
|
130
|
+
const boundary = clipped.lastIndexOf(' ');
|
|
131
|
+
return `${clipped.slice(0, boundary > max * 0.6 ? boundary : max - 1).trimEnd()}…`;
|
|
132
|
+
};
|
|
133
|
+
const keyPoints = args.judgeReport.key_points ?? [];
|
|
134
|
+
const missing = args.missingRequestedOutputs.length
|
|
135
|
+
? `Requested deliverables unavailable: ${args.missingRequestedOutputs.join(', ')}.`
|
|
136
|
+
: 'No additional requested deliverable was synthesized.';
|
|
137
|
+
const firstAction = args.actionPlan && !('kind' in args.actionPlan) ? args.actionPlan.actions[0]?.action : undefined;
|
|
138
|
+
const nextStep = firstAction
|
|
139
|
+
?? args.openQuestions[0]
|
|
140
|
+
?? args.judgeReport.strongest_counter_case?.reasoning
|
|
141
|
+
?? 'Review the unresolved decision evidence before committing.';
|
|
142
|
+
const adjudicationById = new Map(args.judgeReport.adjudications.map((item) => [item.id, item]));
|
|
143
|
+
const acceptedClaims = new Set(args.graph.claims
|
|
144
|
+
.filter((claim) => claim.evidence_state === 'SUPPORTED'
|
|
145
|
+
&& interpretClaimOutcome(args.graph, claim, adjudicationById.get(claim.id)).propositionTruth === 'HOLDS')
|
|
146
|
+
.map((claim) => claim.id));
|
|
147
|
+
const sourceIds = args.graph.evidence
|
|
148
|
+
.filter((evidence) => {
|
|
149
|
+
if (evidence.source_kind !== 'PRIMARY' && evidence.source_kind !== 'SECONDARY')
|
|
150
|
+
return false;
|
|
151
|
+
try {
|
|
152
|
+
const url = new URL(evidence.url ?? '');
|
|
153
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return args.graph.edges.some((edge) => edge.from === evidence.id && edge.type === 'SUPPORTS' && acceptedClaims.has(edge.to));
|
|
160
|
+
})
|
|
161
|
+
.map((evidence) => evidence.id)
|
|
162
|
+
.slice(0, 8);
|
|
163
|
+
const raw = {
|
|
164
|
+
headline: clean(args.judgeReport.verdict, 160),
|
|
165
|
+
bottom_line: clean(args.judgeReport.verdict, 1200),
|
|
166
|
+
sections: [
|
|
167
|
+
{
|
|
168
|
+
heading: 'Why',
|
|
169
|
+
summary: clean(keyPoints[0] ?? args.judgeReport.verdict, 1000),
|
|
170
|
+
bullets: [...keyPoints.slice(1, 3), ...(args.judgeReport.conditions ?? []).slice(0, 2)].map((item) => clean(item, 500)),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
heading: 'What remains',
|
|
174
|
+
summary: missing,
|
|
175
|
+
bullets: [args.openQuestions[0], args.judgeReport.strongest_counter_case?.reasoning]
|
|
176
|
+
.filter((item) => Boolean(item)).map((item) => clean(item, 500)),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
next_step: clean(nextStep, 600),
|
|
180
|
+
caveats: [
|
|
181
|
+
...(args.missingRequestedOutputs.length ? [missing] : []),
|
|
182
|
+
...(args.flags.has('plan_skipped') || args.flags.has('plan_fallback')
|
|
183
|
+
? ['The requested synthesis was unavailable; this concise summary uses the recorded chair decision only.'] : []),
|
|
184
|
+
],
|
|
185
|
+
source_ids: sourceIds,
|
|
186
|
+
};
|
|
187
|
+
const brief = ReaderBriefSchema.parse(raw);
|
|
188
|
+
const issues = readerBriefIssues(brief, args.graph.claims.map((claim) => claim.id));
|
|
189
|
+
if (issues.length)
|
|
190
|
+
throw new Error(`fallback reader brief leaked graph ids: ${issues.join(', ')}`);
|
|
191
|
+
return brief;
|
|
128
192
|
}
|
|
129
193
|
function verificationStatusByClaim(verifications) {
|
|
130
194
|
const statuses = new Map();
|
|
@@ -154,6 +218,18 @@ function buildDossier(args) {
|
|
|
154
218
|
}
|
|
155
219
|
const recommendationIds = (judgeReport.recommendation_claim_ids ?? [])
|
|
156
220
|
.filter((id) => claimById.has(id));
|
|
221
|
+
const recommendationPositionIds = new Set(recommendationIds.flatMap((id) => claimById.get(id)?.position_ids ?? []));
|
|
222
|
+
const seatStats = [...graph.positions.reduce((stats, position) => {
|
|
223
|
+
const provider = position.id.split('/')[0].replace(/-coverage-fill$/, '');
|
|
224
|
+
const entry = stats.get(provider) ?? { provider, positions: 0, evidenced: 0, survivedIntoDecision: 0 };
|
|
225
|
+
entry.positions += 1;
|
|
226
|
+
if (position.evidence_ids.length)
|
|
227
|
+
entry.evidenced += 1;
|
|
228
|
+
if (recommendationPositionIds.has(position.id))
|
|
229
|
+
entry.survivedIntoDecision += 1;
|
|
230
|
+
stats.set(provider, entry);
|
|
231
|
+
return stats;
|
|
232
|
+
}, new Map()).values()];
|
|
157
233
|
const anchors = recommendationIds.length
|
|
158
234
|
? recommendationIds
|
|
159
235
|
: graph.claims.filter((claim) => claim.load_bearing).slice(0, 8).map((claim) => claim.id);
|
|
@@ -191,6 +267,8 @@ function buildDossier(args) {
|
|
|
191
267
|
id: item.id,
|
|
192
268
|
source: item.locator ?? (item.source_kind === 'MODEL_KNOWLEDGE' ? `${disp(item.provider)} model knowledge` : item.source_kind),
|
|
193
269
|
sourceKind: item.source_kind,
|
|
270
|
+
...(item.title ? { title: item.title } : {}),
|
|
271
|
+
...(item.url ? { url: item.url } : {}),
|
|
194
272
|
date: item.accessed_at ?? 'Not recorded',
|
|
195
273
|
freshness: item.freshness,
|
|
196
274
|
verificationStatus,
|
|
@@ -264,7 +342,7 @@ function buildDossier(args) {
|
|
|
264
342
|
linkedClaimIds: [...new Set(related)],
|
|
265
343
|
};
|
|
266
344
|
});
|
|
267
|
-
const experiments = actionPlan && !('kind' in actionPlan)
|
|
345
|
+
const experiments = actionPlan && !('kind' in actionPlan) && actionPlan.actions.length > 0
|
|
268
346
|
? {
|
|
269
347
|
status: 'AVAILABLE',
|
|
270
348
|
note: actionPlan.sequencing_note,
|
|
@@ -279,9 +357,11 @@ function buildDossier(args) {
|
|
|
279
357
|
}
|
|
280
358
|
: {
|
|
281
359
|
status: 'DEGRADED',
|
|
282
|
-
note: actionPlan && 'kind' in actionPlan
|
|
283
|
-
?
|
|
284
|
-
:
|
|
360
|
+
note: actionPlan && !('kind' in actionPlan)
|
|
361
|
+
? 'The answer is available, but no validation action survived anchor checks.'
|
|
362
|
+
: actionPlan && 'kind' in actionPlan
|
|
363
|
+
? `Planner unavailable: ${actionPlan.reason}. Unresolved: ${actionPlan.unresolved_questions.join('; ')}`
|
|
364
|
+
: 'No planner artifact was recorded.',
|
|
285
365
|
actions: [],
|
|
286
366
|
};
|
|
287
367
|
const featureBacklog = actionPlan && !('kind' in actionPlan) ? actionPlan.feature_backlog : undefined;
|
|
@@ -290,6 +370,15 @@ function buildDossier(args) {
|
|
|
290
370
|
...(args.requestedOutputs.includes('FEATURE_BACKLOG') && !featureBacklog ? ['FEATURE_BACKLOG'] : []),
|
|
291
371
|
...(args.requestedOutputs.includes('IMPLEMENTATION_PLAN') && !implementationPlan ? ['IMPLEMENTATION_PLAN'] : []),
|
|
292
372
|
];
|
|
373
|
+
const persistedReaderBrief = actionPlan && !('kind' in actionPlan) ? actionPlan.reader_brief : undefined;
|
|
374
|
+
const readerBrief = persistedReaderBrief ?? (args.newDecisionContract ? fallbackReaderBrief({
|
|
375
|
+
graph,
|
|
376
|
+
judgeReport,
|
|
377
|
+
actionPlan,
|
|
378
|
+
openQuestions: mergeOpenQuestions(seats),
|
|
379
|
+
missingRequestedOutputs,
|
|
380
|
+
flags: args.flags,
|
|
381
|
+
}) : undefined);
|
|
293
382
|
const counter = judgeReport.strongest_counter_case;
|
|
294
383
|
const contributions = args.models.map((model) => ({
|
|
295
384
|
provider: model.provider,
|
|
@@ -330,11 +419,13 @@ function buildDossier(args) {
|
|
|
330
419
|
experiments,
|
|
331
420
|
...(featureBacklog ? { featureBacklog } : {}),
|
|
332
421
|
...(implementationPlan ? { implementationPlan } : {}),
|
|
422
|
+
...(readerBrief ? { readerBrief } : {}),
|
|
333
423
|
missingRequestedOutputs,
|
|
334
424
|
counterCase: counter
|
|
335
425
|
? { available: true, reasoning: counter.reasoning, claimIds: counter.claim_ids.filter((id) => claimById.has(id)) }
|
|
336
426
|
: { available: false, reasoning: 'No graph-anchored counter-case was recorded.', claimIds: [] },
|
|
337
427
|
contributions,
|
|
428
|
+
seatStats,
|
|
338
429
|
technical: {
|
|
339
430
|
submissions: seats.map((seat) => ({
|
|
340
431
|
provider: seat.provider,
|
|
@@ -352,7 +443,14 @@ function buildDossier(args) {
|
|
|
352
443
|
export function buildDecisionReport(ctx, args) {
|
|
353
444
|
const { contract, seats, graph, verifications, judgeReport, actionPlan } = args;
|
|
354
445
|
const mode = ctx.mode ?? 'council';
|
|
446
|
+
const adaptiveAuto = ctx.isAuto === true;
|
|
447
|
+
const autoEscalationReasons = ctx.autoEscalationReasons ?? [];
|
|
355
448
|
const flags = new Set(ctx.flags);
|
|
449
|
+
const newDecisionContract = 'success_bar' in contract;
|
|
450
|
+
const hasReaderBrief = Boolean(actionPlan && !('kind' in actionPlan) && actionPlan.reader_brief);
|
|
451
|
+
if (newDecisionContract && !hasReaderBrief && !flags.has('plan_skipped') && !flags.has('plan_fallback')) {
|
|
452
|
+
flags.add('plan_fallback');
|
|
453
|
+
}
|
|
356
454
|
const confidence = computeConfidence(graph, flags);
|
|
357
455
|
const status = statusFrom(judgeReport);
|
|
358
456
|
const rulingById = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
@@ -372,7 +470,7 @@ export function buildDecisionReport(ctx, args) {
|
|
|
372
470
|
text: claim.proposition,
|
|
373
471
|
stances,
|
|
374
472
|
verification: VERIFICATION_MAP[claim.evidence_state],
|
|
375
|
-
ruling: claimRuling(claim, rulingById.get(claim.id)),
|
|
473
|
+
ruling: claimRuling(graph, claim, rulingById.get(claim.id)),
|
|
376
474
|
loadBearing: claim.load_bearing,
|
|
377
475
|
sensitivity: claim.sensitivity,
|
|
378
476
|
};
|
|
@@ -401,12 +499,9 @@ export function buildDecisionReport(ctx, args) {
|
|
|
401
499
|
.filter((position) => position.stance === 'OPPOSE')
|
|
402
500
|
.map((position) => ({ provider: disp(position.provider), proposition: position.proposition }));
|
|
403
501
|
const unresolvedDecisive = disagreements.some((d) => d.status === 'UNRESOLVED' && claimById.get(d.id)?.sensitivity === 'DECISIVE');
|
|
404
|
-
const consensusType = mode === 'quick' ? 'single_analyst' : disagreements.length === 0
|
|
502
|
+
const consensusType = adaptiveAuto || mode === 'quick' ? 'single_analyst' : disagreements.length === 0
|
|
405
503
|
? (claims.some((claim) => claim.ruling === 'UNRESOLVED') ? 'convergent_with_unresolved_claims' : 'unanimous')
|
|
406
504
|
: disagreements.every((d) => d.status === 'RESOLVED') ? 'majority_with_dissent' : 'contested';
|
|
407
|
-
const risks = graph.claims
|
|
408
|
-
.filter((claim) => claim.load_bearing && claim.evidence_state !== 'SUPPORTED')
|
|
409
|
-
.map((claim) => ({ risk: claim.proposition, severity: SEVERITY_MAP[claim.sensitivity] ?? 'Medium' }));
|
|
410
505
|
// Frame the slot as what it is — a decisive assumption that is NOT proven. Echoing the bare
|
|
411
506
|
// proposition inverts the meaning when the claim is phrased affirmatively (run 20260714-2321:
|
|
412
507
|
// "a cutover can preserve compliance" read as reassurance, the opposite of the verdict).
|
|
@@ -414,6 +509,18 @@ export function buildDecisionReport(ctx, args) {
|
|
|
414
509
|
const criticalWarning = criticalClaim
|
|
415
510
|
? `Unverified decisive assumption (if false, STOP): ${criticalClaim.proposition} — evidence ${criticalClaim.evidence_state}.`
|
|
416
511
|
: null;
|
|
512
|
+
// Same framing problem as the critical warning above: an unsettled load-bearing claim rendered as
|
|
513
|
+
// its bare (often affirmatively-phrased) proposition reads as an endorsement, not a risk. Frame
|
|
514
|
+
// each explicitly, sort worst-first, and skip the claim already surfaced as the critical warning
|
|
515
|
+
// so it isn't double-counted.
|
|
516
|
+
const severityRank = { High: 0, Medium: 1, Low: 2 };
|
|
517
|
+
const risks = graph.claims
|
|
518
|
+
.filter((claim) => claim.load_bearing && claim.evidence_state !== 'SUPPORTED' && claim.id !== criticalClaim?.id)
|
|
519
|
+
.map((claim) => ({
|
|
520
|
+
risk: `Rests on unsettled claim: ${claim.proposition} (evidence ${claim.evidence_state}).`,
|
|
521
|
+
severity: SEVERITY_MAP[claim.sensitivity] ?? 'Medium',
|
|
522
|
+
}))
|
|
523
|
+
.sort((a, b) => severityRank[a.severity] - severityRank[b.severity]);
|
|
417
524
|
const verificationResults = verifications.verifications.map((v) => {
|
|
418
525
|
if ('claim_id' in v) {
|
|
419
526
|
return {
|
|
@@ -436,16 +543,29 @@ export function buildDecisionReport(ctx, args) {
|
|
|
436
543
|
});
|
|
437
544
|
const byProvider = {};
|
|
438
545
|
let modelTimeMs = 0;
|
|
546
|
+
let inputTokens = 0, outputTokens = 0, estimatedCalls = 0;
|
|
439
547
|
for (const call of ctx.calls) {
|
|
440
548
|
byProvider[call.provider] = (byProvider[call.provider] ?? 0) + 1;
|
|
441
549
|
modelTimeMs += call.durationMs;
|
|
550
|
+
if (call.usage) {
|
|
551
|
+
inputTokens += call.usage.inputTokens ?? 0;
|
|
552
|
+
outputTokens += call.usage.outputTokens ?? 0;
|
|
553
|
+
if (call.usage.estimated)
|
|
554
|
+
estimatedCalls++;
|
|
555
|
+
}
|
|
442
556
|
}
|
|
443
557
|
const roles = ctx.roles;
|
|
444
|
-
const reportProviders =
|
|
558
|
+
const reportProviders = adaptiveAuto
|
|
559
|
+
? [...new Set([...seats.map((seat) => seat.provider), ...ctx.calls.map((call) => call.provider)])]
|
|
560
|
+
: mode === 'quick' ? [...new Set(seats.map((seat) => seat.provider))] : ctx.available();
|
|
445
561
|
const models = reportProviders.map((provider) => ({
|
|
446
562
|
provider,
|
|
447
563
|
name: disp(provider),
|
|
448
|
-
roles:
|
|
564
|
+
roles: adaptiveAuto ? [
|
|
565
|
+
...(seats.some((seat) => seat.provider === provider) ? ['primary analyst'] : []),
|
|
566
|
+
...(ctx.calls.some((call) => call.provider === provider && call.stage.startsWith('P0-')) ? ['preflight reader'] : []),
|
|
567
|
+
...(ctx.calls.some((call) => call.provider === provider && call.stage === 'S4b') ? ['delta challenger'] : []),
|
|
568
|
+
] : mode === 'quick' ? ['single analyst'] : [
|
|
449
569
|
...(roles.analyst === provider ? ['analyst'] : []),
|
|
450
570
|
...(roles.judge === provider ? ['judge'] : []),
|
|
451
571
|
...(roles.verifier === provider ? ['verifier'] : []),
|
|
@@ -479,6 +599,8 @@ export function buildDecisionReport(ctx, args) {
|
|
|
479
599
|
rebuttals: args.rebuttals,
|
|
480
600
|
rubric: args.rubric ?? [],
|
|
481
601
|
requestedOutputs,
|
|
602
|
+
newDecisionContract,
|
|
603
|
+
flags,
|
|
482
604
|
});
|
|
483
605
|
const decisionSnapshot = judgeReport.decision_snapshot ? {
|
|
484
606
|
decisiveNumbers: judgeReport.decision_snapshot.decisive_numbers.map((item) => ({
|
|
@@ -515,6 +637,10 @@ export function buildDecisionReport(ctx, args) {
|
|
|
515
637
|
reportId: ctx.runId,
|
|
516
638
|
generatedAt: new Date().toISOString(),
|
|
517
639
|
mode,
|
|
640
|
+
...(ctx.fastPath ? { fastPath: true } : {}),
|
|
641
|
+
...(adaptiveAuto ? { adaptiveAuto: true } : {}),
|
|
642
|
+
...(autoEscalationReasons.length ? { autoEscalationReasons } : {}),
|
|
643
|
+
...(adaptiveAuto && ctx.attemptedStages?.includes('S4b') ? { autoChallengeUsed: true } : {}),
|
|
518
644
|
task: {
|
|
519
645
|
original: args.original ?? contract.task,
|
|
520
646
|
normalized: contract.task,
|
|
@@ -564,55 +690,41 @@ export function buildDecisionReport(ctx, args) {
|
|
|
564
690
|
? actionPlan.actions.map((action) => ({ order: action.order, action: action.action, why: action.why, effort: action.effort, killSignal: action.kill_signal }))
|
|
565
691
|
: [],
|
|
566
692
|
openQuestions,
|
|
567
|
-
flags: [...
|
|
568
|
-
receipt: { calls: ctx.calls.length, budget: ctx.budget.limit, byProvider, modelTimeMs, categories: receiptCategories(ctx) },
|
|
693
|
+
flags: [...flags],
|
|
694
|
+
receipt: { calls: ctx.calls.length, budget: ctx.budget.limit, byProvider, modelTimeMs, categories: receiptCategories(ctx), tokens: { inputTokens, outputTokens, estimatedCalls } },
|
|
569
695
|
dossier,
|
|
570
696
|
};
|
|
571
697
|
}
|
|
572
|
-
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
573
698
|
export { renderDecisionDossierMarkdown };
|
|
574
699
|
export function renderReport(ctx, args) {
|
|
575
700
|
return renderDecisionDossierMarkdown(buildDecisionReport(ctx, args));
|
|
576
701
|
}
|
|
577
702
|
// ── Terminal summary (level 1) ───────────────────────────────────────────────
|
|
578
|
-
const RULE = '─'.repeat(
|
|
703
|
+
const RULE = '─'.repeat(48);
|
|
704
|
+
function terminalLine(value) {
|
|
705
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
706
|
+
}
|
|
579
707
|
export function renderTerminalSummary(report, paths) {
|
|
580
|
-
const
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
...(snapshot.payback ? [`Payback (${snapshot.payback.status.replaceAll('_', ' ').toLowerCase()}): ${snapshot.payback.result}`] : []),
|
|
587
|
-
`Options: ${snapshot.options.map((option) => `${option.label} — ${option.commitment} (${option.commitmentKind.replace('_', ' ').toLowerCase()})`).join(' · ')}`,
|
|
588
|
-
] : ['Decisive result:', report.verdict.primaryReason];
|
|
589
|
-
const checks = [
|
|
590
|
-
`[${c.verificationCoverage >= 0.5 ? 'PASS' : 'WARN'}] Verification coverage ${pct(c.verificationCoverage)} of load-bearing claims`,
|
|
591
|
-
`[PASS] Minority opinion preserved (${report.minority.dissent.length} dissent item(s))`,
|
|
592
|
-
`[${report.verification.refuted === 0 ? 'PASS' : 'WARN'}] Verifier refutations: ${report.verification.refuted}`,
|
|
593
|
-
...(report.flags.length ? [`[WARN] Degradation flags: ${report.flags.join(', ')}`] : []),
|
|
594
|
-
`[WARN] Structural score ${c.score}/100 is heuristic — not yet benchmark-calibrated`,
|
|
595
|
-
];
|
|
708
|
+
const projection = report.dossier.readerBrief ? buildReaderProjection(report) : undefined;
|
|
709
|
+
const candidates = projection
|
|
710
|
+
? [...projection.sections.flatMap((section) => [section.summary, ...section.bullets]), ...projection.caveats, projection.nextStep]
|
|
711
|
+
: [...(report.keyFindings ?? []), ...report.verdict.conditions, report.verdict.primaryReason];
|
|
712
|
+
const takeaways = [...new Set(candidates.map(terminalLine).filter(Boolean))].slice(0, 3);
|
|
713
|
+
const nextStep = projection?.nextStep ?? report.recommendedActions[0]?.action ?? 'Open the report and choose the smallest decisive next action.';
|
|
596
714
|
return [
|
|
597
|
-
report.
|
|
715
|
+
report.adaptiveAuto ? 'AIKI · ADAPTIVE DECISION'
|
|
716
|
+
: report.mode === 'quick' ? 'AIKI · SINGLE-MODEL DECISION' : 'AIKI · COUNCIL DECISION',
|
|
598
717
|
RULE,
|
|
599
|
-
`Verdict: ${report.verdict.summary}`,
|
|
600
|
-
|
|
601
|
-
`
|
|
602
|
-
'Coverage note: unchecked inputs remain; this is not a probability of correctness.',
|
|
603
|
-
`${report.mode === 'quick' ? 'Claims' : 'Consensus'}: ${report.verdict.consensusType.replaceAll('_', ' ')} — ${summary.accepted} accepted · ${summary.rejected} rejected · ${summary.unresolved} unresolved`,
|
|
604
|
-
'',
|
|
605
|
-
...decisiveLines,
|
|
718
|
+
`Verdict: ${terminalLine(projection?.headline ?? report.verdict.summary)}`,
|
|
719
|
+
terminalLine(projection?.bottomLine ?? report.verdict.primaryReason),
|
|
720
|
+
...(projection?.warnings[0] ? [`Warning: ${projection.warnings[0].message}`] : []),
|
|
606
721
|
'',
|
|
607
|
-
|
|
608
|
-
...(
|
|
609
|
-
'Verification:',
|
|
610
|
-
...checks,
|
|
722
|
+
'Key takeaways:',
|
|
723
|
+
...takeaways.map((item) => `- ${item}`),
|
|
611
724
|
'',
|
|
612
|
-
|
|
613
|
-
...(
|
|
614
|
-
`
|
|
615
|
-
`Audit JSON: ${paths.jsonPath}`,
|
|
725
|
+
`Next step: ${terminalLine(nextStep)}`,
|
|
726
|
+
...(report.receipt.tokens ? [`Tokens: ${formatTokenLine(report.receipt.tokens)}`] : []),
|
|
727
|
+
`Report: ${paths.markdownPath}`,
|
|
616
728
|
].join('\n');
|
|
617
729
|
}
|
|
618
730
|
export async function s10Render(ctx, args) {
|
|
@@ -16,15 +16,23 @@ import { IDEA_LANES } from '../idea-lanes.js';
|
|
|
16
16
|
/** Run one analyst seat → validated `RoleOutput`, persisted to 04-role-outputs/<label>.json.
|
|
17
17
|
* `label` doubles as the artifact filename, so a resample uses a distinct label. */
|
|
18
18
|
async function runSeat(ctx, seat, label, lane, prompt) {
|
|
19
|
-
|
|
19
|
+
// v6 tail guard: an S4 repair may spend only when the worst case (both seats repairing) still
|
|
20
|
+
// leaves the reserved tail — 2 seat calls + 2 repairs + 3 (chair, planner, one tail repair).
|
|
21
|
+
// When disallowed, jsonCall's salvage floor keeps the seat alive (proven on the f740 payloads).
|
|
22
|
+
const model = await jsonCall(ctx, ctx.handle(seat), `S4-${label}`, prompt, IdeaRoleOutputModel, {
|
|
23
|
+
salvage: salvageIdeaRoleOutputModel,
|
|
24
|
+
repair: ctx.budget.limit - ctx.budget.used >= 7,
|
|
25
|
+
});
|
|
20
26
|
const output = { workflow: 'idea-refinement', ...model };
|
|
21
27
|
await ctx.writer.writeRoleOutput(label, output);
|
|
22
28
|
return { provider: seat, sample: label, lane, output };
|
|
23
29
|
}
|
|
24
|
-
export async function s4Analyze(ctx, prompts) {
|
|
30
|
+
export async function s4Analyze(ctx, prompts, needsSourceFallback = false) {
|
|
25
31
|
const seats = ctx.roles.s4;
|
|
26
32
|
const settled = await Promise.allSettled(seats.map((seat, index) => {
|
|
27
33
|
const lane = IDEA_LANES[index % IDEA_LANES.length];
|
|
34
|
+
if (needsSourceFallback && ctx.mode !== 'quick' && seat === 'codex')
|
|
35
|
+
ctx.addFlag('source_fallback_search');
|
|
28
36
|
return runSeat(ctx, seat, seat, lane, prompts[lane]);
|
|
29
37
|
}));
|
|
30
38
|
const survivors = [];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// v7 Phase D — one auto-only delta challenge over hard-gated claim packets.
|
|
2
|
+
import { ChallengeDeltaSet as ChallengeDeltaSetSchema, } from '../../schemas/index.js';
|
|
3
|
+
import { canProduceNewInformation, } from '../auto-profile.js';
|
|
4
|
+
import { isFatal, StageError } from '../context.js';
|
|
5
|
+
import { jsonCall } from '../jsonStage.js';
|
|
6
|
+
import { buildClaimPackets } from './s8-verify.js';
|
|
7
|
+
const CHALLENGE_PROMPT = `ROLE: Independent delta challenger. You see only the targeted anonymous claim
|
|
8
|
+
packets below, not the full task. Confirm, counter, narrow, replace, or leave each claim unresolved.
|
|
9
|
+
|
|
10
|
+
Output ONLY JSON:
|
|
11
|
+
{"deltas":[{"claimId":"<G-id>","response":"CONFIRM|COUNTER|NARROW|REPLACE|UNRESOLVED",
|
|
12
|
+
"reasoning":"<precise reason>","newEvidenceIds":["<E-id>"],
|
|
13
|
+
"changedDecisionImpact":"<what changes, or why the decision stays stable>"}]}
|
|
14
|
+
|
|
15
|
+
Return exactly one delta per packet. Cite only evidence IDs shown in that packet. Do not invent a
|
|
16
|
+
source, URL, claim ID, or evidence ID. JSON only.
|
|
17
|
+
TARGET CLAIM PACKETS: {{PACKETS_JSON}}`;
|
|
18
|
+
export function buildChallengePrompt(packets) {
|
|
19
|
+
return CHALLENGE_PROMPT.replace('{{PACKETS_JSON}}', JSON.stringify(packets, null, 2));
|
|
20
|
+
}
|
|
21
|
+
function validateAndTranslate(result, targetIds, evidenceRefs, allowedEvidence) {
|
|
22
|
+
const expected = new Set(targetIds);
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const issues = [];
|
|
25
|
+
for (const delta of result.deltas) {
|
|
26
|
+
if (!expected.has(delta.claimId))
|
|
27
|
+
issues.push(`unknown claim id: ${delta.claimId}`);
|
|
28
|
+
if (seen.has(delta.claimId))
|
|
29
|
+
issues.push(`duplicate claim delta: ${delta.claimId}`);
|
|
30
|
+
seen.add(delta.claimId);
|
|
31
|
+
const allowed = allowedEvidence.get(delta.claimId) ?? new Set();
|
|
32
|
+
for (const id of delta.newEvidenceIds)
|
|
33
|
+
if (!allowed.has(id))
|
|
34
|
+
issues.push(`invalid evidence id for ${delta.claimId}: ${id}`);
|
|
35
|
+
}
|
|
36
|
+
for (const id of expected)
|
|
37
|
+
if (!seen.has(id))
|
|
38
|
+
issues.push(`missing claim delta: ${id}`);
|
|
39
|
+
if (issues.length)
|
|
40
|
+
throw new StageError('S4b', 'BAD_OUTPUT', issues.join('; '));
|
|
41
|
+
return {
|
|
42
|
+
deltas: result.deltas.map((delta) => ({
|
|
43
|
+
...delta,
|
|
44
|
+
newEvidenceIds: delta.newEvidenceIds.map((id) => evidenceRefs.get(id)),
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function challengerProvider(ctx, primaryProvider) {
|
|
49
|
+
return [...new Set([ctx.roles.verifier, ...ctx.roles.s4, ...ctx.available()])]
|
|
50
|
+
.find((provider) => provider !== primaryProvider && ctx.available().includes(provider));
|
|
51
|
+
}
|
|
52
|
+
/** At most one provider call; no repair call and no work for an information-free target. */
|
|
53
|
+
export async function s4bChallenge(ctx, graph, gates, primaryProvider) {
|
|
54
|
+
const byClaim = new Map(gates.map((gate) => [gate.claimId, gate]));
|
|
55
|
+
const targets = [...byClaim.values()]
|
|
56
|
+
.filter((gate) => graph.claims.some((claim) => claim.id === gate.claimId))
|
|
57
|
+
.filter((gate) => canProduceNewInformation(graph, gate.claimId))
|
|
58
|
+
.slice(0, 3);
|
|
59
|
+
const provider = challengerProvider(ctx, primaryProvider);
|
|
60
|
+
if (!targets.length || !provider)
|
|
61
|
+
return { deltas: [] };
|
|
62
|
+
const packets = buildClaimPackets(graph, targets.map((gate) => ({
|
|
63
|
+
claim_id: gate.claimId,
|
|
64
|
+
kind: gate.kind,
|
|
65
|
+
})));
|
|
66
|
+
try {
|
|
67
|
+
const result = await jsonCall(ctx, ctx.handle(provider), 'S4b', buildChallengePrompt(packets.packets), ChallengeDeltaSetSchema, { repair: false });
|
|
68
|
+
const translated = validateAndTranslate(result, targets.map((gate) => gate.claimId), packets.evidenceRefs, packets.allowedEvidence);
|
|
69
|
+
await ctx.writer.writeJson('challenge-deltas', translated);
|
|
70
|
+
return translated;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (isFatal(error))
|
|
74
|
+
throw error;
|
|
75
|
+
const empty = { deltas: [] };
|
|
76
|
+
await ctx.writer.writeJson('challenge-deltas', empty);
|
|
77
|
+
return empty;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Derived graph overlay; the stored S7 artifact and every non-target claim stay untouched. */
|
|
81
|
+
export function overlayChallengeDeltas(graph, deltas) {
|
|
82
|
+
if (!deltas.length)
|
|
83
|
+
return graph;
|
|
84
|
+
const stateById = new Map(deltas.map((delta) => [delta.claimId,
|
|
85
|
+
delta.response === 'CONFIRM' ? 'CONSENSUS'
|
|
86
|
+
: delta.response === 'COUNTER' ? 'DISAGREEMENT'
|
|
87
|
+
: 'UNCERTAIN']));
|
|
88
|
+
return {
|
|
89
|
+
...graph,
|
|
90
|
+
claims: graph.claims.map((claim) => {
|
|
91
|
+
const state = stateById.get(claim.id);
|
|
92
|
+
if (!state || (state === 'CONSENSUS' && claim.state === 'DISAGREEMENT'))
|
|
93
|
+
return claim;
|
|
94
|
+
return { ...claim, state };
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -2,17 +2,27 @@
|
|
|
2
2
|
// contract's task? Two code checks (no model call — the §601/§532 T6 "deterministic core"; the §9
|
|
3
3
|
// "verifier spot-check" is deferred): (1) the output produced ≥1 position (an analyst that
|
|
4
4
|
// produced none did not engage the task), and (2) its `task_echo` is similar enough to the contract
|
|
5
|
-
// task (overlap coefficient ≥ DRIFT_MIN_SIMILARITY). Drifted
|
|
5
|
+
// — task + constraints + success_criteria (overlap coefficient ≥ DRIFT_MIN_SIMILARITY). Drifted
|
|
6
|
+
// outputs are excluded from everything
|
|
6
7
|
// downstream and logged; if exclusion drops the survivor count below quorum (2), the run aborts.
|
|
7
8
|
import { StageError } from '../context.js';
|
|
8
9
|
import { overlapCoefficient, tokenize } from '../cluster.js';
|
|
9
|
-
/** `task_echo` must share ≥ this fraction of its tokens with the contract
|
|
10
|
+
/** `task_echo` must share ≥ this fraction of its tokens with the contract to count as on-task.
|
|
10
11
|
* Overlap coefficient (not Jaccard) so a short echo is not penalized against a longer contract
|
|
11
12
|
* paragraph. Lenient by design — this catches an analyst that wandered off-task, not paraphrase
|
|
12
13
|
* distance. Tunable (same footnote class as the S2 clustering threshold). */
|
|
13
14
|
export const DRIFT_MIN_SIMILARITY = 0.3;
|
|
15
|
+
/** The reference token set is the contract the seat was told to address — task + constraints +
|
|
16
|
+
* success_criteria — NOT just the one-sentence task. Run 626e (2026-07-18, REAL QUORUM kill):
|
|
17
|
+
* the prompt says "restate the task" and both seats paraphrased accurately, but against the
|
|
18
|
+
* 17-token model-authored task sentence they scored 0.294/0.250 < 0.3 and the run died with 18
|
|
19
|
+
* good positions on disk. Against the full contract the same echoes score 0.680/0.500 while a
|
|
20
|
+
* genuinely off-task echo stays ≤0.08 — paraphrase passes, drift still fails, threshold untouched. */
|
|
21
|
+
function contractTokens(contract) {
|
|
22
|
+
return tokenize([contract.task, ...contract.constraints, ...contract.success_criteria].join(' '));
|
|
23
|
+
}
|
|
14
24
|
export async function s5Drift(ctx, contract, seats, minSeats = 2) {
|
|
15
|
-
const taskTokens =
|
|
25
|
+
const taskTokens = contractTokens(contract);
|
|
16
26
|
const entries = [];
|
|
17
27
|
const kept = [];
|
|
18
28
|
const excluded = [];
|
|
@@ -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
|
}
|