aiki-cli 0.2.1 → 0.3.0
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 +99 -7
- package/README.md +109 -30
- package/dist/bench/arms.js +10 -9
- package/dist/bench/harness.js +13 -10
- package/dist/bench/idea-lane-rotation.js +237 -0
- package/dist/bench/idea-v3-bench.js +506 -0
- package/dist/bench/idea-v3-rating.js +582 -0
- package/dist/bench/results.js +10 -3
- package/dist/bench/scoring/decision-insights.js +112 -0
- package/dist/bench/scoring/seeded-bugs.js +4 -0
- package/dist/cli/bench.js +180 -3
- package/dist/cli/doctor.js +56 -24
- package/dist/cli/index.js +32 -7
- package/dist/cli/resolve.js +7 -6
- package/dist/cli/resume.js +18 -0
- package/dist/cli/run.js +63 -8
- package/dist/cli/version.js +8 -0
- package/dist/council/view.js +378 -109
- package/dist/orchestration/calculations.js +97 -0
- package/dist/orchestration/context.js +37 -9
- package/dist/orchestration/decision-dossier.js +262 -0
- package/dist/orchestration/decision-graph.js +256 -0
- package/dist/orchestration/engine.js +5 -2
- package/dist/orchestration/evidence-pack.js +46 -0
- package/dist/orchestration/idea-lanes.js +20 -0
- package/dist/orchestration/jsonStage.js +72 -0
- package/dist/orchestration/legacy-idea-adapter.js +102 -0
- package/dist/orchestration/modes.js +33 -0
- package/dist/orchestration/preflight.js +183 -0
- package/dist/orchestration/quick-analysis.js +81 -0
- package/dist/orchestration/stages/cr-ladder.js +80 -0
- package/dist/orchestration/stages/s10-render.js +562 -150
- package/dist/orchestration/stages/s4-analyze.js +12 -7
- package/dist/orchestration/stages/s5-drift.js +9 -9
- package/dist/orchestration/stages/s6-positions.js +10 -0
- package/dist/orchestration/stages/s7-decision-graph.js +76 -0
- package/dist/orchestration/stages/s8-verify.js +153 -46
- package/dist/orchestration/stages/s8b-rebuttal.js +208 -0
- package/dist/orchestration/stages/s9-judge.js +329 -108
- package/dist/orchestration/stages/s9b-plan.js +85 -75
- package/dist/providers/codex.js +2 -1
- package/dist/providers/spawn.js +5 -0
- package/dist/schemas/index.js +572 -13
- package/dist/skills/idea-refinement/analyst.md +18 -14
- package/dist/skills/idea-refinement/economics-delivery.md +7 -0
- package/dist/skills/idea-refinement/market-adoption.md +7 -0
- package/dist/storage/runs.js +11 -4
- package/dist/tui/app.js +37 -13
- package/dist/tui/format.js +4 -5
- package/dist/tui/smart-entry.js +17 -1
- package/dist/tui/timeline.js +2 -4
- package/dist/workflows/code-review.js +4 -2
- package/dist/workflows/idea-refinement.js +110 -46
- package/package.json +12 -4
- package/dist/orchestration/stages/s0-grill.js +0 -79
- package/dist/orchestration/stages/s1-intent.js +0 -25
- package/dist/orchestration/stages/s2-misread.js +0 -76
- package/dist/orchestration/stages/s3-prompts.js +0 -55
- package/dist/orchestration/stages/s6-claims.js +0 -56
- package/dist/orchestration/stages/s7-disagreement.js +0 -134
|
@@ -1,46 +1,48 @@
|
|
|
1
1
|
// S10 — artifact rendering (§9, §12.1, §307). Pure code → `final-report.md`, a DECISION BRIEF, not a
|
|
2
|
-
// smoothed essay (§263). Every section is assembled deterministically from prior artifacts;
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// smoothed essay (§263). Every section is assembled deterministically from prior artifacts; audit,
|
|
3
|
+
// sensitivity, coverage, contribution, and confidence are DERIVED here rather than invented by the judge.
|
|
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
6
|
import { DISPLAY_NAME } from '../../providers/types.js';
|
|
7
7
|
import { overlap, tokenize } from '../cluster.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export function deriveAudit(
|
|
12
|
-
const contestedBy = new Map(); // claim id → contradiction id
|
|
13
|
-
for (const d of map.contradictions)
|
|
14
|
-
for (const cid of d.claim_ids)
|
|
15
|
-
contestedBy.set(cid, d.id);
|
|
8
|
+
import { renderDecisionDossierMarkdown } from '../decision-dossier.js';
|
|
9
|
+
import { callCategory } from '../modes.js';
|
|
10
|
+
/** Pure graph-backed decision audit. */
|
|
11
|
+
export function deriveAudit(graph, judgeReport) {
|
|
16
12
|
const ruling = new Map(judgeReport.adjudications.map((a) => [a.id, a.ruling]));
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
14
|
+
return graph.claims.map((claim) => {
|
|
15
|
+
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
16
|
+
const providers = [...new Set(positions.map((position) => position.provider))];
|
|
19
17
|
let status;
|
|
20
18
|
let confidence;
|
|
21
|
-
if (
|
|
22
|
-
status = '
|
|
23
|
-
confidence = c.providers.length >= 2 ? 'HIGH' : 'MEDIUM';
|
|
19
|
+
if (claim.state === 'UNCERTAIN' || claim.evidence_state !== 'SUPPORTED') {
|
|
20
|
+
[status, confidence] = ['unverified', 'LOW'];
|
|
24
21
|
}
|
|
25
|
-
else {
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
else if (claim.state === 'SHARED_CONCERN' || (claim.state === 'UNIQUE' && positions[0]?.stance === 'OPPOSE')) {
|
|
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')
|
|
28
30
|
[status, confidence] = ['held', 'MEDIUM'];
|
|
29
|
-
else if (r === 'UPHOLD')
|
|
30
|
-
[status, confidence] = ['failed', 'LOW'];
|
|
31
31
|
else
|
|
32
|
-
[status, confidence] = ['unverified', 'LOW'];
|
|
32
|
+
[status, confidence] = ['unverified', 'LOW'];
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
else {
|
|
35
|
+
[status, confidence] = ['held', providers.length >= 2 ? 'HIGH' : 'MEDIUM'];
|
|
36
|
+
}
|
|
37
|
+
return { id: claim.id, statement: claim.proposition, providers, status, confidence };
|
|
35
38
|
});
|
|
36
39
|
}
|
|
37
40
|
const disp = (id) => DISPLAY_NAME[id];
|
|
38
|
-
|
|
39
|
-
/** Union of the seats' open questions, deduped by lexical similarity (≥0.85), capped. */
|
|
41
|
+
/** Union of the seats' decision questions, deduped by lexical similarity (≥0.85), capped. */
|
|
40
42
|
export function mergeOpenQuestions(seats, cap = 10) {
|
|
41
43
|
const kept = [];
|
|
42
44
|
for (const seat of seats) {
|
|
43
|
-
for (const q of seat.output.
|
|
45
|
+
for (const { question: q } of seat.output.decision_questions) {
|
|
44
46
|
const tokens = tokenize(q);
|
|
45
47
|
if (!kept.some((k) => overlap(k.tokens, tokens) >= 0.85))
|
|
46
48
|
kept.push({ q, tokens });
|
|
@@ -48,25 +50,29 @@ export function mergeOpenQuestions(seats, cap = 10) {
|
|
|
48
50
|
}
|
|
49
51
|
return kept.slice(0, cap).map((k) => k.q);
|
|
50
52
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const blind = new Set(map.blind_spots.map((b) => b.toLowerCase()));
|
|
56
|
-
const contestedTexts = map.contradictions.map((d) => d.claim_ids.map((id) => claimById.get(id) ?? id).join(' '));
|
|
53
|
+
/** Exact rubric coverage derived from graph dimension anchors and holes. */
|
|
54
|
+
export function deriveScorecard(rubric, graph) {
|
|
55
|
+
const blind = new Set(graph.holes.coverage.map((hole) => hole.dimension_id));
|
|
56
|
+
const claimByPosition = new Map(graph.claims.flatMap((claim) => claim.position_ids.map((id) => [id, claim])));
|
|
57
57
|
return rubric.map((r) => {
|
|
58
|
-
if (blind.has(r.
|
|
58
|
+
if (blind.has(r.id))
|
|
59
59
|
return { id: r.id, label: r.label, status: 'unexamined' };
|
|
60
|
-
const
|
|
61
|
-
|
|
60
|
+
const contested = graph.positions
|
|
61
|
+
.filter((position) => position.dimension_id === r.id)
|
|
62
|
+
.some((position) => {
|
|
63
|
+
const state = claimByPosition.get(position.id)?.state;
|
|
64
|
+
return state === 'DISAGREEMENT' || state === 'UNCERTAIN';
|
|
65
|
+
});
|
|
62
66
|
return { id: r.id, label: r.label, status: contested ? 'contested' : 'examined' };
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
function receiptCategories(ctx) {
|
|
70
|
+
if (typeof ctx.receipt === 'function')
|
|
71
|
+
return ctx.receipt();
|
|
72
|
+
const categories = { discovery: 0, verification: 0, repair: 0, planning: 0 };
|
|
73
|
+
for (const call of ctx.calls)
|
|
74
|
+
categories[call.category ?? callCategory(call.stage)]++;
|
|
75
|
+
return categories;
|
|
70
76
|
}
|
|
71
77
|
function rulingPhrase(ruling) {
|
|
72
78
|
if (ruling === 'UPHOLD')
|
|
@@ -75,124 +81,530 @@ function rulingPhrase(ruling) {
|
|
|
75
81
|
return 'the idea holds here';
|
|
76
82
|
return 'left to you';
|
|
77
83
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
export function statusFrom(judgeReport) {
|
|
85
|
+
switch (judgeReport.recommendation) {
|
|
86
|
+
case 'PROCEED': return 'ACCEPTED';
|
|
87
|
+
case 'PROCEED_WITH_CONDITIONS': return 'ACCEPTED_WITH_CONDITIONS';
|
|
88
|
+
case 'PIVOT':
|
|
89
|
+
case 'STOP': return 'REJECTED';
|
|
90
|
+
default: return 'INCONCLUSIVE';
|
|
84
91
|
}
|
|
85
|
-
const providerCounts = [...byProvider.entries()].map(([p, n]) => `${disp(p)} ${n}`).join(', ') || 'none';
|
|
86
|
-
return [
|
|
87
|
-
`- Calls: ${ctx.calls.length}/${ctx.budget.limit}`,
|
|
88
|
-
`- By provider: ${providerCounts}`,
|
|
89
|
-
`- Recorded model time: ${(ms / 1000).toFixed(1)}s`,
|
|
90
|
-
];
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
93
|
+
const DEGRADATION_FLAGS = ['low_diversity', 'synthesis_suspect', 'plan_fallback', 'plan_skipped'];
|
|
94
|
+
/** Structural confidence — 40% verification coverage + 25% convergence + 20% evidence quality +
|
|
95
|
+
* 15% stability − critical-risk penalty. A starting heuristic, NOT a calibrated probability; model
|
|
96
|
+
* self-confidence never enters it, and consensus alone can never reach the High band. */
|
|
97
|
+
export function computeConfidence(graph, flags) {
|
|
98
|
+
const loadBearing = graph.claims.filter((claim) => claim.load_bearing);
|
|
99
|
+
const verificationCoverage = loadBearing.length
|
|
100
|
+
? loadBearing.filter((claim) => claim.evidence_state === 'SUPPORTED').length / loadBearing.length : 0;
|
|
101
|
+
const independentConvergence = graph.claims.length
|
|
102
|
+
? graph.claims.filter((claim) => claim.state === 'CONSENSUS' || claim.state === 'SHARED_CONCERN').length / graph.claims.length : 0;
|
|
103
|
+
const evidenceQuality = graph.evidence.length
|
|
104
|
+
? graph.evidence.filter((card) => card.source_kind !== 'MODEL_KNOWLEDGE').length / graph.evidence.length : 0;
|
|
105
|
+
const stability = Math.max(0, 1 - 0.25 * DEGRADATION_FLAGS.filter((flag) => flags.has(flag)).length);
|
|
106
|
+
const criticalRiskPenalty = Math.min(20, 5 * loadBearing.filter((claim) => claim.if_false === 'STOP' && claim.evidence_state !== 'SUPPORTED').length);
|
|
107
|
+
let score = Math.round(verificationCoverage * 40 + independentConvergence * 25 + evidenceQuality * 20 + stability * 15 - criticalRiskPenalty);
|
|
108
|
+
if (verificationCoverage < 0.5)
|
|
109
|
+
score = Math.min(score, 79); // consensus alone never yields High
|
|
110
|
+
score = Math.max(0, Math.min(100, score));
|
|
111
|
+
const label = score >= 80 ? 'High' : score >= 60 ? 'Medium' : 'Low';
|
|
112
|
+
return { score, label, verificationCoverage, independentConvergence, evidenceQuality, stability, criticalRiskPenalty };
|
|
113
|
+
}
|
|
114
|
+
const STANCE_MAP = { SUPPORT: 'AGREE', OPPOSE: 'DISAGREE', MIXED: 'CONDITIONAL', UNKNOWN: 'UNKNOWN' };
|
|
115
|
+
const VERIFICATION_MAP = { SUPPORTED: 'VERIFIED', CONFLICTED: 'PARTIAL', UNVERIFIED: 'UNVERIFIED' };
|
|
116
|
+
const SEVERITY_MAP = { DECISIVE: 'High', MATERIAL: 'Medium', LOW: 'Low' };
|
|
117
|
+
function claimRuling(claim, adjudication) {
|
|
118
|
+
// UPHOLD/REJECT inverts ONLY on a disagreement (the ruling is on the objection); for every other
|
|
119
|
+
// state the ruling is evidence-based, matching deriveAudit's semantics.
|
|
120
|
+
if (claim.state === 'DISAGREEMENT') {
|
|
121
|
+
if (!adjudication || adjudication.ruling === 'UNRESOLVED')
|
|
122
|
+
return 'UNRESOLVED';
|
|
123
|
+
return adjudication.ruling === 'UPHOLD' ? 'REJECTED' : 'ACCEPTED';
|
|
123
124
|
}
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
if (claim.state === 'UNCERTAIN')
|
|
126
|
+
return 'UNRESOLVED';
|
|
127
|
+
return claim.evidence_state === 'SUPPORTED' ? 'ACCEPTED' : 'CONDITIONAL';
|
|
128
|
+
}
|
|
129
|
+
function verificationStatusByClaim(verifications) {
|
|
130
|
+
const statuses = new Map();
|
|
131
|
+
for (const item of verifications.verifications) {
|
|
132
|
+
if ('claim_id' in item)
|
|
133
|
+
statuses.set(item.claim_id, item.status);
|
|
134
|
+
else
|
|
135
|
+
statuses.set(item.target_id, item.verdict === 'CONFIRM' ? 'VERIFIED' : item.verdict === 'REFUTE' ? 'CONTRADICTED' : 'PARTIAL');
|
|
129
136
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
return statuses;
|
|
138
|
+
}
|
|
139
|
+
function buildDossier(args) {
|
|
140
|
+
const { graph, judgeReport, actionPlan, rebuttals, seats } = args;
|
|
141
|
+
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
142
|
+
const reportClaimById = new Map(args.claims.map((claim) => [claim.id, claim]));
|
|
143
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
144
|
+
const verificationById = verificationStatusByClaim(args.verifications);
|
|
145
|
+
const verifiedClaimIds = new Set([...verificationById].filter(([, status]) => status === 'VERIFIED').map(([id]) => id));
|
|
146
|
+
const dependencies = new Map();
|
|
147
|
+
for (const edge of graph.edges) {
|
|
148
|
+
if (edge.type !== 'DEPENDS_ON' || !claimById.has(edge.from) || !claimById.has(edge.to))
|
|
149
|
+
continue;
|
|
150
|
+
const ids = dependencies.get(edge.from) ?? [];
|
|
151
|
+
if (!ids.includes(edge.to))
|
|
152
|
+
ids.push(edge.to);
|
|
153
|
+
dependencies.set(edge.from, ids);
|
|
140
154
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
const recommendationIds = (judgeReport.recommendation_claim_ids ?? [])
|
|
156
|
+
.filter((id) => claimById.has(id));
|
|
157
|
+
const anchors = recommendationIds.length
|
|
158
|
+
? recommendationIds
|
|
159
|
+
: graph.claims.filter((claim) => claim.load_bearing).slice(0, 8).map((claim) => claim.id);
|
|
160
|
+
const chainIds = [];
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
const visit = (id) => {
|
|
163
|
+
if (seen.has(id))
|
|
164
|
+
return;
|
|
165
|
+
seen.add(id);
|
|
166
|
+
for (const dependency of dependencies.get(id) ?? [])
|
|
167
|
+
visit(dependency);
|
|
168
|
+
chainIds.push(id);
|
|
169
|
+
};
|
|
170
|
+
anchors.forEach(visit);
|
|
171
|
+
const claimChain = chainIds.flatMap((id) => {
|
|
172
|
+
const claim = reportClaimById.get(id);
|
|
173
|
+
return claim ? [{
|
|
174
|
+
claimId: id,
|
|
175
|
+
text: claim.text,
|
|
176
|
+
ruling: claim.ruling,
|
|
177
|
+
evidenceStatus: verificationById.get(id) ?? claim.verification,
|
|
178
|
+
dependsOn: dependencies.get(id) ?? [],
|
|
179
|
+
}] : [];
|
|
180
|
+
});
|
|
181
|
+
const evidence = graph.evidence.map((item) => {
|
|
182
|
+
const claimIds = graph.edges
|
|
183
|
+
.filter((edge) => edge.from === item.id && (edge.type === 'SUPPORTS' || edge.type === 'ATTACKS') && claimById.has(edge.to))
|
|
184
|
+
.map((edge) => edge.to);
|
|
185
|
+
const statuses = claimIds.map((id) => verificationById.get(id)).filter((status) => Boolean(status));
|
|
186
|
+
const verificationStatus = statuses.includes('CONTRADICTED') ? 'CONTRADICTED'
|
|
187
|
+
: statuses.length > 0 && statuses.every((status) => status === 'VERIFIED') ? 'VERIFIED'
|
|
188
|
+
: statuses.includes('UNVERIFIABLE') ? 'UNVERIFIABLE'
|
|
189
|
+
: statuses.length ? 'PARTIAL' : 'NOT_CHECKED';
|
|
190
|
+
return {
|
|
191
|
+
id: item.id,
|
|
192
|
+
source: item.locator ?? (item.source_kind === 'MODEL_KNOWLEDGE' ? `${disp(item.provider)} model knowledge` : item.source_kind),
|
|
193
|
+
sourceKind: item.source_kind,
|
|
194
|
+
date: item.accessed_at ?? 'Not recorded',
|
|
195
|
+
freshness: item.freshness,
|
|
196
|
+
verificationStatus,
|
|
197
|
+
claimIds,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
const positionChanges = (rebuttals?.events ?? []).map((event) => ({
|
|
201
|
+
eventId: event.id,
|
|
202
|
+
claimId: event.claim_id,
|
|
203
|
+
responder: event.responder,
|
|
204
|
+
response: event.response,
|
|
205
|
+
reasoning: event.reasoning,
|
|
206
|
+
evidenceIds: event.evidence_ids,
|
|
207
|
+
...(event.narrowed_proposition ? { narrowedProposition: event.narrowed_proposition } : {}),
|
|
208
|
+
}));
|
|
209
|
+
const claimProviders = (claimId) => {
|
|
210
|
+
const claim = claimById.get(claimId);
|
|
211
|
+
if (!claim)
|
|
212
|
+
return [];
|
|
213
|
+
return [...new Set(claim.position_ids.map((id) => positionById.get(id).provider))];
|
|
214
|
+
};
|
|
215
|
+
const sharedConcerns = graph.claims.filter((claim) => claim.state === 'SHARED_CONCERN').map((claim) => ({
|
|
216
|
+
claimId: claim.id,
|
|
217
|
+
text: claim.proposition,
|
|
218
|
+
providerIds: claimProviders(claim.id),
|
|
219
|
+
evidenceStatus: verificationById.get(claim.id) ?? VERIFICATION_MAP[claim.evidence_state],
|
|
220
|
+
}));
|
|
221
|
+
const uniqueSupportedInsights = graph.claims.filter((claim) => claim.state === 'UNIQUE' && claim.evidence_state === 'SUPPORTED').flatMap((claim) => {
|
|
222
|
+
const providerId = claimProviders(claim.id)[0];
|
|
223
|
+
return providerId ? [{
|
|
224
|
+
claimId: claim.id,
|
|
225
|
+
text: claim.proposition,
|
|
226
|
+
providerId,
|
|
227
|
+
verificationStatus: verificationById.get(claim.id) ?? 'NOT_CHECKED',
|
|
228
|
+
}] : [];
|
|
229
|
+
});
|
|
230
|
+
const claimByPosition = new Map(graph.claims.flatMap((claim) => claim.position_ids.map((id) => [id, claim.id])));
|
|
231
|
+
const missingCoverage = new Set(graph.holes.coverage.map((hole) => hole.dimension_id));
|
|
232
|
+
const missingEvidence = new Set(graph.holes.evidence.map((hole) => hole.claim_id));
|
|
233
|
+
const coverage = args.rubric.map((dimension) => {
|
|
234
|
+
const claimIds = [...new Set(graph.positions
|
|
235
|
+
.filter((position) => position.dimension_id === dimension.id)
|
|
236
|
+
.map((position) => claimByPosition.get(position.id))
|
|
237
|
+
.filter((id) => Boolean(id)))];
|
|
238
|
+
const notApplicable = seats.some((seat) => seat.output.coverage.find((entry) => entry.dimension_id === dimension.id)?.status === 'NOT_APPLICABLE');
|
|
239
|
+
const status = missingCoverage.has(dimension.id) ? 'MISSING'
|
|
240
|
+
: claimIds.some((id) => missingEvidence.has(id)) ? 'MISSING_EVIDENCE'
|
|
241
|
+
: notApplicable && claimIds.length === 0 ? 'NOT_APPLICABLE' : 'COVERED';
|
|
242
|
+
return { dimensionId: dimension.id, label: dimension.label, status, claimIds };
|
|
243
|
+
});
|
|
244
|
+
const adjudicationById = new Map(judgeReport.adjudications.map((item) => [item.id, item]));
|
|
245
|
+
const sensitivity = graph.claims
|
|
246
|
+
.filter((claim) => claim.load_bearing && claim.sensitivity !== 'LOW')
|
|
247
|
+
.sort((a, b) => (a.sensitivity === 'DECISIVE' ? 0 : 1) - (b.sensitivity === 'DECISIVE' ? 0 : 1))
|
|
248
|
+
.map((claim) => {
|
|
249
|
+
const related = graph.edges
|
|
250
|
+
.filter((edge) => (edge.from === claim.id || edge.to === claim.id) && claimById.has(edge.from) && claimById.has(edge.to))
|
|
251
|
+
.map((edge) => edge.from === claim.id ? edge.to : edge.from);
|
|
252
|
+
const experiment = actionPlan && !('kind' in actionPlan)
|
|
253
|
+
? actionPlan.actions.find((action) => action.validates === claim.id) : undefined;
|
|
254
|
+
const evidenceHole = graph.holes.evidence.find((hole) => hole.claim_id === claim.id);
|
|
255
|
+
return {
|
|
256
|
+
claimId: claim.id,
|
|
257
|
+
fact: claim.proposition,
|
|
258
|
+
sensitivity: claim.sensitivity,
|
|
259
|
+
impactIfFalse: claim.if_false,
|
|
260
|
+
whatWouldChangeIt: adjudicationById.get(claim.id)?.what_would_change_it
|
|
261
|
+
?? experiment?.kill_signal
|
|
262
|
+
?? evidenceHole?.reason
|
|
263
|
+
?? `Resolve the evidence status for ${claim.id}.`,
|
|
264
|
+
linkedClaimIds: [...new Set(related)],
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
const experiments = actionPlan && !('kind' in actionPlan)
|
|
268
|
+
? {
|
|
269
|
+
status: 'AVAILABLE',
|
|
270
|
+
note: actionPlan.sequencing_note,
|
|
271
|
+
actions: actionPlan.actions.map((action) => ({
|
|
272
|
+
order: action.order,
|
|
273
|
+
action: action.action,
|
|
274
|
+
why: action.why,
|
|
275
|
+
validates: action.validates,
|
|
276
|
+
effort: action.effort,
|
|
277
|
+
killSignal: action.kill_signal,
|
|
278
|
+
})),
|
|
150
279
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
280
|
+
: {
|
|
281
|
+
status: 'DEGRADED',
|
|
282
|
+
note: actionPlan && 'kind' in actionPlan
|
|
283
|
+
? `Planner unavailable: ${actionPlan.reason}. Unresolved: ${actionPlan.unresolved_questions.join('; ')}`
|
|
284
|
+
: 'No planner artifact was recorded.',
|
|
285
|
+
actions: [],
|
|
286
|
+
};
|
|
287
|
+
const counter = judgeReport.strongest_counter_case;
|
|
288
|
+
const contributions = args.models.map((model) => ({
|
|
289
|
+
provider: model.provider,
|
|
290
|
+
name: model.name,
|
|
291
|
+
verifiedUniqueClaimIds: graph.claims
|
|
292
|
+
.filter((claim) => claim.state === 'UNIQUE' && verifiedClaimIds.has(claim.id) && claimProviders(claim.id).includes(model.provider))
|
|
293
|
+
.map((claim) => claim.id),
|
|
294
|
+
}));
|
|
295
|
+
const technicalPositions = graph.positions.map((position) => ({
|
|
296
|
+
id: position.id,
|
|
297
|
+
provider: position.provider,
|
|
298
|
+
stance: position.stance,
|
|
299
|
+
proposition: position.proposition,
|
|
300
|
+
evidenceIds: graph.evidence
|
|
301
|
+
.filter((evidence) => evidence.provider === position.provider
|
|
302
|
+
&& evidence.source_id === position.source_id
|
|
303
|
+
&& position.evidence_ids.some((id) => id === evidence.id || evidence.id.endsWith(`/${id}`)))
|
|
304
|
+
.map((evidence) => evidence.id),
|
|
305
|
+
}));
|
|
306
|
+
return {
|
|
307
|
+
recommendation: {
|
|
308
|
+
status: args.status,
|
|
309
|
+
summary: args.summary,
|
|
310
|
+
reason: args.reason,
|
|
311
|
+
claimIds: recommendationIds,
|
|
312
|
+
conditions: (judgeReport.conditions ?? []).map((text) => ({
|
|
313
|
+
text,
|
|
314
|
+
claimIds: (judgeReport.condition_claim_ids ?? []).filter((id) => claimById.has(id)),
|
|
315
|
+
})),
|
|
316
|
+
},
|
|
317
|
+
claimChain,
|
|
318
|
+
evidence,
|
|
319
|
+
positionChanges,
|
|
320
|
+
sharedConcerns,
|
|
321
|
+
uniqueSupportedInsights,
|
|
322
|
+
coverage,
|
|
323
|
+
sensitivity,
|
|
324
|
+
experiments,
|
|
325
|
+
counterCase: counter
|
|
326
|
+
? { available: true, reasoning: counter.reasoning, claimIds: counter.claim_ids.filter((id) => claimById.has(id)) }
|
|
327
|
+
: { available: false, reasoning: 'No graph-anchored counter-case was recorded.', claimIds: [] },
|
|
328
|
+
contributions,
|
|
329
|
+
technical: {
|
|
330
|
+
submissions: seats.map((seat) => ({
|
|
331
|
+
provider: seat.provider,
|
|
332
|
+
name: disp(seat.provider),
|
|
333
|
+
strongestVersion: seat.output.strongest_version,
|
|
334
|
+
positionIds: graph.positions.filter((position) => position.provider === seat.provider).map((position) => position.id),
|
|
335
|
+
})),
|
|
336
|
+
positions: technicalPositions,
|
|
337
|
+
edges: graph.edges,
|
|
338
|
+
events: positionChanges,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** Assemble the machine-readable report deterministically from the run's persisted artifacts. */
|
|
343
|
+
export function buildDecisionReport(ctx, args) {
|
|
344
|
+
const { contract, seats, graph, verifications, judgeReport, actionPlan } = args;
|
|
345
|
+
const mode = ctx.mode ?? 'council';
|
|
346
|
+
const flags = new Set(ctx.flags);
|
|
347
|
+
const confidence = computeConfidence(graph, flags);
|
|
348
|
+
const status = statusFrom(judgeReport);
|
|
349
|
+
const rulingById = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
350
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
351
|
+
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
352
|
+
const openQuestions = mergeOpenQuestions(seats);
|
|
353
|
+
const claims = graph.claims.map((claim) => {
|
|
354
|
+
const stances = {};
|
|
355
|
+
for (const id of claim.position_ids) {
|
|
356
|
+
const position = positionById.get(id);
|
|
357
|
+
stances[position.provider] ??= STANCE_MAP[position.stance] ?? 'UNKNOWN';
|
|
157
358
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
359
|
+
return {
|
|
360
|
+
id: claim.id,
|
|
361
|
+
text: claim.proposition,
|
|
362
|
+
stances,
|
|
363
|
+
verification: VERIFICATION_MAP[claim.evidence_state],
|
|
364
|
+
ruling: claimRuling(claim, rulingById.get(claim.id)),
|
|
365
|
+
loadBearing: claim.load_bearing,
|
|
366
|
+
sensitivity: claim.sensitivity,
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
const disagreements = graph.claims.filter((claim) => claim.state === 'DISAGREEMENT').map((claim) => {
|
|
370
|
+
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
371
|
+
const sides = ['SUPPORT', 'OPPOSE', 'MIXED'].flatMap((stance) => {
|
|
372
|
+
const inStance = positions.filter((position) => position.stance === stance);
|
|
373
|
+
return inStance.length
|
|
374
|
+
? [{ stance, providers: inStance.map((position) => disp(position.provider)), reasoning: inStance.map((position) => position.reasoning) }]
|
|
375
|
+
: [];
|
|
376
|
+
});
|
|
377
|
+
const adjudication = rulingById.get(claim.id);
|
|
378
|
+
return {
|
|
379
|
+
id: claim.id,
|
|
380
|
+
topic: claim.proposition,
|
|
381
|
+
sides,
|
|
382
|
+
ruling: rulingPhrase(adjudication?.ruling),
|
|
383
|
+
reasoning: adjudication?.reasoning ?? null,
|
|
384
|
+
status: (adjudication && adjudication.ruling !== 'UNRESOLVED' ? 'RESOLVED' : 'UNRESOLVED'),
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
const uniqueOppositions = graph.claims
|
|
388
|
+
.filter((claim) => claim.state === 'UNIQUE')
|
|
389
|
+
.flatMap((claim) => claim.position_ids.map((id) => positionById.get(id)))
|
|
390
|
+
.filter((position) => position.stance === 'OPPOSE')
|
|
391
|
+
.map((position) => ({ provider: disp(position.provider), proposition: position.proposition }));
|
|
392
|
+
const unresolvedDecisive = disagreements.some((d) => d.status === 'UNRESOLVED' && claimById.get(d.id)?.sensitivity === 'DECISIVE');
|
|
393
|
+
const consensusType = mode === 'quick' ? 'single_analyst' : disagreements.length === 0
|
|
394
|
+
? (claims.some((claim) => claim.ruling === 'UNRESOLVED') ? 'convergent_with_unresolved_claims' : 'unanimous')
|
|
395
|
+
: 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
|
+
// Frame the slot as what it is — a decisive assumption that is NOT proven. Echoing the bare
|
|
400
|
+
// proposition inverts the meaning when the claim is phrased affirmatively (run 20260714-2321:
|
|
401
|
+
// "a cutover can preserve compliance" read as reassurance, the opposite of the verdict).
|
|
402
|
+
const criticalClaim = graph.claims.find((claim) => claim.load_bearing && claim.if_false === 'STOP' && claim.evidence_state !== 'SUPPORTED');
|
|
403
|
+
const criticalWarning = criticalClaim
|
|
404
|
+
? `Unverified decisive assumption (if false, STOP): ${criticalClaim.proposition} — evidence ${criticalClaim.evidence_state}.`
|
|
405
|
+
: null;
|
|
406
|
+
const verificationResults = verifications.verifications.map((v) => {
|
|
407
|
+
if ('claim_id' in v) {
|
|
408
|
+
return {
|
|
409
|
+
claimId: v.claim_id,
|
|
410
|
+
claim: claimById.get(v.claim_id)?.proposition ?? v.claim_id,
|
|
411
|
+
method: 'independent verifier model',
|
|
412
|
+
verdict: (v.status === 'VERIFIED' ? 'CONFIRMED' : v.status === 'CONTRADICTED' ? 'REFUTED' : 'UNCERTAIN'),
|
|
413
|
+
evidence: v.evidence_ids.length ? v.evidence_ids.join(', ') : v.missing_evidence.join('; '),
|
|
414
|
+
note: v.reasoning,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
claimId: v.target_id,
|
|
419
|
+
claim: claimById.get(v.target_id)?.proposition ?? v.target_id,
|
|
420
|
+
method: 'independent verifier model',
|
|
421
|
+
verdict: (v.verdict === 'CONFIRM' ? 'CONFIRMED' : v.verdict === 'REFUTE' ? 'REFUTED' : 'UNCERTAIN'),
|
|
422
|
+
evidence: v.evidence,
|
|
423
|
+
note: v.note,
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
const byProvider = {};
|
|
427
|
+
let modelTimeMs = 0;
|
|
428
|
+
for (const call of ctx.calls) {
|
|
429
|
+
byProvider[call.provider] = (byProvider[call.provider] ?? 0) + 1;
|
|
430
|
+
modelTimeMs += call.durationMs;
|
|
191
431
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
432
|
+
const roles = ctx.roles;
|
|
433
|
+
const reportProviders = mode === 'quick' ? [...new Set(seats.map((seat) => seat.provider))] : ctx.available();
|
|
434
|
+
const models = reportProviders.map((provider) => ({
|
|
435
|
+
provider,
|
|
436
|
+
name: disp(provider),
|
|
437
|
+
roles: mode === 'quick' ? ['single analyst'] : [
|
|
438
|
+
...(roles.analyst === provider ? ['analyst'] : []),
|
|
439
|
+
...(roles.judge === provider ? ['judge'] : []),
|
|
440
|
+
...(roles.verifier === provider ? ['verifier'] : []),
|
|
441
|
+
...(roles.s4.includes(provider) ? ['scout'] : []),
|
|
442
|
+
],
|
|
443
|
+
}));
|
|
444
|
+
const positions = seats.map((seat) => {
|
|
445
|
+
const seatPositions = seat.output.positions;
|
|
446
|
+
const oppose = seatPositions.filter((position) => position.stance === 'OPPOSE').length;
|
|
447
|
+
const support = seatPositions.filter((position) => position.stance === 'SUPPORT').length;
|
|
448
|
+
return {
|
|
449
|
+
provider: seat.provider,
|
|
450
|
+
name: disp(seat.provider),
|
|
451
|
+
initialConclusion: seat.output.strongest_version,
|
|
452
|
+
mainArgument: seatPositions.find((position) => position.load_bearing)?.reasoning ?? seatPositions[0]?.reasoning ?? '—',
|
|
453
|
+
keyRisk: seatPositions.find((position) => position.stance === 'OPPOSE')?.proposition ?? '—',
|
|
454
|
+
finalPosition: (oppose > support ? 'Oppose' : oppose > 0 || status === 'ACCEPTED_WITH_CONDITIONS' ? 'Conditional' : 'Support'),
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
const dossier = buildDossier({
|
|
458
|
+
status,
|
|
459
|
+
summary: judgeReport.verdict,
|
|
460
|
+
reason: judgeReport.key_points?.[0] ?? judgeReport.verdict,
|
|
461
|
+
claims,
|
|
462
|
+
models,
|
|
463
|
+
seats,
|
|
464
|
+
graph,
|
|
465
|
+
verifications,
|
|
466
|
+
judgeReport,
|
|
467
|
+
actionPlan,
|
|
468
|
+
rebuttals: args.rebuttals,
|
|
469
|
+
rubric: args.rubric ?? [],
|
|
470
|
+
});
|
|
471
|
+
const decisionSnapshot = judgeReport.decision_snapshot ? {
|
|
472
|
+
decisiveNumbers: judgeReport.decision_snapshot.decisive_numbers.map((item) => ({
|
|
473
|
+
label: item.label,
|
|
474
|
+
value: item.value,
|
|
475
|
+
meaning: item.meaning,
|
|
476
|
+
claimIds: item.claim_ids,
|
|
477
|
+
})),
|
|
478
|
+
...(judgeReport.decision_snapshot.payback ? {
|
|
479
|
+
payback: {
|
|
480
|
+
status: judgeReport.decision_snapshot.payback.status,
|
|
481
|
+
result: judgeReport.decision_snapshot.payback.result,
|
|
482
|
+
basis: judgeReport.decision_snapshot.payback.basis,
|
|
483
|
+
claimIds: judgeReport.decision_snapshot.payback.claim_ids,
|
|
484
|
+
},
|
|
485
|
+
} : {}),
|
|
486
|
+
options: judgeReport.decision_snapshot.options.map((option) => ({
|
|
487
|
+
label: option.label,
|
|
488
|
+
commitment: option.commitment,
|
|
489
|
+
commitmentKind: option.commitment_kind,
|
|
490
|
+
tradeoff: option.tradeoff,
|
|
491
|
+
claimIds: option.claim_ids,
|
|
492
|
+
})),
|
|
493
|
+
...(judgeReport.decision_snapshot.tripwire ? {
|
|
494
|
+
tripwire: {
|
|
495
|
+
metric: judgeReport.decision_snapshot.tripwire.metric,
|
|
496
|
+
threshold: judgeReport.decision_snapshot.tripwire.threshold,
|
|
497
|
+
decisionRule: judgeReport.decision_snapshot.tripwire.decision_rule,
|
|
498
|
+
claimIds: judgeReport.decision_snapshot.tripwire.claim_ids,
|
|
499
|
+
},
|
|
500
|
+
} : {}),
|
|
501
|
+
} : undefined;
|
|
502
|
+
return {
|
|
503
|
+
reportId: ctx.runId,
|
|
504
|
+
generatedAt: new Date().toISOString(),
|
|
505
|
+
mode,
|
|
506
|
+
task: {
|
|
507
|
+
original: args.original ?? contract.task,
|
|
508
|
+
normalized: contract.task,
|
|
509
|
+
type: contract.task_type,
|
|
510
|
+
constraints: contract.constraints,
|
|
511
|
+
successCriteria: contract.success_criteria,
|
|
512
|
+
...('confirmation' in contract && typeof contract.confirmation === 'string' ? { confirmation: contract.confirmation } : {}),
|
|
513
|
+
},
|
|
514
|
+
verdict: {
|
|
515
|
+
status,
|
|
516
|
+
summary: judgeReport.verdict,
|
|
517
|
+
confidence: confidence.score / 100,
|
|
518
|
+
confidenceLabel: confidence.label,
|
|
519
|
+
consensusType,
|
|
520
|
+
conditions: judgeReport.conditions ?? [],
|
|
521
|
+
primaryReason: judgeReport.key_points?.[0] ?? judgeReport.verdict,
|
|
522
|
+
criticalWarning,
|
|
523
|
+
},
|
|
524
|
+
keyFindings: (judgeReport.key_points?.length ? judgeReport.key_points : [judgeReport.verdict]).slice(0, 4),
|
|
525
|
+
criticalUnknowns: openQuestions.slice(0, 3),
|
|
526
|
+
...(decisionSnapshot ? { decisionSnapshot } : {}),
|
|
527
|
+
confidenceBreakdown: confidence,
|
|
528
|
+
models,
|
|
529
|
+
positions,
|
|
530
|
+
claims,
|
|
531
|
+
consensusSummary: {
|
|
532
|
+
unanimous: graph.claims.filter((claim) => claim.state === 'CONSENSUS').length,
|
|
533
|
+
accepted: claims.filter((claim) => claim.ruling === 'ACCEPTED').length,
|
|
534
|
+
conditional: claims.filter((claim) => claim.ruling === 'CONDITIONAL').length,
|
|
535
|
+
unresolved: claims.filter((claim) => claim.ruling === 'UNRESOLVED').length,
|
|
536
|
+
rejected: claims.filter((claim) => claim.ruling === 'REJECTED').length,
|
|
537
|
+
},
|
|
538
|
+
disagreements,
|
|
539
|
+
minority: {
|
|
540
|
+
dissent: judgeReport.dissent,
|
|
541
|
+
uniqueOppositions,
|
|
542
|
+
blocksDecision: unresolvedDecisive ? 'YES' : (judgeReport.conditions?.length ? 'ONLY_IF_CONDITION' : 'NO'),
|
|
543
|
+
},
|
|
544
|
+
verification: {
|
|
545
|
+
results: verificationResults,
|
|
546
|
+
confirmed: verificationResults.filter((result) => result.verdict === 'CONFIRMED').length,
|
|
547
|
+
refuted: verificationResults.filter((result) => result.verdict === 'REFUTED').length,
|
|
548
|
+
uncertain: verificationResults.filter((result) => result.verdict === 'UNCERTAIN').length,
|
|
549
|
+
},
|
|
550
|
+
risks,
|
|
551
|
+
recommendedActions: actionPlan && !('kind' in actionPlan)
|
|
552
|
+
? actionPlan.actions.map((action) => ({ order: action.order, action: action.action, why: action.why, effort: action.effort, killSignal: action.kill_signal }))
|
|
553
|
+
: [],
|
|
554
|
+
openQuestions,
|
|
555
|
+
flags: [...ctx.flags],
|
|
556
|
+
receipt: { calls: ctx.calls.length, budget: ctx.budget.limit, byProvider, modelTimeMs, categories: receiptCategories(ctx) },
|
|
557
|
+
dossier,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
561
|
+
export { renderDecisionDossierMarkdown };
|
|
562
|
+
export function renderReport(ctx, args) {
|
|
563
|
+
return renderDecisionDossierMarkdown(buildDecisionReport(ctx, args));
|
|
564
|
+
}
|
|
565
|
+
// ── Terminal summary (level 1) ───────────────────────────────────────────────
|
|
566
|
+
const RULE = '─'.repeat(52);
|
|
567
|
+
export function renderTerminalSummary(report, paths) {
|
|
568
|
+
const summary = report.consensusSummary;
|
|
569
|
+
const c = report.confidenceBreakdown;
|
|
570
|
+
const snapshot = report.decisionSnapshot;
|
|
571
|
+
const decisiveLines = snapshot ? [
|
|
572
|
+
'Decision numbers:',
|
|
573
|
+
...snapshot.decisiveNumbers.slice(0, 3).map((item) => `${item.label}: ${item.value} — ${item.meaning}`),
|
|
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
|
+
];
|
|
584
|
+
return [
|
|
585
|
+
report.mode === 'quick' ? 'SINGLE-MODEL DECISION REPORT' : 'MULTI-MODEL DECISION REPORT',
|
|
586
|
+
RULE,
|
|
587
|
+
`Verdict: ${report.verdict.summary}`,
|
|
588
|
+
`Decision state: ${report.verdict.status}`,
|
|
589
|
+
`Evidence coverage: ${pct(c.verificationCoverage)} of load-bearing claims independently verified`,
|
|
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,
|
|
594
|
+
'',
|
|
595
|
+
...(report.verdict.criticalWarning ? ['Critical warning:', report.verdict.criticalWarning, ''] : []),
|
|
596
|
+
...(report.minority.dissent[0] ? ['Critical dissent:', report.minority.dissent[0], ''] : []),
|
|
597
|
+
'Verification:',
|
|
598
|
+
...checks,
|
|
599
|
+
'',
|
|
600
|
+
...(report.recommendedActions[0] ? ['Recommended action:', report.recommendedActions[0].action, ''] : []),
|
|
601
|
+
...(snapshot?.tripwire ? ['Go/no-go tripwire:', `${snapshot.tripwire.metric}: ${snapshot.tripwire.threshold} — ${snapshot.tripwire.decisionRule}`, ''] : []),
|
|
602
|
+
`Full report: ${paths.markdownPath}`,
|
|
603
|
+
`Audit JSON: ${paths.jsonPath}`,
|
|
604
|
+
].join('\n');
|
|
195
605
|
}
|
|
196
606
|
export async function s10Render(ctx, args) {
|
|
197
|
-
|
|
607
|
+
const report = buildDecisionReport(ctx, args);
|
|
608
|
+
await ctx.writer.writeJson('decision-report', report);
|
|
609
|
+
await ctx.writer.writeText('final-report', renderDecisionDossierMarkdown(report));
|
|
198
610
|
}
|