aiki-cli 0.2.2 → 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 +98 -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 +31 -6
- package/dist/cli/resolve.js +7 -6
- package/dist/cli/resume.js +18 -0
- package/dist/cli/run.js +63 -8
- 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
package/dist/council/view.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
3
|
import { DISPLAY_NAME } from '../providers/types.js';
|
|
4
|
+
import { RoleOutput as RoleOutputSchema } from '../schemas/index.js';
|
|
4
5
|
import { deriveAudit, deriveScorecard, mergeOpenQuestions } from '../orchestration/stages/s10-render.js';
|
|
6
|
+
import { renderDecisionDossierMarkdown } from '../orchestration/decision-dossier.js';
|
|
5
7
|
import { IDEA_RUBRIC } from '../workflows/idea-refinement.js';
|
|
6
8
|
import { listArtifacts, readJsonArtifact } from '../storage/runs-read.js';
|
|
9
|
+
import { adaptIdeaOutput, adaptLegacyDecisionGraph } from '../orchestration/legacy-idea-adapter.js';
|
|
7
10
|
function providerName(id) {
|
|
8
11
|
return id in DISPLAY_NAME ? DISPLAY_NAME[id] : id;
|
|
9
12
|
}
|
|
@@ -23,9 +26,8 @@ function roleColumn(provider, role) {
|
|
|
23
26
|
title: providerName(provider),
|
|
24
27
|
lines: [
|
|
25
28
|
`Strongest version: ${role.strongest_version}`,
|
|
26
|
-
...role.
|
|
27
|
-
...role.
|
|
28
|
-
...role.open_questions.map((q) => `Question: ${q}`),
|
|
29
|
+
...role.positions.map((position) => `${position.load_bearing ? 'Load-bearing' : position.stance}: ${position.proposition}`),
|
|
30
|
+
...role.decision_questions.map(({ question }) => `Question: ${question}`),
|
|
29
31
|
],
|
|
30
32
|
};
|
|
31
33
|
}
|
|
@@ -47,27 +49,28 @@ function codeReviewRows(map, judge) {
|
|
|
47
49
|
...map.single_reviewer.map((a) => row('single', a)),
|
|
48
50
|
];
|
|
49
51
|
}
|
|
50
|
-
function
|
|
52
|
+
function graphRows(graph, judge) {
|
|
51
53
|
const rulings = judgeRulings(judge);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
55
|
+
return graph.claims.map((claim) => {
|
|
56
|
+
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
57
|
+
const providers = [...new Set(positions.map((position) => position.provider))];
|
|
58
|
+
const kind = claim.state === 'DISAGREEMENT' ? 'dispute'
|
|
59
|
+
: claim.state === 'CONSENSUS' || claim.state === 'SHARED_CONCERN' ? 'consensus'
|
|
60
|
+
: 'unique';
|
|
61
|
+
return {
|
|
62
|
+
kind,
|
|
63
|
+
title: claim.proposition,
|
|
64
|
+
detail: positions.map((position) => `${providerName(position.provider)} ${position.stance}: ${position.reasoning}`).join(' · '),
|
|
65
|
+
providers,
|
|
66
|
+
ruling: rulings.get(claim.id),
|
|
67
|
+
};
|
|
68
|
+
});
|
|
63
69
|
}
|
|
64
70
|
const SEV_ORDER = { HIGH: 0, MED: 1, MEDIUM: 1, LOW: 2 };
|
|
65
71
|
function sevRank(s) {
|
|
66
72
|
return SEV_ORDER[s.toUpperCase()] ?? 1;
|
|
67
73
|
}
|
|
68
|
-
function maxSeverity(sevs) {
|
|
69
|
-
return [...sevs].sort((a, b) => sevRank(a) - sevRank(b))[0] ?? 'MED';
|
|
70
|
-
}
|
|
71
74
|
function clip(s, n) {
|
|
72
75
|
const t = s.trim();
|
|
73
76
|
return t.length > n ? `${t.slice(0, n - 1).trimEnd()}…` : t;
|
|
@@ -107,20 +110,21 @@ function chairPhrase(ruling) {
|
|
|
107
110
|
return 'the idea holds here';
|
|
108
111
|
return 'left to you';
|
|
109
112
|
}
|
|
110
|
-
function
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
return
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
113
|
+
function graphDebates(graph, judge) {
|
|
114
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
115
|
+
const adjudication = new Map((judge?.adjudications ?? []).map((item) => [item.id, item]));
|
|
116
|
+
return graph.claims.filter((claim) => claim.state === 'DISAGREEMENT').map((claim) => {
|
|
117
|
+
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
118
|
+
const supporting = positions.filter((position) => position.stance === 'SUPPORT');
|
|
119
|
+
const opposing = positions.filter((position) => position.stance === 'OPPOSE');
|
|
120
|
+
const ruling = adjudication.get(claim.id);
|
|
117
121
|
return {
|
|
118
|
-
claim:
|
|
119
|
-
claimantProviders,
|
|
120
|
-
attackerProviders,
|
|
121
|
-
challenge:
|
|
122
|
-
chair: chairPhrase(
|
|
123
|
-
reasoning:
|
|
122
|
+
claim: claim.proposition,
|
|
123
|
+
claimantProviders: [...new Set(supporting.map((position) => position.provider))],
|
|
124
|
+
attackerProviders: [...new Set(opposing.map((position) => position.provider))],
|
|
125
|
+
challenge: opposing.map((position) => `${providerName(position.provider)}: ${position.reasoning}`).join('\n\n'),
|
|
126
|
+
chair: chairPhrase(ruling?.ruling),
|
|
127
|
+
reasoning: ruling?.reasoning ?? '',
|
|
124
128
|
};
|
|
125
129
|
});
|
|
126
130
|
}
|
|
@@ -143,41 +147,50 @@ function receiptLines(meta) {
|
|
|
143
147
|
.join(' · ');
|
|
144
148
|
return [
|
|
145
149
|
`Calls: ${meta.call_count ?? calls.length}/${meta.budget?.limit ?? '?'}`,
|
|
150
|
+
...(meta.receipt ? [`Categories: discovery ${meta.receipt.discovery} · verification ${meta.receipt.verification} · repair ${meta.receipt.repair} · planning ${meta.receipt.planning}`] : []),
|
|
146
151
|
`Per-provider: ${providerCounts}`,
|
|
147
152
|
`Recorded model time: ${(ms / 1000).toFixed(1)}s`,
|
|
148
153
|
profiles ? `Models: ${profiles}` : '',
|
|
149
154
|
meta.flags?.length ? `Flags: ${meta.flags.join(', ')}` : '',
|
|
150
155
|
].filter(Boolean);
|
|
151
156
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
function graphNarrative(graph, judge) {
|
|
158
|
+
const positionById = new Map(graph.positions.map((position) => [position.id, position]));
|
|
159
|
+
const ruling = new Map((judge?.adjudications ?? []).map((item) => [item.id, item]));
|
|
160
|
+
const agreements = graph.claims
|
|
161
|
+
.filter((claim) => claim.state === 'CONSENSUS')
|
|
162
|
+
.map((claim) => ({
|
|
163
|
+
statement: claim.proposition,
|
|
164
|
+
providers: [...new Set(claim.position_ids.map((id) => positionById.get(id).provider))],
|
|
165
|
+
}));
|
|
166
|
+
const risks = graph.claims.flatMap((claim) => {
|
|
167
|
+
const decision = ruling.get(claim.id);
|
|
168
|
+
if (claim.state !== 'SHARED_CONCERN' && decision?.ruling !== 'UPHOLD')
|
|
169
|
+
return [];
|
|
170
|
+
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
171
|
+
return [{
|
|
172
|
+
assumption: claim.proposition,
|
|
173
|
+
severity: claim.sensitivity === 'DECISIVE' ? 'HIGH' : claim.sensitivity === 'MATERIAL' ? 'MED' : 'LOW',
|
|
174
|
+
challenge: positions.filter((position) => position.stance === 'OPPOSE').map((position) => `${providerName(position.provider)}: ${position.reasoning}`).join('\n\n'),
|
|
175
|
+
reasoning: decision?.reasoning ?? 'Multiple analysts independently raised this concern.',
|
|
176
|
+
providers: [...new Set(positions.map((position) => position.provider))],
|
|
177
|
+
}];
|
|
178
|
+
}).sort((a, b) => sevRank(a.severity) - sevRank(b.severity));
|
|
179
|
+
const defended = graph.claims.flatMap((claim) => {
|
|
180
|
+
const decision = ruling.get(claim.id);
|
|
181
|
+
if (decision?.ruling !== 'REJECT')
|
|
182
|
+
return [];
|
|
183
|
+
const positions = claim.position_ids.map((id) => positionById.get(id));
|
|
184
|
+
return [{
|
|
185
|
+
assumption: claim.proposition,
|
|
186
|
+
challenge: positions.filter((position) => position.stance === 'OPPOSE').map((position) => position.reasoning).join('\n\n'),
|
|
187
|
+
reasoning: decision.reasoning,
|
|
188
|
+
}];
|
|
168
189
|
});
|
|
169
|
-
const
|
|
170
|
-
.filter((d) => d.ruling === 'UPHOLD')
|
|
171
|
-
.sort((a, b) => sevRank(a.severity) - sevRank(b.severity))
|
|
172
|
-
.map(({ assumption, severity, challenge, reasoning, providers }) => ({ assumption, severity, challenge, reasoning, providers }));
|
|
173
|
-
const defended = disputes
|
|
174
|
-
.filter((d) => d.ruling === 'REJECT')
|
|
175
|
-
.map(({ assumption, challenge, reasoning }) => ({ assumption, challenge, reasoning }));
|
|
176
|
-
const agreements = map.consensus.map((c) => ({ statement: c.statement, providers: c.providers }));
|
|
177
|
-
const blindSpots = map.blind_spots ?? [];
|
|
190
|
+
const blindSpots = graph.holes.coverage.map((hole) => hole.label);
|
|
178
191
|
const nextSteps = [
|
|
179
|
-
...risks.map((
|
|
180
|
-
...blindSpots.map((
|
|
192
|
+
...risks.map((risk) => `Pressure-test “${clip(risk.assumption, 130)}”.`),
|
|
193
|
+
...blindSpots.map((spot) => `Work out: ${spot}.`),
|
|
181
194
|
];
|
|
182
195
|
return {
|
|
183
196
|
signal: computeSignal(risks.length, agreements.length),
|
|
@@ -193,21 +206,25 @@ function ideaNarrative(map, judge) {
|
|
|
193
206
|
async function loadRoleOutputs(dir) {
|
|
194
207
|
const artifacts = await listArtifacts(dir);
|
|
195
208
|
const roleFiles = artifacts.filter((f) => f.startsWith('04-role-outputs/') && f.endsWith('.json'));
|
|
196
|
-
const roles = await Promise.all(roleFiles.map(async (file) =>
|
|
197
|
-
|
|
198
|
-
role
|
|
199
|
-
|
|
209
|
+
const roles = await Promise.all(roleFiles.map(async (file) => {
|
|
210
|
+
const raw = JSON.parse(await readFile(join(dir, file), 'utf8'));
|
|
211
|
+
const role = typeof raw === 'object' && raw !== null && 'workflow' in raw && raw.workflow === 'idea-refinement'
|
|
212
|
+
? adaptIdeaOutput(raw)
|
|
213
|
+
: RoleOutputSchema.parse(raw);
|
|
214
|
+
return { provider: basename(file, '.json'), role };
|
|
215
|
+
}));
|
|
200
216
|
return roles.sort((a, b) => providerName(a.provider).localeCompare(providerName(b.provider)));
|
|
201
217
|
}
|
|
202
218
|
export async function loadCouncilView(runId, dir) {
|
|
203
219
|
const meta = await readJsonArtifact(dir, 'meta.json');
|
|
204
220
|
if (!meta)
|
|
205
221
|
return null;
|
|
206
|
-
const [judge, actionPlan, roles, intent] = await Promise.all([
|
|
222
|
+
const [judge, actionPlan, roles, intent, decisionReport] = await Promise.all([
|
|
207
223
|
readJsonArtifact(dir, '09-judge-report.json'),
|
|
208
224
|
readJsonArtifact(dir, '09b-action-plan.json'),
|
|
209
225
|
loadRoleOutputs(dir),
|
|
210
226
|
readJsonArtifact(dir, '01-intent-contract.json'),
|
|
227
|
+
readJsonArtifact(dir, '10-decision-report.json'),
|
|
211
228
|
]);
|
|
212
229
|
const columns = roles.map(({ provider, role }) => roleColumn(provider, role));
|
|
213
230
|
const verdict = judge?.verdict ?? (meta.exit_status === 'ok' ? 'Run completed without a judge verdict artifact.' : `Run ${meta.exit_status}.`);
|
|
@@ -226,18 +243,21 @@ export async function loadCouncilView(runId, dir) {
|
|
|
226
243
|
}
|
|
227
244
|
}
|
|
228
245
|
else {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
246
|
+
const storedGraph = await readJsonArtifact(dir, '07-decision-graph.json');
|
|
247
|
+
const legacyMap = storedGraph ? null : await readJsonArtifact(dir, '07-disagreement-map.json');
|
|
248
|
+
const graph = storedGraph ?? (legacyMap ? adaptLegacyDecisionGraph(legacyMap) : null);
|
|
249
|
+
if (graph) {
|
|
250
|
+
rows = graphRows(graph, judge);
|
|
232
251
|
stats = [
|
|
233
|
-
`${
|
|
234
|
-
`${
|
|
235
|
-
`${
|
|
236
|
-
`${
|
|
252
|
+
`${graph.claims.filter((claim) => claim.state === 'CONSENSUS').length} consensus`,
|
|
253
|
+
`${graph.claims.filter((claim) => claim.state === 'SHARED_CONCERN').length} shared concerns`,
|
|
254
|
+
`${graph.claims.filter((claim) => claim.state === 'DISAGREEMENT').length} disputes`,
|
|
255
|
+
`${graph.claims.filter((claim) => claim.state === 'UNIQUE').length} unique`,
|
|
256
|
+
`${graph.holes.coverage.length} coverage holes`,
|
|
237
257
|
];
|
|
238
|
-
narrative =
|
|
258
|
+
narrative = graphNarrative(graph, judge);
|
|
239
259
|
const reportV3 = Boolean(judge?.recommendation || actionPlan);
|
|
240
|
-
if (actionPlan)
|
|
260
|
+
if (actionPlan && !('kind' in actionPlan))
|
|
241
261
|
narrative.nextSteps = actionPlan.actions.map((a) => a.action);
|
|
242
262
|
if (reportV3) {
|
|
243
263
|
const ideaSeats = ideaSeatOutputs(roles);
|
|
@@ -245,9 +265,9 @@ export async function loadCouncilView(runId, dir) {
|
|
|
245
265
|
...narrative,
|
|
246
266
|
recommendation: judge?.recommendation,
|
|
247
267
|
conditions: judge?.conditions,
|
|
248
|
-
scorecard: deriveScorecard(IDEA_RUBRIC,
|
|
249
|
-
audit: judge ? deriveAudit(
|
|
250
|
-
debates:
|
|
268
|
+
scorecard: deriveScorecard(IDEA_RUBRIC, graph),
|
|
269
|
+
audit: judge ? deriveAudit(graph, judge) : [],
|
|
270
|
+
debates: graphDebates(graph, judge),
|
|
251
271
|
actionPlan: actionPlan ?? undefined,
|
|
252
272
|
openQuestions: mergeOpenQuestions(ideaSeats),
|
|
253
273
|
receipt: receiptLines(meta),
|
|
@@ -256,9 +276,17 @@ export async function loadCouncilView(runId, dir) {
|
|
|
256
276
|
}
|
|
257
277
|
}
|
|
258
278
|
const moderator = meta.roles?.judge ? providerName(meta.roles.judge) : undefined;
|
|
279
|
+
const hydratedDecisionReport = decisionReport?.dossier ? {
|
|
280
|
+
...decisionReport,
|
|
281
|
+
keyFindings: decisionReport.keyFindings?.length ? decisionReport.keyFindings : judge?.key_points ?? [decisionReport.verdict.primaryReason],
|
|
282
|
+
criticalUnknowns: decisionReport.criticalUnknowns?.length
|
|
283
|
+
? decisionReport.criticalUnknowns
|
|
284
|
+
: (narrative.openQuestions ?? []).slice(0, 3),
|
|
285
|
+
} : undefined;
|
|
259
286
|
return {
|
|
260
287
|
runId,
|
|
261
288
|
workflow: meta.workflow,
|
|
289
|
+
mode: meta.mode,
|
|
262
290
|
verdict,
|
|
263
291
|
keyPoints: judge?.key_points ?? [],
|
|
264
292
|
confidence: judge?.confidence_notes ?? '',
|
|
@@ -268,8 +296,9 @@ export async function loadCouncilView(runId, dir) {
|
|
|
268
296
|
stats,
|
|
269
297
|
calls: `${meta.call_count}/${meta.budget.limit} provider calls`,
|
|
270
298
|
flags: meta.flags ?? [],
|
|
271
|
-
topic: intent?.task,
|
|
299
|
+
topic: intent?.task ?? decisionReport?.task.original,
|
|
272
300
|
moderator,
|
|
301
|
+
...(hydratedDecisionReport ? { decisionReport: hydratedDecisionReport } : {}),
|
|
273
302
|
...narrative,
|
|
274
303
|
};
|
|
275
304
|
}
|
|
@@ -310,6 +339,155 @@ function section(index, title, inner, delay, note = '') {
|
|
|
310
339
|
${inner}
|
|
311
340
|
</section>`;
|
|
312
341
|
}
|
|
342
|
+
function dossierTable(headers, rows) {
|
|
343
|
+
if (!rows.length)
|
|
344
|
+
return '<p class="muted">None recorded.</p>';
|
|
345
|
+
return `<div class="table-wrap"><table class="data-table"><thead><tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join('')}</tr></thead><tbody>${rows
|
|
346
|
+
.map((row) => `<tr>${row.map((value) => `<td>${escapeHtml(value)}</td>`).join('')}</tr>`)
|
|
347
|
+
.join('')}</tbody></table></div>`;
|
|
348
|
+
}
|
|
349
|
+
function dossierRefs(ids) {
|
|
350
|
+
return ids.length ? ids.join(', ') : 'none recorded';
|
|
351
|
+
}
|
|
352
|
+
function dossierPct(value) {
|
|
353
|
+
return `${Math.round(value * 100)}%`;
|
|
354
|
+
}
|
|
355
|
+
function dossierCoverageLabel(value) {
|
|
356
|
+
return value >= 0.75 ? 'High' : value >= 0.5 ? 'Medium' : 'Low';
|
|
357
|
+
}
|
|
358
|
+
function dossierCouncilRead(report) {
|
|
359
|
+
if (report.mode === 'quick')
|
|
360
|
+
return 'One structured analyst produced this result; no council, consensus, or independent-verification claim is being made.';
|
|
361
|
+
const scouts = report.models.filter((model) => model.roles.includes('scout')).length;
|
|
362
|
+
if (report.disagreements.length === 0) {
|
|
363
|
+
return `${scouts || 'The'} independent scout ${scouts === 1 ? 'analysis produced' : 'analyses produced'} no genuine opposing claim. The chair had less contested material to resolve.`;
|
|
364
|
+
}
|
|
365
|
+
const resolved = report.disagreements.filter((item) => item.status === 'RESOLVED').length;
|
|
366
|
+
return `${report.disagreements.length} genuine disagreement${report.disagreements.length === 1 ? '' : 's'} reached the chair; ${resolved} ${resolved === 1 ? 'was' : 'were'} resolved.`;
|
|
367
|
+
}
|
|
368
|
+
function dossierWarning(report, flags) {
|
|
369
|
+
const active = flags.filter((flag) => report.flags.includes(flag));
|
|
370
|
+
return active.length ? `<div class="warns"><span class="warn">⚑ DEGRADED: ${escapeHtml(active.join(', '))}</span></div>` : '';
|
|
371
|
+
}
|
|
372
|
+
function renderDossierIdeaBody(report) {
|
|
373
|
+
const dossier = report.dossier;
|
|
374
|
+
const confidence = report.confidenceBreakdown;
|
|
375
|
+
const coverageLabel = dossierCoverageLabel(confidence.verificationCoverage);
|
|
376
|
+
const keyFindings = report.keyFindings?.length ? report.keyFindings : [dossier.recommendation.reason];
|
|
377
|
+
const criticalUnknowns = report.criticalUnknowns?.length ? report.criticalUnknowns : report.openQuestions.slice(0, 3);
|
|
378
|
+
const tone = dossier.recommendation.status === 'ACCEPTED' ? 'good'
|
|
379
|
+
: dossier.recommendation.status === 'REJECTED' ? 'risk' : 'caution';
|
|
380
|
+
const conditions = dossier.recommendation.conditions.length
|
|
381
|
+
? `<details class="decision-details"><summary>Conditions and decision state</summary><div><p><strong>Internal state:</strong> ${escapeHtml(dossier.recommendation.status)}</p><ul>${dossier.recommendation.conditions.map((condition) => `<li>${escapeHtml(condition.text)} <code>${escapeHtml(dossierRefs(condition.claimIds))}</code></li>`).join('')}</ul></div></details>`
|
|
382
|
+
: '';
|
|
383
|
+
const startHere = dossier.experiments.actions[0]?.action ?? 'No executable next step was produced.';
|
|
384
|
+
const factLabels = ['Decisive result', 'Consequence', 'Supporting signal'];
|
|
385
|
+
const facts = keyFindings.slice(0, 3).map((finding, index) => `<article class="decision-fact"><span>${factLabels[index]}</span><p>${escapeHtml(finding)}</p></article>`).join('');
|
|
386
|
+
const snapshot = report.decisionSnapshot;
|
|
387
|
+
const decisiveNumbers = snapshot ? `<div class="decision-numbers">
|
|
388
|
+
<span class="section-eyebrow">Decisive numbers</span>
|
|
389
|
+
<div class="table-wrap"><table class="snapshot-table"><thead><tr><th>Metric</th><th>Value</th><th>What it means</th></tr></thead><tbody>${snapshot.decisiveNumbers.map((item) => `<tr><td>${escapeHtml(item.label)}</td><td class="number-value">${escapeHtml(item.value)}</td><td>${escapeHtml(item.meaning)} <code>${escapeHtml(dossierRefs(item.claimIds))}</code></td></tr>`).join('')}</tbody></table></div>
|
|
390
|
+
${snapshot.payback ? `<div class="payback-result"><span>Payback · ${escapeHtml(snapshot.payback.status.replaceAll('_', ' '))}</span><strong>${escapeHtml(snapshot.payback.result)}</strong><p>${escapeHtml(snapshot.payback.basis)} <code>${escapeHtml(dossierRefs(snapshot.payback.claimIds))}</code></p></div>` : ''}
|
|
391
|
+
</div>` : '';
|
|
392
|
+
const optionComparison = snapshot ? `<div class="option-comparison">
|
|
393
|
+
<span class="section-eyebrow">Options at a glance</span>
|
|
394
|
+
<div class="table-wrap"><table class="snapshot-table"><thead><tr><th>Path</th><th>Commitment</th><th>Basis</th><th>Trade-off</th></tr></thead><tbody>${snapshot.options.map((option) => `<tr><td>${escapeHtml(option.label)}</td><td class="number-value">${escapeHtml(option.commitment)}</td><td><span class="basis-chip ${option.commitmentKind.toLowerCase()}">${escapeHtml(option.commitmentKind.replace('_', ' '))}</span></td><td>${escapeHtml(option.tradeoff)}${option.claimIds.length ? ` <code>${escapeHtml(dossierRefs(option.claimIds))}</code>` : ''}</td></tr>`).join('')}</tbody></table></div>
|
|
395
|
+
</div>` : '';
|
|
396
|
+
const tripwire = snapshot?.tripwire ? `<div class="tripwire"><span class="tag">Go/no-go tripwire</span><strong>${escapeHtml(snapshot.tripwire.metric)} · ${escapeHtml(snapshot.tripwire.threshold)}</strong><p>${escapeHtml(snapshot.tripwire.decisionRule)} <code>${escapeHtml(dossierRefs(snapshot.tripwire.claimIds))}</code></p></div>` : '';
|
|
397
|
+
const topCounterCase = dossier.counterCase.reasoning;
|
|
398
|
+
const unknowns = criticalUnknowns.length
|
|
399
|
+
? `<ul class="critical-unknowns">${criticalUnknowns.map((unknown) => `<li>${escapeHtml(unknown)}</li>`).join('')}</ul>`
|
|
400
|
+
: '<p class="muted">No verdict-flipping unknown was recorded.</p>';
|
|
401
|
+
const recommendation = `<div class="verdict tone-${tone}">
|
|
402
|
+
<span class="pill">Council recommendation</span>
|
|
403
|
+
${decisiveNumbers}
|
|
404
|
+
<p class="verdict-text">${escapeHtml(dossier.recommendation.summary)}</p>
|
|
405
|
+
<div class="evidence-coverage ${coverageLabel.toLowerCase()}">
|
|
406
|
+
<div><span class="fk">Evidence coverage</span><strong>${coverageLabel} · ${dossierPct(confidence.verificationCoverage)}</strong></div>
|
|
407
|
+
<div class="coverage-track"><span style="width:${Math.round(confidence.verificationCoverage * 100)}%"></span></div>
|
|
408
|
+
<p>${confidence.verificationCoverage < 0.5 ? 'Important inputs remain unchecked. Confirm them before committing.' : 'Most load-bearing claims received independent checking.'} This is not a probability that the recommendation is correct.</p>
|
|
409
|
+
</div>
|
|
410
|
+
${snapshot ? '' : `<div class="decision-facts">${facts}</div>`}
|
|
411
|
+
<div class="action-callout"><span>Do this first</span><p>${escapeHtml(startHere)}</p></div>
|
|
412
|
+
${optionComparison}
|
|
413
|
+
${tripwire}
|
|
414
|
+
<div class="decision-safety">
|
|
415
|
+
<article><span class="tag">What could overturn this</span><p>${escapeHtml(topCounterCase)}</p></article>
|
|
416
|
+
<article><span class="tag">Critical unknowns</span>${unknowns}</article>
|
|
417
|
+
</div>
|
|
418
|
+
${report.verdict.criticalWarning ? `<div class="critical-warning"><span class="tag">Critical warning</span><p>${escapeHtml(report.verdict.criticalWarning)}</p></div>` : ''}
|
|
419
|
+
<p class="council-read"><strong>Council read:</strong> ${escapeHtml(dossierCouncilRead(report))}</p>
|
|
420
|
+
${conditions}
|
|
421
|
+
<div class="evidence-anchors"><span>Evidence anchors</span><code>${escapeHtml(dossierRefs(dossier.recommendation.claimIds))}</code></div>
|
|
422
|
+
${dossierWarning(report, ['synthesis_suspect'])}
|
|
423
|
+
${dossier.recommendation.claimIds.length ? '' : '<div class="warns"><span class="warn">⚑ DEGRADED: recommendation has no stored graph anchor</span></div>'}
|
|
424
|
+
</div>`;
|
|
425
|
+
const claimChain = dossierTable(['Claim ID', 'Claim', 'Ruling', 'Evidence status', 'Depends on'], dossier.claimChain.map((claim) => [claim.claimId, claim.text, claim.ruling, claim.evidenceStatus, dossierRefs(claim.dependsOn)]));
|
|
426
|
+
const evidence = `${dossierWarning(report, ['verification_skipped', 'research_ungrounded'])}${dossierTable(['Evidence ID', 'Source', 'Date', 'Freshness', 'Verification', 'Linked claims'], dossier.evidence.map((item) => [item.id, `${item.source} (${item.sourceKind})`, item.date, item.freshness, item.verificationStatus, dossierRefs(item.claimIds)]))}`;
|
|
427
|
+
const disagreementCards = report.disagreements.length
|
|
428
|
+
? report.disagreements.map((item) => `<article class="card debate-card"><h3><code>${escapeHtml(item.id)}</code> ${escapeHtml(item.topic)}</h3>${item.sides.map((side) => `<div class="field"><span class="fk">${escapeHtml(side.stance)} · ${escapeHtml(side.providers.map(providerName).join(', '))}</span><p>${escapeHtml(side.reasoning.join(' '))}</p></div>`).join('')}<div class="field"><span class="fk">Ruling</span><p>${escapeHtml(item.status)} · ${escapeHtml(item.ruling)}</p></div>${item.reasoning ? `<div class="field"><span class="fk">Why</span><p>${escapeHtml(item.reasoning)}</p></div>` : ''}</article>`).join('')
|
|
429
|
+
: `<p class="muted">${report.mode === 'quick' ? 'No cross-model disagreement analysis runs in quick mode.' : 'No genuine disagreements were stored.'}</p>`;
|
|
430
|
+
const changes = dossier.positionChanges.length
|
|
431
|
+
? dossierTable(['Event', 'Claim', 'Responder', 'Change', 'Evidence', 'Detail'], dossier.positionChanges.map((event) => [event.eventId, event.claimId, providerName(event.responder), event.response, dossierRefs(event.evidenceIds), event.narrowedProposition ?? event.reasoning]))
|
|
432
|
+
: '<p class="muted">No CONCEDE, COUNTER, or NARROW event was recorded.</p>';
|
|
433
|
+
const minority = `<h3>Minority report</h3>${report.minority.dissent.length
|
|
434
|
+
? `<ul class="checks">${report.minority.dissent.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`
|
|
435
|
+
: '<p class="muted">No minority dissent was recorded.</p>'}
|
|
436
|
+
${report.minority.uniqueOppositions.length ? `<ul class="checks">${report.minority.uniqueOppositions.map((item) => `<li><strong>${escapeHtml(providerName(item.provider))}</strong> uniquely opposed: ${escapeHtml(item.proposition)}</li>`).join('')}</ul>` : ''}
|
|
437
|
+
<p class="lede">Decision-blocking status: <strong>${escapeHtml(report.minority.blocksDecision)}</strong></p>`;
|
|
438
|
+
const disagreement = `${dossierWarning(report, ['single_model', 'low_diversity'])}${disagreementCards}<h3>Position changes</h3>${changes}${minority}`;
|
|
439
|
+
const shared = dossier.sharedConcerns.length
|
|
440
|
+
? `<h3>Shared concerns</h3><ul class="agree">${dossier.sharedConcerns.map((item) => `<li><p><code>${escapeHtml(item.claimId)}</code> ${escapeHtml(item.text)}</p><span class="who-names">${escapeHtml(item.providerIds.map(providerName).join(', '))} · ${escapeHtml(item.evidenceStatus)}</span></li>`).join('')}</ul>`
|
|
441
|
+
: '<h3>Shared concerns</h3><p class="muted">None recorded.</p>';
|
|
442
|
+
const unique = dossier.uniqueSupportedInsights.length
|
|
443
|
+
? `<h3>Unique supported insights</h3><ul class="agree">${dossier.uniqueSupportedInsights.map((item) => `<li><p><code>${escapeHtml(item.claimId)}</code> ${escapeHtml(item.text)}</p><span class="who-names">${escapeHtml(providerName(item.providerId))} · ${escapeHtml(item.verificationStatus)}</span></li>`).join('')}</ul>`
|
|
444
|
+
: '<h3>Unique supported insights</h3><p class="muted">None recorded.</p>';
|
|
445
|
+
const coverage = dossierTable(['Dimension', 'Status', 'Claim IDs'], dossier.coverage.map((item) => [item.label, item.status, dossierRefs(item.claimIds)]));
|
|
446
|
+
const sensitivity = dossierTable(['Claim ID', 'Fact', 'Sensitivity', 'If false', 'What would change it', 'Linked claims'], dossier.sensitivity.map((item) => [item.claimId, item.fact, item.sensitivity, item.impactIfFalse, item.whatWouldChangeIt, dossierRefs(item.linkedClaimIds)]));
|
|
447
|
+
const experiments = `${dossierWarning(report, ['plan_fallback', 'plan_skipped'])}${dossier.experiments.status === 'DEGRADED' ? `<div class="warns"><span class="warn">⚑ DEGRADED: ${escapeHtml(dossier.experiments.note)}</span></div>` : ''}${dossierTable(['#', 'Experiment', 'Why', 'Validates', 'Effort', 'Kill signal'], dossier.experiments.actions.map((action) => [String(action.order), action.action, action.why, action.validates, action.effort, action.killSignal]))}${dossier.experiments.actions.length ? `<p class="lede">${escapeHtml(dossier.experiments.note)}</p>` : ''}`;
|
|
448
|
+
const counterCase = dossier.counterCase.available
|
|
449
|
+
? `<article class="card risk-card"><p>${escapeHtml(dossier.counterCase.reasoning)}</p><div class="field"><span class="fk">Evidence anchors</span><p><code>${escapeHtml(dossierRefs(dossier.counterCase.claimIds))}</code></p></div></article>`
|
|
450
|
+
: `<div class="warns"><span class="warn">⚑ DEGRADED: ${escapeHtml(dossier.counterCase.reasoning)}</span></div>`;
|
|
451
|
+
const contributions = `${dossierWarning(report, ['verification_skipped', 'single_model', 'low_diversity'])}<p class="lede">Only unique claims that survived independent verification receive credit.</p>${dossierTable(['Provider', 'Verified unique claim IDs', 'Count'], dossier.contributions.map((item) => [item.name, dossierRefs(item.verifiedUniqueClaimIds), String(item.verifiedUniqueClaimIds.length)]))}`;
|
|
452
|
+
const receipt = `<div class="receipt">
|
|
453
|
+
<span>mode ${escapeHtml(report.mode)}</span><span>${report.receipt.calls}/${report.receipt.budget} provider calls</span>
|
|
454
|
+
<span>discovery ${report.receipt.categories.discovery}</span><span>verification ${report.receipt.categories.verification}</span>
|
|
455
|
+
<span>repair ${report.receipt.categories.repair}</span><span>planning ${report.receipt.categories.planning}</span>
|
|
456
|
+
<span>by provider ${escapeHtml(Object.entries(report.receipt.byProvider).map(([provider, count]) => `${providerName(provider)} ${count}`).join(', ') || 'none')}</span>
|
|
457
|
+
<span>model time ${(report.receipt.modelTimeMs / 1000).toFixed(1)}s</span>
|
|
458
|
+
</div>${report.flags.length ? `<div class="warns">${report.flags.map((flag) => `<span class="warn">⚑ ${escapeHtml(flag)}</span>`).join('')}</div>` : '<p class="muted">No degradation flags.</p>'}`;
|
|
459
|
+
const risks = dossierTable(['Risk', 'Severity'], report.risks.map((item) => [item.risk, item.severity]));
|
|
460
|
+
const questions = report.openQuestions.length
|
|
461
|
+
? `<ul class="checks">${report.openQuestions.map((question) => `<li>${escapeHtml(question)}</li>`).join('')}</ul>`
|
|
462
|
+
: '<p class="muted">No verdict-flipping open question was recorded.</p>';
|
|
463
|
+
const runDetails = `<article class="card">
|
|
464
|
+
<div class="field"><span class="fk">Report</span><p><code>${escapeHtml(report.reportId)}</code> · ${escapeHtml(report.generatedAt)}</p></div>
|
|
465
|
+
<div class="field"><span class="fk">Original task</span><p>${escapeHtml(report.task.original)}</p></div>
|
|
466
|
+
<div class="field"><span class="fk">Normalized question</span><p>${escapeHtml(report.task.normalized)}</p></div>
|
|
467
|
+
<div class="field"><span class="fk">Constraints</span><p>${escapeHtml(report.task.constraints.join('; ') || 'none recorded')}</p></div>
|
|
468
|
+
<div class="field"><span class="fk">Success criteria</span><p>${escapeHtml(report.task.successCriteria.join('; ') || 'none recorded')}</p></div>
|
|
469
|
+
<div class="field"><span class="fk">Models and roles</span><p>${escapeHtml(report.models.map((model) => `${model.name} (${model.roles.join(', ')})`).join(' · ') || 'none recorded')}</p></div>
|
|
470
|
+
<div class="field"><span class="fk">Structural score</span><p>${confidence.score}/100 (${escapeHtml(confidence.label)}) · heuristic, not benchmark-calibrated</p></div>
|
|
471
|
+
</article>${receipt}`;
|
|
472
|
+
const technical = `<details class="fold"><summary>Original submissions and graph events</summary><div class="fold-body">
|
|
473
|
+
<h4 class="fold-h">Original submissions</h4><ul>${dossier.technical.submissions.map((item) => `<li><strong>${escapeHtml(item.name)}:</strong> ${escapeHtml(item.strongestVersion)} <code>${escapeHtml(dossierRefs(item.positionIds))}</code></li>`).join('') || '<li>none</li>'}</ul>
|
|
474
|
+
<h4 class="fold-h">Original positions</h4><ul>${dossier.technical.positions.map((position) => `<li><code>${escapeHtml(position.id)}</code> [${escapeHtml(providerName(position.provider))} ${escapeHtml(position.stance)}] ${escapeHtml(position.proposition)} · evidence <code>${escapeHtml(dossierRefs(position.evidenceIds))}</code></li>`).join('') || '<li>none</li>'}</ul>
|
|
475
|
+
<h4 class="fold-h">Graph edges</h4><ul>${dossier.technical.edges.map((edge) => `<li><code>${escapeHtml(edge.from)} —${escapeHtml(edge.type)}→ ${escapeHtml(edge.to)}</code></li>`).join('') || '<li>none</li>'}</ul>
|
|
476
|
+
<h4 class="fold-h">Position-change events</h4><ul>${dossier.technical.events.map((event) => `<li><code>${escapeHtml(event.eventId)}</code>: ${escapeHtml(providerName(event.responder))} ${escapeHtml(event.response)} <code>${escapeHtml(event.claimId)}</code> — ${escapeHtml(event.reasoning)}</li>`).join('') || '<li>none</li>'}</ul>
|
|
477
|
+
</div></details>`;
|
|
478
|
+
return [
|
|
479
|
+
section('01', 'Decision', recommendation, 60),
|
|
480
|
+
section('02', 'Action plan', experiments, 100, 'The smallest useful test, its effort, and the signal that should stop the idea.'),
|
|
481
|
+
section('03', 'Why this decision', claimChain, 140, 'The load-bearing claim chain behind the recommendation.'),
|
|
482
|
+
section('04', 'What could change the decision', `<h3>Decision-sensitive facts</h3>${sensitivity}<h3>Strongest counter-case</h3>${counterCase}`, 180),
|
|
483
|
+
section('05', 'Evidence and verification', evidence, 220),
|
|
484
|
+
section('06', 'Risks, gaps, and open questions', `<h3>Risks</h3>${risks}<h3>Coverage</h3>${coverage}<h3>Open questions</h3>${questions}`, 260),
|
|
485
|
+
section('07', 'Disagreement and dissent', disagreement, 300),
|
|
486
|
+
section('08', 'What the council added', `${shared}${unique}<h3>Verified unique contributions</h3>${contributions}`, 340),
|
|
487
|
+
section('09', 'Run details', runDetails, 380),
|
|
488
|
+
section('10', 'Technical audit', technical, 420),
|
|
489
|
+
].join('');
|
|
490
|
+
}
|
|
313
491
|
function renderLegacyIdeaBody(view) {
|
|
314
492
|
const risks = view.risks ?? [];
|
|
315
493
|
const agreements = view.agreements ?? [];
|
|
@@ -412,9 +590,10 @@ function renderIdeaBody(view) {
|
|
|
412
590
|
${conditions}
|
|
413
591
|
${glance}
|
|
414
592
|
</section>`;
|
|
593
|
+
const synthesisWarning = view.flags.includes('synthesis_suspect') ? '<div class="warns"><span class="warn">⚑ synthesis suspect</span></div>' : '';
|
|
415
594
|
const chairman = view.keyPoints?.length
|
|
416
|
-
? section('01', "Chairman's reasoning",
|
|
417
|
-
: '';
|
|
595
|
+
? section('01', "Chairman's reasoning", `${synthesisWarning}<ul class="reasons">${view.keyPoints.map((p) => `<li>${escapeHtml(p)}</li>`).join('')}</ul>`, 100)
|
|
596
|
+
: synthesisWarning ? section('01', "Chairman's reasoning", `${synthesisWarning}<p class="muted">No reliable chairman reasoning was produced.</p>`, 100) : '';
|
|
418
597
|
const scorecard = view.scorecard?.length
|
|
419
598
|
? `<div class="score-grid">${view.scorecard.map((s) => `<div class="score ${s.status}"><span>${escapeHtml(s.label)}</span><strong>${escapeHtml(s.status)}</strong></div>`).join('')}</div>`
|
|
420
599
|
: '';
|
|
@@ -440,11 +619,13 @@ function renderIdeaBody(view) {
|
|
|
440
619
|
<div class="field"><span class="fk">Chair</span><p>${escapeHtml(d.chair)}${d.reasoning ? ` — ${escapeHtml(d.reasoning)}` : ''}</p></div>
|
|
441
620
|
</article>`).join('')
|
|
442
621
|
: '<p class="muted">No contradictions reached the chair.</p>';
|
|
443
|
-
const plan = view.actionPlan
|
|
444
|
-
? `<div class="
|
|
622
|
+
const plan = view.actionPlan && 'kind' in view.actionPlan
|
|
623
|
+
? `<div class="card"><div class="warns"><span class="warn">⚑ ${view.flags.filter((f) => f === 'plan_fallback' || f === 'plan_skipped').map((f) => escapeHtml(f.replaceAll('_', ' '))).join(' · ')}</span></div><p><strong>Planner unavailable: ${escapeHtml(view.actionPlan.reason)}</strong></p><ul class="checks">${view.actionPlan.unresolved_questions.map((q) => `<li>${escapeHtml(q)}</li>`).join('')}</ul></div>`
|
|
624
|
+
: view.actionPlan
|
|
625
|
+
? `<div class="table-wrap"><table class="data-table plan-table"><thead><tr><th>#</th><th>Action</th><th>Why</th><th>Validates</th><th>Effort</th><th>Kill signal</th></tr></thead><tbody>
|
|
445
626
|
${view.actionPlan.actions.map((a) => `<tr><td>${a.order}</td><td>${escapeHtml(a.action)}</td><td>${escapeHtml(a.why)}</td><td><code>${escapeHtml(a.validates)}</code></td><td>${escapeHtml(a.effort)}</td><td>${escapeHtml(a.kill_signal)}</td></tr>`).join('')}
|
|
446
627
|
</tbody></table><p class="lede">${escapeHtml(view.actionPlan.sequencing_note)}</p></div>`
|
|
447
|
-
|
|
628
|
+
: '<p class="muted">No validation plan artifact recorded.</p>';
|
|
448
629
|
const questions = view.openQuestions?.length
|
|
449
630
|
? `<ul class="checks">${view.openQuestions.map((q) => `<li>${escapeHtml(q)}</li>`).join('')}</ul>`
|
|
450
631
|
: '<p class="muted">No verdict-flipping open questions recorded.</p>';
|
|
@@ -478,15 +659,43 @@ function renderIdeaBody(view) {
|
|
|
478
659
|
${renderTechnical(view)}
|
|
479
660
|
`;
|
|
480
661
|
}
|
|
662
|
+
function renderQuickIdeaBody(view) {
|
|
663
|
+
const tone = recommendationTone(view.recommendation, view.signal?.tone ?? 'caution');
|
|
664
|
+
const label = recommendationLabel(view.recommendation, view.signal?.label ?? 'Quick analysis');
|
|
665
|
+
const conditions = view.conditions?.length
|
|
666
|
+
? `<div class="conditions"><span class="fk">Conditions</span><ul>${view.conditions.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul></div>`
|
|
667
|
+
: '';
|
|
668
|
+
const hero = `<section class="verdict tone-${tone} reveal" style="animation-delay:60ms"><span class="pill">${escapeHtml(label)}</span><p class="verdict-text">${escapeHtml(view.verdict)}</p>${conditions}</section>`;
|
|
669
|
+
const reasoning = view.keyPoints.length
|
|
670
|
+
? `<ul class="reasons">${view.keyPoints.map((point) => `<li>${escapeHtml(point)}</li>`).join('')}</ul>`
|
|
671
|
+
: '<p class="muted">No structured reasoning recorded.</p>';
|
|
672
|
+
const risks = view.risks?.length
|
|
673
|
+
? `<ul class="checks">${view.risks.map((risk) => `<li>${escapeHtml(risk.assumption)} — ${escapeHtml(risk.challenge)}</li>`).join('')}</ul>`
|
|
674
|
+
: '<p class="muted">No load-bearing risk was supported strongly enough to list.</p>';
|
|
675
|
+
const plan = view.actionPlan && !('kind' in view.actionPlan)
|
|
676
|
+
? `<ul class="checks">${view.actionPlan.actions.map((action) => `<li><strong>${escapeHtml(action.action)}</strong> — ${escapeHtml(action.kill_signal)}</li>`).join('')}</ul>`
|
|
677
|
+
: '<p class="muted">No executable plan was produced.</p>';
|
|
678
|
+
const analyst = view.columns.map((column) => `<article class="card model-card"><h3>${escapeHtml(column.title)}</h3><ul class="model-lines">${column.lines.map((line) => `<li>${escapeHtml(line)}</li>`).join('')}</ul></article>`).join('');
|
|
679
|
+
const receipt = view.receipt?.length ? `<div class="receipt">${view.receipt.map((line) => `<span>${escapeHtml(line)}</span>`).join('')}</div>` : '';
|
|
680
|
+
return `${hero}
|
|
681
|
+
${section('01', 'Analyst reasoning', reasoning, 100, 'One structured analyst; no council or independent-consensus claim.')}
|
|
682
|
+
${section('02', 'Load-bearing risks', risks, 180)}
|
|
683
|
+
${section('03', 'Validation plan', plan, 240)}
|
|
684
|
+
${receipt ? section('04', 'Receipt', receipt, 300) : ''}
|
|
685
|
+
${section('05', 'Analyst output', `<div class="model-grid">${analyst}</div>`, 360)}`;
|
|
686
|
+
}
|
|
481
687
|
/** Serialize a council view to clean Markdown — for the HTML "Copy" button (paste into a coding assistant). */
|
|
482
688
|
function councilMarkdown(view) {
|
|
689
|
+
if (view.decisionReport?.dossier)
|
|
690
|
+
return renderDecisionDossierMarkdown(view.decisionReport);
|
|
483
691
|
const isIdea = view.workflow !== 'code-review';
|
|
692
|
+
const quick = isIdea && view.mode === 'quick';
|
|
484
693
|
const reportV3 = isIdea && Boolean(view.recommendation || view.actionPlan);
|
|
485
694
|
const L = [];
|
|
486
695
|
L.push(`# ${isIdea && view.topic ? cleanTopic(view.topic) : isIdea ? 'Idea refinement' : 'Code review'}`, '');
|
|
487
696
|
const panel = view.columns.map((c) => c.title).join(', ');
|
|
488
697
|
if (panel)
|
|
489
|
-
L.push(`Panel: ${panel}${view.moderator ? ` · Chair: ${view.moderator}` : ''}`, '');
|
|
698
|
+
L.push(quick ? `Analyst: ${panel}` : `Panel: ${panel}${view.moderator ? ` · Chair: ${view.moderator}` : ''}`, '');
|
|
490
699
|
if (view.recommendation) {
|
|
491
700
|
L.push('## Bottom line', '', `**${recommendationLabel(view.recommendation, '')}** — ${view.verdict}`, '');
|
|
492
701
|
if (view.conditions?.length) {
|
|
@@ -500,11 +709,16 @@ function councilMarkdown(view) {
|
|
|
500
709
|
L.push('## Verdict', '', view.verdict, '');
|
|
501
710
|
}
|
|
502
711
|
if (view.keyPoints?.length) {
|
|
503
|
-
L.push("## Chairman's reasoning", '');
|
|
712
|
+
L.push(quick ? '## Analyst reasoning' : "## Chairman's reasoning", '');
|
|
713
|
+
if (view.flags.includes('synthesis_suspect'))
|
|
714
|
+
L.push('⚠ synthesis_suspect — the chair output required deterministic repair or degradation handling.', '');
|
|
504
715
|
for (const p of view.keyPoints)
|
|
505
716
|
L.push(`- ${p}`);
|
|
506
717
|
L.push('');
|
|
507
718
|
}
|
|
719
|
+
else if (view.flags.includes('synthesis_suspect')) {
|
|
720
|
+
L.push("## Chairman's reasoning", '', '⚠ synthesis_suspect — no reliable chairman reasoning was produced.', '');
|
|
721
|
+
}
|
|
508
722
|
if (isIdea) {
|
|
509
723
|
if (reportV3 && view.scorecard?.length) {
|
|
510
724
|
L.push('## Dimension scorecard (best-effort)', '');
|
|
@@ -537,14 +751,23 @@ function councilMarkdown(view) {
|
|
|
537
751
|
}
|
|
538
752
|
if (reportV3 && view.actionPlan) {
|
|
539
753
|
L.push('## Validation plan', '');
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
L.push(`
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
754
|
+
if ('kind' in view.actionPlan) {
|
|
755
|
+
const planFlags = view.flags.filter((flag) => flag === 'plan_fallback' || flag === 'plan_skipped');
|
|
756
|
+
L.push(`Planner unavailable: ${view.actionPlan.reason}${planFlags.length ? ` (${planFlags.join(', ')})` : ''}.`, '', 'Unresolved questions:');
|
|
757
|
+
for (const question of view.actionPlan.unresolved_questions)
|
|
758
|
+
L.push(`- ${question}`);
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
for (const a of view.actionPlan.actions) {
|
|
762
|
+
L.push(`${a.order}. ${a.action}`);
|
|
763
|
+
L.push(` - Why: ${a.why}`);
|
|
764
|
+
L.push(` - Validates: ${a.validates}`);
|
|
765
|
+
L.push(` - Effort: ${a.effort}`);
|
|
766
|
+
L.push(` - Kill signal: ${a.kill_signal}`);
|
|
767
|
+
}
|
|
768
|
+
L.push('', view.actionPlan.sequencing_note);
|
|
546
769
|
}
|
|
547
|
-
L.push(''
|
|
770
|
+
L.push('');
|
|
548
771
|
}
|
|
549
772
|
if (reportV3 && view.openQuestions?.length) {
|
|
550
773
|
L.push('## Open questions that flip the verdict', '');
|
|
@@ -553,7 +776,7 @@ function councilMarkdown(view) {
|
|
|
553
776
|
L.push('');
|
|
554
777
|
}
|
|
555
778
|
if (view.columns.length) {
|
|
556
|
-
L.push('## How each model saw it', '');
|
|
779
|
+
L.push(quick ? '## Analyst output' : '## How each model saw it', '');
|
|
557
780
|
for (const c of view.columns) {
|
|
558
781
|
L.push(`### ${c.title}`);
|
|
559
782
|
for (const l of c.lines.slice(0, 10))
|
|
@@ -640,7 +863,7 @@ function renderTechnical(view) {
|
|
|
640
863
|
const dissent = view.dissent.length ? `<ul>${view.dissent.map((d) => `<li>${escapeHtml(d)}</li>`).join('')}</ul>` : '<p class="muted">None recorded.</p>';
|
|
641
864
|
return `
|
|
642
865
|
<details class="fold reveal" style="animation-delay:0ms">
|
|
643
|
-
<summary
|
|
866
|
+
<summary>${view.mode === 'quick' ? 'Full single-analyst output (technical)' : 'Full council analysis (technical)'}</summary>
|
|
644
867
|
<div class="fold-body">
|
|
645
868
|
<h4 class="fold-h">Each model, in its own words</h4>
|
|
646
869
|
<div class="cols">${columns || '<p class="muted">No model output recorded.</p>'}</div>
|
|
@@ -653,18 +876,21 @@ function renderTechnical(view) {
|
|
|
653
876
|
}
|
|
654
877
|
export function renderCouncilHtml(view) {
|
|
655
878
|
const isIdea = view.workflow !== 'code-review';
|
|
656
|
-
const
|
|
879
|
+
const quick = isIdea && view.mode === 'quick';
|
|
880
|
+
const kicker = quick ? 'aiki · quick analysis' : isIdea ? 'aiki · idea refinement' : 'aiki · code review';
|
|
657
881
|
const title = isIdea && view.topic ? cleanTopic(view.topic) : (isIdea ? 'Idea refinement' : 'Code review');
|
|
658
882
|
const panel = view.columns.map((c) => c.title);
|
|
659
883
|
const metaBits = [
|
|
660
|
-
panel.length ?
|
|
661
|
-
view.moderator ? `Chair: ${view.moderator}` : '',
|
|
884
|
+
panel.length ? `${quick ? 'Analyst' : 'Panel'}: ${panel.join(' · ')}` : '',
|
|
885
|
+
!quick && view.moderator ? `Chair: ${view.moderator}` : '',
|
|
662
886
|
view.calls,
|
|
663
887
|
].filter(Boolean);
|
|
664
888
|
const flags = view.flags.length
|
|
665
889
|
? `<div class="warns">${view.flags.map((f) => `<span class="warn">⚑ ${escapeHtml(f.replaceAll('_', ' '))}</span>`).join('')}</div>`
|
|
666
890
|
: '';
|
|
667
|
-
const body = isIdea
|
|
891
|
+
const body = isIdea && view.decisionReport?.dossier
|
|
892
|
+
? renderDossierIdeaBody(view.decisionReport)
|
|
893
|
+
: quick ? renderQuickIdeaBody(view) : isIdea ? renderIdeaBody(view) : renderReviewBody(view);
|
|
668
894
|
// Embed the report as Markdown for the Copy button. Escape "<" so a "</script>" in content can't break out.
|
|
669
895
|
const md = JSON.stringify(councilMarkdown(view)).replace(/</g, '\\u003c');
|
|
670
896
|
return `<!doctype html>
|
|
@@ -672,44 +898,84 @@ export function renderCouncilHtml(view) {
|
|
|
672
898
|
<head>
|
|
673
899
|
<meta charset="utf-8">
|
|
674
900
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
675
|
-
<title>${escapeHtml(isIdea ? 'Idea refinement' : 'Code review')} — aiki
|
|
901
|
+
<title>${escapeHtml(quick ? 'Quick analysis' : isIdea ? 'Idea refinement' : 'Code review')} — aiki</title>
|
|
676
902
|
<style>
|
|
677
903
|
:root{
|
|
678
904
|
color-scheme: light;
|
|
679
905
|
--serif:"Iowan Old Style","Palatino Linotype",Palatino,Charter,Georgia,"Times New Roman",serif;
|
|
680
906
|
--sans:-apple-system,BlinkMacSystemFont,"Avenir Next","Segoe UI",system-ui,sans-serif;
|
|
681
907
|
--mono:"SF Mono","JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;
|
|
682
|
-
--paper:#
|
|
683
|
-
--good:#
|
|
684
|
-
--caution:#
|
|
908
|
+
--paper:#fbfaf7; --panel:#f1efe9; --ink:#171a18; --soft:#59625e; --faint:#8b928e; --line:#dcded8;
|
|
909
|
+
--good:#176b52; --good-bg:#e5f3ed; --risk:#a63a30; --risk-bg:#f8e8e4;
|
|
910
|
+
--caution:#a96813; --caution-bg:#f8eedc; --slate:#4f5e59; --accent:#155f55;
|
|
685
911
|
}
|
|
686
912
|
*{box-sizing:border-box;}
|
|
687
913
|
html{-webkit-text-size-adjust:100%;}
|
|
688
914
|
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.6;}
|
|
689
|
-
main{max-width:
|
|
915
|
+
main{max-width:880px;margin:0 auto;padding:34px 24px 90px;}
|
|
690
916
|
a{color:var(--accent);}
|
|
691
|
-
h1
|
|
917
|
+
h1{font-family:var(--serif);font-weight:600;letter-spacing:-.025em;}
|
|
918
|
+
h2,h3,h4{font-family:var(--sans);font-weight:650;letter-spacing:-.01em;}
|
|
692
919
|
p{margin:0 0 .6em;}
|
|
693
920
|
|
|
694
921
|
/* masthead */
|
|
695
|
-
.mast{border-bottom:
|
|
922
|
+
.mast{border-bottom:1px solid var(--ink);padding-bottom:22px;margin-bottom:30px;}
|
|
696
923
|
.kicker{font-family:var(--mono);font-size:11.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--accent);}
|
|
697
|
-
.mast h1{font-size:clamp(
|
|
924
|
+
.mast h1{font-size:clamp(32px,5.4vw,50px);line-height:1.05;margin:12px 0 18px;max-width:18ch;}
|
|
698
925
|
.mmeta{display:flex;flex-wrap:wrap;gap:7px;}
|
|
699
926
|
.mmeta span{font-family:var(--mono);font-size:11.5px;color:var(--soft);border:1px solid var(--line);background:var(--panel);border-radius:100px;padding:3px 10px;}
|
|
700
927
|
.warns{margin-top:12px;display:flex;flex-wrap:wrap;gap:8px;}
|
|
701
928
|
.warn{font-family:var(--mono);font-size:11.5px;color:var(--caution);background:var(--caution-bg);border:1px solid #e6d09b;border-radius:6px;padding:3px 9px;}
|
|
702
929
|
|
|
703
930
|
/* verdict hero */
|
|
704
|
-
.verdict{position:relative;background
|
|
705
|
-
padding:
|
|
931
|
+
.verdict{position:relative;background:#fff;border:1px solid var(--line);border-radius:18px;
|
|
932
|
+
padding:30px 32px 26px;margin-bottom:18px;overflow:hidden;box-shadow:0 18px 50px rgba(32,45,40,.07);}
|
|
706
933
|
.verdict::before{content:"";position:absolute;left:0;top:0;bottom:0;width:6px;}
|
|
707
934
|
.tone-good::before{background:var(--good);} .tone-caution::before{background:var(--caution);} .tone-risk::before{background:var(--risk);}
|
|
708
935
|
.pill{display:inline-block;font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.02em;
|
|
709
936
|
padding:5px 13px;border-radius:100px;margin-bottom:14px;}
|
|
710
937
|
.tone-good .pill{background:var(--good-bg);color:var(--good);} .tone-caution .pill{background:var(--caution-bg);color:var(--caution);}
|
|
711
938
|
.tone-risk .pill{background:var(--risk-bg);color:var(--risk);}
|
|
712
|
-
.verdict-text{font-family:var(--
|
|
939
|
+
.verdict-text{font-family:var(--serif);font-weight:600;font-size:clamp(22px,3vw,29px);line-height:1.32;color:var(--ink);margin:0;max-width:28ch;}
|
|
940
|
+
.section-eyebrow{display:block;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--accent);margin-bottom:9px;}
|
|
941
|
+
.decision-numbers{margin:4px 0 22px;}.snapshot-table{width:100%;border-collapse:collapse;font-size:13.5px;line-height:1.4;}
|
|
942
|
+
.snapshot-table th{padding:8px 10px;text-align:left;font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);border-bottom:1px solid var(--line);}
|
|
943
|
+
.snapshot-table td{padding:11px 10px;vertical-align:top;border-bottom:1px solid var(--line);}.snapshot-table tbody tr:last-child td{border-bottom:0;}
|
|
944
|
+
.snapshot-table .number-value{font-family:var(--serif);font-size:17px;font-weight:700;color:var(--ink);white-space:nowrap;}
|
|
945
|
+
.snapshot-table code,.payback-result code,.tripwire code{font-size:9.5px;color:var(--faint);white-space:nowrap;}
|
|
946
|
+
.payback-result{display:grid;grid-template-columns:auto 1fr;gap:3px 16px;align-items:baseline;margin-top:10px;padding:14px 16px;background:var(--paper);border:1px solid var(--line);border-radius:10px;}
|
|
947
|
+
.payback-result span{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);}.payback-result strong{font-family:var(--serif);font-size:18px;}.payback-result p{grid-column:1/-1;margin:2px 0 0;font-size:12.5px;color:var(--soft);}
|
|
948
|
+
.evidence-coverage{margin:24px 0 18px;padding:14px 16px;background:var(--paper);border:1px solid var(--line);border-radius:12px;}
|
|
949
|
+
.evidence-coverage>div:first-child{display:flex;align-items:end;justify-content:space-between;gap:12px;}
|
|
950
|
+
.evidence-coverage strong{font-family:var(--mono);font-size:13px;color:var(--ink);}
|
|
951
|
+
.coverage-track{height:5px;margin:10px 0;border-radius:99px;background:var(--line);overflow:hidden;}
|
|
952
|
+
.coverage-track span{display:block;height:100%;border-radius:inherit;background:var(--accent);}
|
|
953
|
+
.evidence-coverage.low .coverage-track span{background:var(--risk);}.evidence-coverage.medium .coverage-track span{background:var(--caution);}
|
|
954
|
+
.evidence-coverage p{font-size:13.5px;color:var(--soft);margin:0;}
|
|
955
|
+
.decision-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;margin:18px 0;}
|
|
956
|
+
.decision-fact{padding:16px;background:var(--paper);border:1px solid var(--line);border-radius:12px;}
|
|
957
|
+
.decision-fact span,.action-callout>span{display:block;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--accent);margin-bottom:7px;}
|
|
958
|
+
.decision-fact p{margin:0;font-size:14.5px;line-height:1.5;}
|
|
959
|
+
.action-callout{position:relative;margin:20px 0;padding:20px 22px 20px 26px;background:var(--ink);color:var(--paper);border-radius:12px;}
|
|
960
|
+
.action-callout::before{content:"→";position:absolute;right:20px;top:12px;font-family:var(--serif);font-size:34px;color:#8fc7b9;}
|
|
961
|
+
.action-callout>span{color:#8fc7b9;}.action-callout p{font-size:17px;line-height:1.5;margin:0;padding-right:34px;}
|
|
962
|
+
.option-comparison{margin:22px 0;}.basis-chip{display:inline-block;padding:3px 7px;border-radius:99px;background:var(--good-bg);color:var(--good);font-family:var(--mono);font-size:9px;letter-spacing:.04em;white-space:nowrap;}
|
|
963
|
+
.basis-chip.target_cap{background:var(--caution-bg);color:var(--caution);}.basis-chip.unknown{background:var(--risk-bg);color:var(--risk);}
|
|
964
|
+
.tripwire{margin:14px 0;padding:16px 18px;border:1px solid #b9d5cd;border-left:4px solid var(--accent);border-radius:10px;background:#f2f8f6;}
|
|
965
|
+
.tripwire .tag{display:block;color:var(--accent);margin-bottom:6px;}.tripwire strong{display:block;font-family:var(--serif);font-size:19px;}.tripwire p{margin:5px 0 0;font-size:13.5px;color:var(--soft);}
|
|
966
|
+
.decision-safety{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:18px;}
|
|
967
|
+
.decision-safety article{padding:17px 18px;background:var(--paper);border:1px solid var(--line);border-radius:12px;}
|
|
968
|
+
.decision-safety article:first-child{border-left:4px solid var(--risk);}
|
|
969
|
+
.decision-safety p{font-size:14px;margin:0;}
|
|
970
|
+
.critical-unknowns{margin:0;padding-left:18px;}.critical-unknowns li{font-size:13.5px;margin:0 0 6px;}
|
|
971
|
+
.critical-warning{margin-top:12px;padding:14px 16px;background:var(--risk-bg);border:1px solid #e1b8b1;border-radius:10px;}
|
|
972
|
+
.critical-warning .tag{color:var(--risk);}.critical-warning p{font-size:13.5px;margin:0;}
|
|
973
|
+
.council-read{margin:15px 0 0;font-size:13.5px;color:var(--soft);}
|
|
974
|
+
.decision-details{margin-top:14px;border-top:1px solid var(--line);padding-top:12px;}
|
|
975
|
+
.decision-details summary{cursor:pointer;font-family:var(--mono);font-size:11px;color:var(--soft);text-transform:uppercase;letter-spacing:.08em;}
|
|
976
|
+
.decision-details>div{padding-top:10px;font-size:13.5px;}.decision-details ul{margin:8px 0;padding-left:18px;}
|
|
977
|
+
.evidence-anchors{display:flex;gap:10px;align-items:center;margin-top:12px;font-family:var(--mono);font-size:10.5px;color:var(--faint);}
|
|
978
|
+
.evidence-anchors span{text-transform:uppercase;letter-spacing:.1em;}
|
|
713
979
|
|
|
714
980
|
/* top action bar + copy */
|
|
715
981
|
.bar{position:sticky;top:0;z-index:5;display:flex;align-items:center;justify-content:space-between;gap:12px;
|
|
@@ -738,6 +1004,7 @@ p{margin:0 0 .6em;}
|
|
|
738
1004
|
|
|
739
1005
|
/* bottom line */
|
|
740
1006
|
.bottomline{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:34px;}
|
|
1007
|
+
.verdict .bottomline{margin:18px 0 0;}
|
|
741
1008
|
.bottomline > div{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;}
|
|
742
1009
|
.tag{display:inline-block;font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);margin-bottom:7px;}
|
|
743
1010
|
.bottomline p{margin:0;font-size:15px;color:var(--ink);}
|
|
@@ -831,7 +1098,9 @@ footer{margin-top:56px;padding-top:18px;border-top:1px solid var(--line);font-fa
|
|
|
831
1098
|
@keyframes rise{to{opacity:1;transform:none;}}
|
|
832
1099
|
@media (max-width:640px){
|
|
833
1100
|
main{padding:34px 16px 60px;}
|
|
834
|
-
.
|
|
1101
|
+
.verdict{padding:24px 20px 20px;}
|
|
1102
|
+
.glance,.bottomline,.checks,.redteam,.decision-safety{grid-template-columns:1fr;}
|
|
1103
|
+
.snapshot-table{min-width:560px;}.payback-result{grid-template-columns:1fr;}.payback-result p{grid-column:1;}
|
|
835
1104
|
}
|
|
836
1105
|
@media (prefers-reduced-motion:reduce){.reveal{opacity:1;transform:none;animation:none;}}
|
|
837
1106
|
@media print{.reveal{opacity:1;transform:none;animation:none;}.fold[open]{break-inside:avoid;}}
|
|
@@ -849,7 +1118,7 @@ footer{margin-top:56px;padding-top:18px;border-top:1px solid var(--line);font-fa
|
|
|
849
1118
|
${flags}
|
|
850
1119
|
</header>
|
|
851
1120
|
${body}
|
|
852
|
-
<footer>Generated by aiki · ${escapeHtml(view.runId)} · a local model council. This is analysis, not advice — verify before acting.</footer>
|
|
1121
|
+
<footer>Generated by aiki · ${escapeHtml(view.runId)} · ${quick ? 'single-model quick analysis' : 'a local model council'}. This is analysis, not advice — verify before acting.</footer>
|
|
853
1122
|
</main>
|
|
854
1123
|
<script>
|
|
855
1124
|
const REPORT_MD = ${md};
|