aiki-cli 0.2.2 → 0.3.1
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 +113 -6
- package/README.md +119 -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 +66 -8
- package/dist/council/view.js +446 -117
- package/dist/orchestration/calculations.js +97 -0
- package/dist/orchestration/context.js +37 -9
- package/dist/orchestration/decision-dossier.js +320 -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 +39 -0
- package/dist/orchestration/preflight.js +203 -0
- package/dist/orchestration/quick-analysis.js +93 -0
- package/dist/orchestration/stages/cr-ladder.js +80 -0
- package/dist/orchestration/stages/s10-render.js +574 -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 +118 -85
- package/dist/orchestration/url-sources.js +200 -0
- package/dist/providers/codex.js +2 -1
- package/dist/providers/spawn.js +5 -0
- package/dist/schemas/index.js +638 -15
- 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 +12 -4
- package/dist/tui/app.js +53 -14
- 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 +118 -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 { readerClaimLabel, readerClaimRefs, renderDecisionDossierMarkdown, stripReaderClaimIds } 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,169 @@ 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)}</li>`).join('')}</ul></div></details>`
|
|
382
|
+
: '';
|
|
383
|
+
const startHere = dossier.experiments.actions[0]?.action ?? 'No executable next step was produced.';
|
|
384
|
+
const recommendationLead = stripReaderClaimIds(dossier.recommendation.reason);
|
|
385
|
+
const recommendationDetail = stripReaderClaimIds(dossier.recommendation.summary);
|
|
386
|
+
const factLabels = ['Decisive result', 'Consequence', 'Supporting signal'];
|
|
387
|
+
const facts = keyFindings.slice(0, 3).map((finding, index) => `<article class="decision-fact"><span>${factLabels[index]}</span><p>${escapeHtml(finding)}</p></article>`).join('');
|
|
388
|
+
const snapshot = report.decisionSnapshot;
|
|
389
|
+
const decisiveNumbers = snapshot ? `<div class="decision-numbers">
|
|
390
|
+
<span class="section-eyebrow">Decisive numbers</span>
|
|
391
|
+
<div class="table-wrap"><table class="snapshot-table decisive-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)}</td></tr>`).join('')}</tbody></table></div>
|
|
392
|
+
${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)}</p></div>` : ''}
|
|
393
|
+
</div>` : '';
|
|
394
|
+
const optionComparison = snapshot ? `<div class="option-comparison">
|
|
395
|
+
<span class="section-eyebrow">Options at a glance</span>
|
|
396
|
+
<div class="table-wrap"><table class="snapshot-table options-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)}</td></tr>`).join('')}</tbody></table></div>
|
|
397
|
+
</div>` : '';
|
|
398
|
+
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)}</p></div>` : '';
|
|
399
|
+
const topCounterCase = dossier.counterCase.reasoning;
|
|
400
|
+
const unknowns = criticalUnknowns.length
|
|
401
|
+
? `<ul class="critical-unknowns">${criticalUnknowns.map((unknown) => `<li>${escapeHtml(unknown)}</li>`).join('')}</ul>`
|
|
402
|
+
: '<p class="muted">No verdict-flipping unknown was recorded.</p>';
|
|
403
|
+
const recommendation = `<div class="verdict tone-${tone}">
|
|
404
|
+
<div class="decision-status"><span class="pill">Council recommendation</span><span>${escapeHtml(dossier.recommendation.status.replaceAll('_', ' '))}</span></div>
|
|
405
|
+
${decisiveNumbers}
|
|
406
|
+
<p class="verdict-text">${escapeHtml(recommendationLead)}</p>
|
|
407
|
+
<p class="verdict-detail">${escapeHtml(recommendationDetail)}</p>
|
|
408
|
+
<div class="evidence-coverage ${coverageLabel.toLowerCase()}">
|
|
409
|
+
<div><span class="fk">Evidence coverage</span><strong>${coverageLabel} · ${dossierPct(confidence.verificationCoverage)}</strong></div>
|
|
410
|
+
<div class="coverage-track"><span style="width:${Math.round(confidence.verificationCoverage * 100)}%"></span></div>
|
|
411
|
+
<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>
|
|
412
|
+
</div>
|
|
413
|
+
${snapshot ? '' : `<div class="decision-facts">${facts}</div>`}
|
|
414
|
+
<div class="action-callout"><span>Do this first</span><p>${escapeHtml(startHere)}</p></div>
|
|
415
|
+
${optionComparison}
|
|
416
|
+
${tripwire}
|
|
417
|
+
<div class="decision-safety">
|
|
418
|
+
<article><span class="tag">What could overturn this</span><p>${escapeHtml(topCounterCase)}</p></article>
|
|
419
|
+
<article><span class="tag">Critical unknowns</span>${unknowns}</article>
|
|
420
|
+
</div>
|
|
421
|
+
${report.verdict.criticalWarning ? `<div class="critical-warning"><span class="tag">Critical warning</span><p>${escapeHtml(report.verdict.criticalWarning)}</p></div>` : ''}
|
|
422
|
+
<p class="council-read"><strong>Council read:</strong> ${escapeHtml(dossierCouncilRead(report))}</p>
|
|
423
|
+
${conditions}
|
|
424
|
+
${dossier.recommendation.claimIds.length ? `<details class="decision-evidence"><summary>Evidence behind this recommendation (${dossier.recommendation.claimIds.length})</summary><ul>${dossier.recommendation.claimIds.map((id) => `<li>${escapeHtml(readerClaimLabel(report, id))}</li>`).join('')}</ul></details>` : ''}
|
|
425
|
+
${dossierWarning(report, ['synthesis_suspect'])}
|
|
426
|
+
${dossier.recommendation.claimIds.length ? '' : '<div class="warns"><span class="warn">⚑ DEGRADED: recommendation has no stored graph anchor</span></div>'}
|
|
427
|
+
</div>`;
|
|
428
|
+
const claimChain = dossierTable(['Claim', 'Ruling', 'Evidence status', 'Depends on'], dossier.claimChain.map((claim) => [claim.text, claim.ruling, claim.evidenceStatus, readerClaimRefs(report, claim.dependsOn)]));
|
|
429
|
+
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, readerClaimRefs(report, item.claimIds)]))}`;
|
|
430
|
+
const disagreementCards = report.disagreements.length
|
|
431
|
+
? report.disagreements.map((item) => `<article class="card debate-card"><h3>${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('')
|
|
432
|
+
: `<p class="muted">${report.mode === 'quick' ? 'No cross-model disagreement analysis runs in quick mode.' : 'No genuine disagreements were stored.'}</p>`;
|
|
433
|
+
const changes = dossier.positionChanges.length
|
|
434
|
+
? dossierTable(['Event', 'Claim', 'Responder', 'Change', 'Evidence', 'Detail'], dossier.positionChanges.map((event) => [event.eventId, readerClaimLabel(report, event.claimId), providerName(event.responder), event.response, dossierRefs(event.evidenceIds), event.narrowedProposition ?? event.reasoning]))
|
|
435
|
+
: '<p class="muted">No CONCEDE, COUNTER, or NARROW event was recorded.</p>';
|
|
436
|
+
const minority = `<h3>Minority report</h3>${report.minority.dissent.length
|
|
437
|
+
? `<ul class="checks">${report.minority.dissent.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`
|
|
438
|
+
: '<p class="muted">No minority dissent was recorded.</p>'}
|
|
439
|
+
${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>` : ''}
|
|
440
|
+
<p class="lede">Decision-blocking status: <strong>${escapeHtml(report.minority.blocksDecision)}</strong></p>`;
|
|
441
|
+
const disagreement = `${dossierWarning(report, ['single_model', 'low_diversity'])}${disagreementCards}<h3>Position changes</h3>${changes}${minority}`;
|
|
442
|
+
const shared = dossier.sharedConcerns.length
|
|
443
|
+
? `<h3>Shared concerns</h3><ul class="agree">${dossier.sharedConcerns.map((item) => `<li><p>${escapeHtml(item.text)}</p><span class="who-names">${escapeHtml(item.providerIds.map(providerName).join(', '))} · ${escapeHtml(item.evidenceStatus)}</span></li>`).join('')}</ul>`
|
|
444
|
+
: '<h3>Shared concerns</h3><p class="muted">None recorded.</p>';
|
|
445
|
+
const unique = dossier.uniqueSupportedInsights.length
|
|
446
|
+
? `<h3>Unique supported insights</h3><ul class="agree">${dossier.uniqueSupportedInsights.map((item) => `<li><p>${escapeHtml(item.text)}</p><span class="who-names">${escapeHtml(providerName(item.providerId))} · ${escapeHtml(item.verificationStatus)}</span></li>`).join('')}</ul>`
|
|
447
|
+
: '<h3>Unique supported insights</h3><p class="muted">None recorded.</p>';
|
|
448
|
+
const coverage = dossierTable(['Dimension', 'Status', 'Related claims'], dossier.coverage.map((item) => [item.label, item.status, readerClaimRefs(report, item.claimIds)]));
|
|
449
|
+
const sensitivity = dossierTable(['Fact', 'Sensitivity', 'If false', 'What would change it', 'Linked claims'], dossier.sensitivity.map((item) => [item.fact, item.sensitivity, item.impactIfFalse, item.whatWouldChangeIt, readerClaimRefs(report, item.linkedClaimIds)]));
|
|
450
|
+
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>` : ''}${dossier.experiments.actions.length ? `<div class="experiment-list">${dossier.experiments.actions.map((action) => `<article class="experiment-card"><div class="experiment-head"><span>${String(action.order).padStart(2, '0')}</span><h4>${escapeHtml(action.action)}</h4><strong>${escapeHtml(action.effort)}</strong></div><p>${escapeHtml(action.why)}</p><dl><div><dt>Validates</dt><dd>${escapeHtml(readerClaimLabel(report, action.validates))}</dd></div><div><dt>Stop if</dt><dd>${escapeHtml(action.killSignal)}</dd></div></dl></article>`).join('')}</div><p class="lede">${escapeHtml(dossier.experiments.note)}</p>` : '<p class="muted">No executable experiment was produced.</p>'}`;
|
|
451
|
+
const missingDeliverables = (dossier.missingRequestedOutputs ?? []).length
|
|
452
|
+
? `<div class="warns"><span class="warn">⚑ DEGRADED: requested output missing: ${escapeHtml(dossier.missingRequestedOutputs.join(', '))}</span></div>`
|
|
453
|
+
: '';
|
|
454
|
+
const backlog = dossier.featureBacklog;
|
|
455
|
+
const featurePriorities = backlog ? `<h3>Feature priorities</h3><div class="feature-groups">${[
|
|
456
|
+
['MUST', 'Build for the first usable release', backlog.must],
|
|
457
|
+
['SHOULD', 'Add after the core path is stable', backlog.should],
|
|
458
|
+
['LATER', 'Keep out of the current build', backlog.later],
|
|
459
|
+
].filter(([, , items]) => items.length).map(([priority, description, items]) => `<section class="feature-group priority-${priority.toLowerCase()}"><header><div><span>${priority}</span><h4>${escapeHtml(description)}</h4></div><strong>${items.length}</strong></header><ul>${items.map((item) => `<li><div class="feature-title"><strong>${escapeHtml(item.feature)}</strong><span>${escapeHtml(item.effort)}</span></div><p>${escapeHtml(item.user_value)}</p><small>${escapeHtml(item.rationale)}</small></li>`).join('')}</ul></section>`).join('')}</div>${backlog.wont.length ? `<details class="inline-fold"><summary>Not in this scope (${backlog.wont.length})</summary><ul>${backlog.wont.map((item) => `<li><strong>${escapeHtml(item.feature)}</strong> — ${escapeHtml(item.reason)}</li>`).join('')}</ul></details>` : ''}` : '';
|
|
460
|
+
const implementationPlan = dossier.implementationPlan ? `<h3>Implementation plan</h3><ol class="milestone-list">${dossier.implementationPlan.milestones.map((milestone) => `<li><div class="milestone-marker"><span>${String(milestone.order).padStart(2, '0')}</span><small>${escapeHtml(milestone.timebox)}</small></div><article><h4>${escapeHtml(milestone.outcome)}</h4><ul>${milestone.tasks.map((task) => `<li>${escapeHtml(task)}</li>`).join('')}</ul><div class="acceptance"><span>Done when</span><p>${escapeHtml(milestone.acceptance_test)}</p></div></article></li>`).join('')}</ol>` : '';
|
|
461
|
+
const actionPlan = `${missingDeliverables}${featurePriorities}${implementationPlan}<h3>Validation plan</h3>${experiments}`;
|
|
462
|
+
const counterCase = dossier.counterCase.available
|
|
463
|
+
? `<article class="card risk-card"><p>${escapeHtml(dossier.counterCase.reasoning)}</p><div class="field"><span class="fk">Evidence behind this counter-case</span><p>${escapeHtml(readerClaimRefs(report, dossier.counterCase.claimIds))}</p></div></article>`
|
|
464
|
+
: `<div class="warns"><span class="warn">⚑ DEGRADED: ${escapeHtml(dossier.counterCase.reasoning)}</span></div>`;
|
|
465
|
+
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 contributions', 'Count'], dossier.contributions.map((item) => [item.name, readerClaimRefs(report, item.verifiedUniqueClaimIds), String(item.verifiedUniqueClaimIds.length)]))}`;
|
|
466
|
+
const receipt = `<div class="receipt">
|
|
467
|
+
<span>mode ${escapeHtml(report.mode)}</span><span>${report.receipt.calls}/${report.receipt.budget} provider calls</span>
|
|
468
|
+
<span>discovery ${report.receipt.categories.discovery}</span><span>verification ${report.receipt.categories.verification}</span>
|
|
469
|
+
<span>repair ${report.receipt.categories.repair}</span><span>planning ${report.receipt.categories.planning}</span>
|
|
470
|
+
<span>by provider ${escapeHtml(Object.entries(report.receipt.byProvider).map(([provider, count]) => `${providerName(provider)} ${count}`).join(', ') || 'none')}</span>
|
|
471
|
+
<span>model time ${(report.receipt.modelTimeMs / 1000).toFixed(1)}s</span>
|
|
472
|
+
</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>'}`;
|
|
473
|
+
const risks = dossierTable(['Risk', 'Severity'], report.risks.map((item) => [item.risk, item.severity]));
|
|
474
|
+
const questions = report.openQuestions.length
|
|
475
|
+
? `<ul class="checks">${report.openQuestions.map((question) => `<li>${escapeHtml(question)}</li>`).join('')}</ul>`
|
|
476
|
+
: '<p class="muted">No verdict-flipping open question was recorded.</p>';
|
|
477
|
+
const runDetails = `<article class="card">
|
|
478
|
+
<div class="field"><span class="fk">Report</span><p><code>${escapeHtml(report.reportId)}</code> · ${escapeHtml(report.generatedAt)}</p></div>
|
|
479
|
+
<details class="inline-fold compact"><summary>View original request</summary><p>${escapeHtml(report.task.original)}</p></details>
|
|
480
|
+
<div class="field"><span class="fk">Normalized question</span><p>${escapeHtml(report.task.normalized)}</p></div>
|
|
481
|
+
<div class="field"><span class="fk">Constraints</span><p>${escapeHtml(report.task.constraints.join('; ') || 'none recorded')}</p></div>
|
|
482
|
+
<div class="field"><span class="fk">Success criteria</span><p>${escapeHtml(report.task.successCriteria.join('; ') || 'none recorded')}</p></div>
|
|
483
|
+
<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>
|
|
484
|
+
<div class="field"><span class="fk">Structural score</span><p>${confidence.score}/100 (${escapeHtml(confidence.label)}) · heuristic, not benchmark-calibrated</p></div>
|
|
485
|
+
</article>${receipt}`;
|
|
486
|
+
const technical = `<details class="fold"><summary>Original submissions and graph events</summary><div class="fold-body">
|
|
487
|
+
<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>
|
|
488
|
+
<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>
|
|
489
|
+
<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>
|
|
490
|
+
<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>
|
|
491
|
+
</div></details>`;
|
|
492
|
+
const readerBody = [
|
|
493
|
+
section('01', 'Decision', recommendation, 60),
|
|
494
|
+
section('02', 'Action plan', actionPlan, 100, 'Requested product priorities, implementation milestones, and the smallest decisive validation test.'),
|
|
495
|
+
section('03', 'Why this decision', claimChain, 140, 'The load-bearing claim chain behind the recommendation.'),
|
|
496
|
+
section('04', 'What could change the decision', `<h3>Decision-sensitive facts</h3>${sensitivity}<h3>Strongest counter-case</h3>${counterCase}`, 180),
|
|
497
|
+
section('05', 'Evidence and verification', evidence, 220),
|
|
498
|
+
section('06', 'Risks, gaps, and open questions', `<h3>Risks</h3>${risks}<details class="inline-fold"><summary>View coverage ledger (${dossier.coverage.length})</summary>${coverage}</details><h3>Open questions</h3>${questions}`, 260),
|
|
499
|
+
section('07', 'Disagreement and dissent', disagreement, 300),
|
|
500
|
+
section('08', 'What the council added', `${shared}${unique}<h3>Verified unique contributions</h3>${contributions}`, 340),
|
|
501
|
+
section('09', 'Run details', runDetails, 380),
|
|
502
|
+
].join('');
|
|
503
|
+
return `${stripReaderClaimIds(readerBody)}${section('10', 'Technical audit', technical, 420)}`;
|
|
504
|
+
}
|
|
313
505
|
function renderLegacyIdeaBody(view) {
|
|
314
506
|
const risks = view.risks ?? [];
|
|
315
507
|
const agreements = view.agreements ?? [];
|
|
@@ -412,9 +604,10 @@ function renderIdeaBody(view) {
|
|
|
412
604
|
${conditions}
|
|
413
605
|
${glance}
|
|
414
606
|
</section>`;
|
|
607
|
+
const synthesisWarning = view.flags.includes('synthesis_suspect') ? '<div class="warns"><span class="warn">⚑ synthesis suspect</span></div>' : '';
|
|
415
608
|
const chairman = view.keyPoints?.length
|
|
416
|
-
? section('01', "Chairman's reasoning",
|
|
417
|
-
: '';
|
|
609
|
+
? section('01', "Chairman's reasoning", `${synthesisWarning}<ul class="reasons">${view.keyPoints.map((p) => `<li>${escapeHtml(p)}</li>`).join('')}</ul>`, 100)
|
|
610
|
+
: synthesisWarning ? section('01', "Chairman's reasoning", `${synthesisWarning}<p class="muted">No reliable chairman reasoning was produced.</p>`, 100) : '';
|
|
418
611
|
const scorecard = view.scorecard?.length
|
|
419
612
|
? `<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
613
|
: '';
|
|
@@ -440,11 +633,13 @@ function renderIdeaBody(view) {
|
|
|
440
633
|
<div class="field"><span class="fk">Chair</span><p>${escapeHtml(d.chair)}${d.reasoning ? ` — ${escapeHtml(d.reasoning)}` : ''}</p></div>
|
|
441
634
|
</article>`).join('')
|
|
442
635
|
: '<p class="muted">No contradictions reached the chair.</p>';
|
|
443
|
-
const plan = view.actionPlan
|
|
444
|
-
? `<div class="
|
|
636
|
+
const plan = view.actionPlan && 'kind' in view.actionPlan
|
|
637
|
+
? `<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>`
|
|
638
|
+
: view.actionPlan
|
|
639
|
+
? `<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
640
|
${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
641
|
</tbody></table><p class="lede">${escapeHtml(view.actionPlan.sequencing_note)}</p></div>`
|
|
447
|
-
|
|
642
|
+
: '<p class="muted">No validation plan artifact recorded.</p>';
|
|
448
643
|
const questions = view.openQuestions?.length
|
|
449
644
|
? `<ul class="checks">${view.openQuestions.map((q) => `<li>${escapeHtml(q)}</li>`).join('')}</ul>`
|
|
450
645
|
: '<p class="muted">No verdict-flipping open questions recorded.</p>';
|
|
@@ -478,15 +673,43 @@ function renderIdeaBody(view) {
|
|
|
478
673
|
${renderTechnical(view)}
|
|
479
674
|
`;
|
|
480
675
|
}
|
|
676
|
+
function renderQuickIdeaBody(view) {
|
|
677
|
+
const tone = recommendationTone(view.recommendation, view.signal?.tone ?? 'caution');
|
|
678
|
+
const label = recommendationLabel(view.recommendation, view.signal?.label ?? 'Quick analysis');
|
|
679
|
+
const conditions = view.conditions?.length
|
|
680
|
+
? `<div class="conditions"><span class="fk">Conditions</span><ul>${view.conditions.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul></div>`
|
|
681
|
+
: '';
|
|
682
|
+
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>`;
|
|
683
|
+
const reasoning = view.keyPoints.length
|
|
684
|
+
? `<ul class="reasons">${view.keyPoints.map((point) => `<li>${escapeHtml(point)}</li>`).join('')}</ul>`
|
|
685
|
+
: '<p class="muted">No structured reasoning recorded.</p>';
|
|
686
|
+
const risks = view.risks?.length
|
|
687
|
+
? `<ul class="checks">${view.risks.map((risk) => `<li>${escapeHtml(risk.assumption)} — ${escapeHtml(risk.challenge)}</li>`).join('')}</ul>`
|
|
688
|
+
: '<p class="muted">No load-bearing risk was supported strongly enough to list.</p>';
|
|
689
|
+
const plan = view.actionPlan && !('kind' in view.actionPlan)
|
|
690
|
+
? `<ul class="checks">${view.actionPlan.actions.map((action) => `<li><strong>${escapeHtml(action.action)}</strong> — ${escapeHtml(action.kill_signal)}</li>`).join('')}</ul>`
|
|
691
|
+
: '<p class="muted">No executable plan was produced.</p>';
|
|
692
|
+
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('');
|
|
693
|
+
const receipt = view.receipt?.length ? `<div class="receipt">${view.receipt.map((line) => `<span>${escapeHtml(line)}</span>`).join('')}</div>` : '';
|
|
694
|
+
return `${hero}
|
|
695
|
+
${section('01', 'Analyst reasoning', reasoning, 100, 'One structured analyst; no council or independent-consensus claim.')}
|
|
696
|
+
${section('02', 'Load-bearing risks', risks, 180)}
|
|
697
|
+
${section('03', 'Validation plan', plan, 240)}
|
|
698
|
+
${receipt ? section('04', 'Receipt', receipt, 300) : ''}
|
|
699
|
+
${section('05', 'Analyst output', `<div class="model-grid">${analyst}</div>`, 360)}`;
|
|
700
|
+
}
|
|
481
701
|
/** Serialize a council view to clean Markdown — for the HTML "Copy" button (paste into a coding assistant). */
|
|
482
702
|
function councilMarkdown(view) {
|
|
703
|
+
if (view.decisionReport?.dossier)
|
|
704
|
+
return renderDecisionDossierMarkdown(view.decisionReport);
|
|
483
705
|
const isIdea = view.workflow !== 'code-review';
|
|
706
|
+
const quick = isIdea && view.mode === 'quick';
|
|
484
707
|
const reportV3 = isIdea && Boolean(view.recommendation || view.actionPlan);
|
|
485
708
|
const L = [];
|
|
486
709
|
L.push(`# ${isIdea && view.topic ? cleanTopic(view.topic) : isIdea ? 'Idea refinement' : 'Code review'}`, '');
|
|
487
710
|
const panel = view.columns.map((c) => c.title).join(', ');
|
|
488
711
|
if (panel)
|
|
489
|
-
L.push(`Panel: ${panel}${view.moderator ? ` · Chair: ${view.moderator}` : ''}`, '');
|
|
712
|
+
L.push(quick ? `Analyst: ${panel}` : `Panel: ${panel}${view.moderator ? ` · Chair: ${view.moderator}` : ''}`, '');
|
|
490
713
|
if (view.recommendation) {
|
|
491
714
|
L.push('## Bottom line', '', `**${recommendationLabel(view.recommendation, '')}** — ${view.verdict}`, '');
|
|
492
715
|
if (view.conditions?.length) {
|
|
@@ -500,11 +723,16 @@ function councilMarkdown(view) {
|
|
|
500
723
|
L.push('## Verdict', '', view.verdict, '');
|
|
501
724
|
}
|
|
502
725
|
if (view.keyPoints?.length) {
|
|
503
|
-
L.push("## Chairman's reasoning", '');
|
|
726
|
+
L.push(quick ? '## Analyst reasoning' : "## Chairman's reasoning", '');
|
|
727
|
+
if (view.flags.includes('synthesis_suspect'))
|
|
728
|
+
L.push('⚠ synthesis_suspect — the chair output required deterministic repair or degradation handling.', '');
|
|
504
729
|
for (const p of view.keyPoints)
|
|
505
730
|
L.push(`- ${p}`);
|
|
506
731
|
L.push('');
|
|
507
732
|
}
|
|
733
|
+
else if (view.flags.includes('synthesis_suspect')) {
|
|
734
|
+
L.push("## Chairman's reasoning", '', '⚠ synthesis_suspect — no reliable chairman reasoning was produced.', '');
|
|
735
|
+
}
|
|
508
736
|
if (isIdea) {
|
|
509
737
|
if (reportV3 && view.scorecard?.length) {
|
|
510
738
|
L.push('## Dimension scorecard (best-effort)', '');
|
|
@@ -537,14 +765,23 @@ function councilMarkdown(view) {
|
|
|
537
765
|
}
|
|
538
766
|
if (reportV3 && view.actionPlan) {
|
|
539
767
|
L.push('## Validation plan', '');
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
L.push(`
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
L.push(` - Kill signal: ${a.kill_signal}`);
|
|
768
|
+
if ('kind' in view.actionPlan) {
|
|
769
|
+
const planFlags = view.flags.filter((flag) => flag === 'plan_fallback' || flag === 'plan_skipped');
|
|
770
|
+
L.push(`Planner unavailable: ${view.actionPlan.reason}${planFlags.length ? ` (${planFlags.join(', ')})` : ''}.`, '', 'Unresolved questions:');
|
|
771
|
+
for (const question of view.actionPlan.unresolved_questions)
|
|
772
|
+
L.push(`- ${question}`);
|
|
546
773
|
}
|
|
547
|
-
|
|
774
|
+
else {
|
|
775
|
+
for (const a of view.actionPlan.actions) {
|
|
776
|
+
L.push(`${a.order}. ${a.action}`);
|
|
777
|
+
L.push(` - Why: ${a.why}`);
|
|
778
|
+
L.push(` - Validates: ${a.validates}`);
|
|
779
|
+
L.push(` - Effort: ${a.effort}`);
|
|
780
|
+
L.push(` - Kill signal: ${a.kill_signal}`);
|
|
781
|
+
}
|
|
782
|
+
L.push('', view.actionPlan.sequencing_note);
|
|
783
|
+
}
|
|
784
|
+
L.push('');
|
|
548
785
|
}
|
|
549
786
|
if (reportV3 && view.openQuestions?.length) {
|
|
550
787
|
L.push('## Open questions that flip the verdict', '');
|
|
@@ -553,7 +790,7 @@ function councilMarkdown(view) {
|
|
|
553
790
|
L.push('');
|
|
554
791
|
}
|
|
555
792
|
if (view.columns.length) {
|
|
556
|
-
L.push('## How each model saw it', '');
|
|
793
|
+
L.push(quick ? '## Analyst output' : '## How each model saw it', '');
|
|
557
794
|
for (const c of view.columns) {
|
|
558
795
|
L.push(`### ${c.title}`);
|
|
559
796
|
for (const l of c.lines.slice(0, 10))
|
|
@@ -640,7 +877,7 @@ function renderTechnical(view) {
|
|
|
640
877
|
const dissent = view.dissent.length ? `<ul>${view.dissent.map((d) => `<li>${escapeHtml(d)}</li>`).join('')}</ul>` : '<p class="muted">None recorded.</p>';
|
|
641
878
|
return `
|
|
642
879
|
<details class="fold reveal" style="animation-delay:0ms">
|
|
643
|
-
<summary
|
|
880
|
+
<summary>${view.mode === 'quick' ? 'Full single-analyst output (technical)' : 'Full council analysis (technical)'}</summary>
|
|
644
881
|
<div class="fold-body">
|
|
645
882
|
<h4 class="fold-h">Each model, in its own words</h4>
|
|
646
883
|
<div class="cols">${columns || '<p class="muted">No model output recorded.</p>'}</div>
|
|
@@ -653,18 +890,21 @@ function renderTechnical(view) {
|
|
|
653
890
|
}
|
|
654
891
|
export function renderCouncilHtml(view) {
|
|
655
892
|
const isIdea = view.workflow !== 'code-review';
|
|
656
|
-
const
|
|
893
|
+
const quick = isIdea && view.mode === 'quick';
|
|
894
|
+
const kicker = quick ? 'aiki · quick analysis' : isIdea ? 'aiki · idea refinement' : 'aiki · code review';
|
|
657
895
|
const title = isIdea && view.topic ? cleanTopic(view.topic) : (isIdea ? 'Idea refinement' : 'Code review');
|
|
658
896
|
const panel = view.columns.map((c) => c.title);
|
|
659
897
|
const metaBits = [
|
|
660
|
-
panel.length ?
|
|
661
|
-
view.moderator ? `Chair: ${view.moderator}` : '',
|
|
898
|
+
panel.length ? `${quick ? 'Analyst' : 'Panel'}: ${panel.join(' · ')}` : '',
|
|
899
|
+
!quick && view.moderator ? `Chair: ${view.moderator}` : '',
|
|
662
900
|
view.calls,
|
|
663
901
|
].filter(Boolean);
|
|
664
902
|
const flags = view.flags.length
|
|
665
903
|
? `<div class="warns">${view.flags.map((f) => `<span class="warn">⚑ ${escapeHtml(f.replaceAll('_', ' '))}</span>`).join('')}</div>`
|
|
666
904
|
: '';
|
|
667
|
-
const body = isIdea
|
|
905
|
+
const body = isIdea && view.decisionReport?.dossier
|
|
906
|
+
? renderDossierIdeaBody(view.decisionReport)
|
|
907
|
+
: quick ? renderQuickIdeaBody(view) : isIdea ? renderIdeaBody(view) : renderReviewBody(view);
|
|
668
908
|
// Embed the report as Markdown for the Copy button. Escape "<" so a "</script>" in content can't break out.
|
|
669
909
|
const md = JSON.stringify(councilMarkdown(view)).replace(/</g, '\\u003c');
|
|
670
910
|
return `<!doctype html>
|
|
@@ -672,44 +912,88 @@ export function renderCouncilHtml(view) {
|
|
|
672
912
|
<head>
|
|
673
913
|
<meta charset="utf-8">
|
|
674
914
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
675
|
-
<title>${escapeHtml(isIdea ? 'Idea refinement' : 'Code review')} — aiki
|
|
915
|
+
<title>${escapeHtml(quick ? 'Quick analysis' : isIdea ? 'Idea refinement' : 'Code review')} — aiki</title>
|
|
676
916
|
<style>
|
|
677
917
|
:root{
|
|
678
918
|
color-scheme: light;
|
|
679
919
|
--serif:"Iowan Old Style","Palatino Linotype",Palatino,Charter,Georgia,"Times New Roman",serif;
|
|
680
920
|
--sans:-apple-system,BlinkMacSystemFont,"Avenir Next","Segoe UI",system-ui,sans-serif;
|
|
681
921
|
--mono:"SF Mono","JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;
|
|
682
|
-
--paper:#
|
|
683
|
-
--good:#
|
|
684
|
-
--caution:#
|
|
922
|
+
--paper:#fbfaf7; --panel:#f1efe9; --ink:#171a18; --soft:#59625e; --faint:#8b928e; --line:#dcded8;
|
|
923
|
+
--good:#176b52; --good-bg:#e5f3ed; --risk:#a63a30; --risk-bg:#f8e8e4;
|
|
924
|
+
--caution:#a96813; --caution-bg:#f8eedc; --slate:#4f5e59; --accent:#155f55;
|
|
685
925
|
}
|
|
686
926
|
*{box-sizing:border-box;}
|
|
687
927
|
html{-webkit-text-size-adjust:100%;}
|
|
688
928
|
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.6;}
|
|
689
|
-
main{max-width:
|
|
929
|
+
main{max-width:940px;margin:0 auto;padding:34px 24px 90px;}
|
|
690
930
|
a{color:var(--accent);}
|
|
691
|
-
h1
|
|
931
|
+
h1{font-family:var(--serif);font-weight:600;letter-spacing:-.025em;}
|
|
932
|
+
h2,h3,h4{font-family:var(--sans);font-weight:650;letter-spacing:-.01em;}
|
|
692
933
|
p{margin:0 0 .6em;}
|
|
693
934
|
|
|
694
935
|
/* masthead */
|
|
695
|
-
.mast{border-bottom:
|
|
936
|
+
.mast{border-bottom:1px solid var(--ink);padding-bottom:22px;margin-bottom:30px;}
|
|
696
937
|
.kicker{font-family:var(--mono);font-size:11.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--accent);}
|
|
697
|
-
.mast h1{font-size:clamp(
|
|
938
|
+
.mast h1{font-size:clamp(32px,5.4vw,50px);line-height:1.05;margin:12px 0 18px;max-width:18ch;}
|
|
698
939
|
.mmeta{display:flex;flex-wrap:wrap;gap:7px;}
|
|
699
940
|
.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
941
|
.warns{margin-top:12px;display:flex;flex-wrap:wrap;gap:8px;}
|
|
701
942
|
.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
943
|
|
|
703
944
|
/* verdict hero */
|
|
704
|
-
.verdict{position:relative;background
|
|
705
|
-
padding:
|
|
945
|
+
.verdict{position:relative;background:#fff;border:1px solid var(--line);border-radius:18px;
|
|
946
|
+
padding:30px 32px 26px;margin-bottom:18px;overflow:hidden;box-shadow:0 18px 50px rgba(32,45,40,.07);}
|
|
706
947
|
.verdict::before{content:"";position:absolute;left:0;top:0;bottom:0;width:6px;}
|
|
707
948
|
.tone-good::before{background:var(--good);} .tone-caution::before{background:var(--caution);} .tone-risk::before{background:var(--risk);}
|
|
708
949
|
.pill{display:inline-block;font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.02em;
|
|
709
|
-
padding:5px 13px;border-radius:100px;
|
|
950
|
+
padding:5px 13px;border-radius:100px;}
|
|
710
951
|
.tone-good .pill{background:var(--good-bg);color:var(--good);} .tone-caution .pill{background:var(--caution-bg);color:var(--caution);}
|
|
711
952
|
.tone-risk .pill{background:var(--risk-bg);color:var(--risk);}
|
|
712
|
-
.
|
|
953
|
+
.decision-status{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px;}
|
|
954
|
+
.decision-status>span:last-child{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;color:var(--soft);text-transform:uppercase;}
|
|
955
|
+
.verdict-text{font-family:var(--sans);font-weight:700;font-size:clamp(18px,2.3vw,21px);line-height:1.42;color:var(--ink);margin:0;max-width:68ch;letter-spacing:-.012em;}
|
|
956
|
+
.verdict-detail{font-size:14.5px;line-height:1.62;color:var(--soft);margin:9px 0 0;max-width:76ch;}
|
|
957
|
+
.section-eyebrow{display:block;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--accent);margin-bottom:9px;}
|
|
958
|
+
.decision-numbers{margin:0 0 20px;}.snapshot-table{width:100%;border-collapse:collapse;font-size:13.5px;line-height:1.4;}
|
|
959
|
+
.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);}
|
|
960
|
+
.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;}
|
|
961
|
+
.snapshot-table .number-value{font-family:var(--serif);font-size:17px;font-weight:700;color:var(--ink);white-space:nowrap;}
|
|
962
|
+
.snapshot-table code,.payback-result code,.tripwire code{font-size:9.5px;color:var(--faint);white-space:nowrap;}
|
|
963
|
+
.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;}
|
|
964
|
+
.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);}
|
|
965
|
+
.evidence-coverage{margin:24px 0 18px;padding:14px 16px;background:var(--paper);border:1px solid var(--line);border-radius:12px;}
|
|
966
|
+
.evidence-coverage>div:first-child{display:flex;align-items:end;justify-content:space-between;gap:12px;}
|
|
967
|
+
.evidence-coverage strong{font-family:var(--mono);font-size:13px;color:var(--ink);}
|
|
968
|
+
.coverage-track{height:5px;margin:10px 0;border-radius:99px;background:var(--line);overflow:hidden;}
|
|
969
|
+
.coverage-track span{display:block;height:100%;border-radius:inherit;background:var(--accent);}
|
|
970
|
+
.evidence-coverage.low .coverage-track span{background:var(--risk);}.evidence-coverage.medium .coverage-track span{background:var(--caution);}
|
|
971
|
+
.evidence-coverage p{font-size:13.5px;color:var(--soft);margin:0;}
|
|
972
|
+
.decision-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;margin:18px 0;}
|
|
973
|
+
.decision-fact{padding:16px;background:var(--paper);border:1px solid var(--line);border-radius:12px;}
|
|
974
|
+
.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;}
|
|
975
|
+
.decision-fact p{margin:0;font-size:14.5px;line-height:1.5;}
|
|
976
|
+
.action-callout{position:relative;margin:20px 0;padding:20px 22px 20px 26px;background:var(--ink);color:var(--paper);border-radius:12px;}
|
|
977
|
+
.action-callout::before{content:"→";position:absolute;right:20px;top:12px;font-family:var(--serif);font-size:34px;color:#8fc7b9;}
|
|
978
|
+
.action-callout>span{color:#8fc7b9;}.action-callout p{font-size:17px;line-height:1.5;margin:0;padding-right:34px;}
|
|
979
|
+
.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;}
|
|
980
|
+
.basis-chip.target_cap{background:var(--caution-bg);color:var(--caution);}.basis-chip.unknown{background:var(--risk-bg);color:var(--risk);}
|
|
981
|
+
.tripwire{margin:14px 0;padding:16px 18px;border:1px solid #b9d5cd;border-left:4px solid var(--accent);border-radius:10px;background:#f2f8f6;}
|
|
982
|
+
.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);}
|
|
983
|
+
.decision-safety{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:18px;}
|
|
984
|
+
.decision-safety article{padding:17px 18px;background:var(--paper);border:1px solid var(--line);border-radius:12px;}
|
|
985
|
+
.decision-safety article:first-child{border-left:4px solid var(--risk);}
|
|
986
|
+
.decision-safety p{font-size:14px;margin:0;}
|
|
987
|
+
.critical-unknowns{margin:0;padding-left:18px;}.critical-unknowns li{font-size:13.5px;margin:0 0 6px;}
|
|
988
|
+
.critical-warning{margin-top:12px;padding:14px 16px;background:var(--risk-bg);border:1px solid #e1b8b1;border-radius:10px;}
|
|
989
|
+
.critical-warning .tag{color:var(--risk);}.critical-warning p{font-size:13.5px;margin:0;}
|
|
990
|
+
.council-read{margin:15px 0 0;font-size:13.5px;color:var(--soft);}
|
|
991
|
+
.decision-details{margin-top:14px;border-top:1px solid var(--line);padding-top:12px;}
|
|
992
|
+
.decision-details summary{cursor:pointer;font-family:var(--mono);font-size:11px;color:var(--soft);text-transform:uppercase;letter-spacing:.08em;}
|
|
993
|
+
.decision-details>div{padding-top:10px;font-size:13.5px;}.decision-details ul{margin:8px 0;padding-left:18px;}
|
|
994
|
+
.decision-evidence{margin-top:12px;border-top:1px solid var(--line);padding-top:12px;}
|
|
995
|
+
.decision-evidence summary{cursor:pointer;font-family:var(--mono);font-size:10.5px;color:var(--faint);letter-spacing:.07em;text-transform:uppercase;}
|
|
996
|
+
.decision-evidence ul{margin:10px 0 0;padding-left:20px;}.decision-evidence li{font-size:13px;color:var(--soft);margin-bottom:5px;}
|
|
713
997
|
|
|
714
998
|
/* top action bar + copy */
|
|
715
999
|
.bar{position:sticky;top:0;z-index:5;display:flex;align-items:center;justify-content:space-between;gap:12px;
|
|
@@ -719,6 +1003,7 @@ p{margin:0 0 .6em;}
|
|
|
719
1003
|
border:0;border-radius:8px;padding:8px 15px;transition:background .15s ease,transform .1s ease;}
|
|
720
1004
|
.copy-btn:hover{filter:brightness(1.05);} .copy-btn:active{transform:translateY(1px);}
|
|
721
1005
|
.copy-btn.ok{background:var(--good);}
|
|
1006
|
+
button:focus-visible,summary:focus-visible,a:focus-visible{outline:3px solid rgba(21,95,85,.28);outline-offset:3px;}
|
|
722
1007
|
|
|
723
1008
|
/* chairman's reasoning */
|
|
724
1009
|
.reasons{margin:0;padding:0;list-style:none;}
|
|
@@ -738,6 +1023,7 @@ p{margin:0 0 .6em;}
|
|
|
738
1023
|
|
|
739
1024
|
/* bottom line */
|
|
740
1025
|
.bottomline{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:34px;}
|
|
1026
|
+
.verdict .bottomline{margin:18px 0 0;}
|
|
741
1027
|
.bottomline > div{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;}
|
|
742
1028
|
.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
1029
|
.bottomline p{margin:0;font-size:15px;color:var(--ink);}
|
|
@@ -794,12 +1080,30 @@ p{margin:0 0 .6em;}
|
|
|
794
1080
|
.score span{font-size:13.5px;color:var(--ink);}
|
|
795
1081
|
.score strong{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.08em;}
|
|
796
1082
|
.score.contested{border-left:4px solid var(--caution);} .score.examined{border-left:4px solid var(--good);} .score.unexamined{border-left:4px solid var(--risk);}
|
|
797
|
-
.table-wrap{overflow-x:auto;}
|
|
1083
|
+
.table-wrap{overflow-x:auto;overscroll-behavior-inline:contain;scrollbar-width:thin;}
|
|
798
1084
|
.data-table{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;font-size:13.5px;}
|
|
799
1085
|
.data-table th,.data-table td{text-align:left;vertical-align:top;border-bottom:1px solid var(--line);padding:9px 10px;}
|
|
800
1086
|
.data-table th{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--soft);background:var(--paper);}
|
|
801
1087
|
.data-table tr:last-child td{border-bottom:0;}
|
|
802
1088
|
.data-table code{font-family:var(--mono);font-size:12px;color:var(--accent);}
|
|
1089
|
+
.feature-groups{display:grid;gap:14px;margin-bottom:26px;}
|
|
1090
|
+
.feature-group{background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:hidden;}
|
|
1091
|
+
.feature-group>header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 18px;border-bottom:1px solid var(--line);background:var(--paper);}
|
|
1092
|
+
.feature-group>header div>span{display:block;font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.13em;color:var(--accent);}
|
|
1093
|
+
.feature-group>header h4{font-size:14px;margin:2px 0 0;}.feature-group>header>strong{font-family:var(--serif);font-size:24px;color:var(--soft);}
|
|
1094
|
+
.feature-group ul{list-style:none;margin:0;padding:0 18px;}.feature-group li{padding:14px 0;border-bottom:1px solid var(--line);}.feature-group li:last-child{border-bottom:0;}
|
|
1095
|
+
.feature-title{display:flex;align-items:baseline;justify-content:space-between;gap:12px;}.feature-title>strong{font-size:15px;}.feature-title>span{font-family:var(--mono);font-size:10px;border:1px solid var(--line);border-radius:99px;padding:1px 7px;color:var(--soft);}
|
|
1096
|
+
.feature-group p{font-size:13.5px;margin:3px 0;color:var(--ink);}.feature-group small{display:block;font-size:12.5px;line-height:1.45;color:var(--soft);}
|
|
1097
|
+
.priority-must{border-left:4px solid var(--accent);}.priority-should{border-left:4px solid var(--caution);}.priority-later{border-left:4px solid var(--slate);}
|
|
1098
|
+
.milestone-list{list-style:none;margin:0 0 28px;padding:0;}.milestone-list>li{display:grid;grid-template-columns:90px 1fr;gap:18px;position:relative;padding-bottom:18px;}
|
|
1099
|
+
.milestone-list>li:not(:last-child)::before{content:"";position:absolute;left:23px;top:37px;bottom:0;border-left:1px solid var(--line);}
|
|
1100
|
+
.milestone-marker{position:relative;z-index:1;display:flex;flex-direction:column;align-items:flex-start;}.milestone-marker>span{display:grid;place-items:center;width:47px;height:35px;border-radius:9px;background:var(--ink);color:var(--paper);font-family:var(--mono);font-size:12px;}.milestone-marker small{font-size:11px;color:var(--soft);margin-top:5px;}
|
|
1101
|
+
.milestone-list article{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;}.milestone-list h4{font-size:16px;margin:0 0 8px;}.milestone-list article ul{margin:0;padding-left:18px;}.milestone-list article li{font-size:13.5px;margin-bottom:4px;}
|
|
1102
|
+
.acceptance{margin-top:12px;padding-top:10px;border-top:1px solid var(--line);}.acceptance span{font-family:var(--mono);font-size:9.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--good);}.acceptance p{font-size:13px;margin:2px 0 0;}
|
|
1103
|
+
.experiment-list{display:grid;gap:12px;margin-bottom:14px;}.experiment-card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:16px 18px;}
|
|
1104
|
+
.experiment-head{display:grid;grid-template-columns:auto 1fr auto;align-items:start;gap:10px;}.experiment-head>span{font-family:var(--mono);font-size:11px;color:var(--accent);}.experiment-head h4{font-size:15px;margin:0;}.experiment-head>strong{font-family:var(--mono);font-size:10px;border:1px solid var(--line);border-radius:99px;padding:2px 7px;color:var(--soft);}
|
|
1105
|
+
.experiment-card>p{font-size:13.5px;color:var(--soft);margin:8px 0 12px 27px;}.experiment-card dl{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:0 0 0 27px;}.experiment-card dl>div{background:var(--paper);border-radius:8px;padding:9px 11px;}.experiment-card dt{font-family:var(--mono);font-size:9.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);}.experiment-card dd{font-size:12.5px;line-height:1.45;margin:2px 0 0;}
|
|
1106
|
+
.inline-fold{margin:14px 0;border:1px solid var(--line);border-radius:10px;background:var(--panel);}.inline-fold>summary{cursor:pointer;padding:11px 14px;font-family:var(--mono);font-size:11px;letter-spacing:.05em;color:var(--soft);}.inline-fold>ul,.inline-fold>.table-wrap,.inline-fold>p{margin:0 14px 14px;}.inline-fold.compact{background:var(--paper);}.inline-fold.compact>p{font-size:13.5px;color:var(--soft);}
|
|
803
1107
|
.debate-card p{margin-bottom:8px;}
|
|
804
1108
|
.redteam{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
|
|
805
1109
|
.redteam>div{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px 16px;}
|
|
@@ -831,7 +1135,16 @@ footer{margin-top:56px;padding-top:18px;border-top:1px solid var(--line);font-fa
|
|
|
831
1135
|
@keyframes rise{to{opacity:1;transform:none;}}
|
|
832
1136
|
@media (max-width:640px){
|
|
833
1137
|
main{padding:34px 16px 60px;}
|
|
834
|
-
.
|
|
1138
|
+
.verdict{padding:24px 20px 20px;}
|
|
1139
|
+
.glance,.bottomline,.checks,.redteam,.decision-safety{grid-template-columns:1fr;}
|
|
1140
|
+
.decision-status{align-items:flex-start;flex-direction:column;gap:8px;}.decision-status>span:last-child{text-align:left;}.pill{white-space:nowrap;}
|
|
1141
|
+
.snapshot-table{min-width:0;}.snapshot-table thead{display:none;}.snapshot-table tbody,.snapshot-table tr,.snapshot-table td{display:block;width:100%;}
|
|
1142
|
+
.snapshot-table tr{padding:12px 0;border-bottom:1px solid var(--line);}.snapshot-table td{padding:3px 8px;border:0;white-space:normal;}
|
|
1143
|
+
.snapshot-table td::before{display:block;font-family:var(--mono);font-size:9px;line-height:1.4;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);}
|
|
1144
|
+
.decisive-table td:nth-child(1)::before{content:"Metric";}.decisive-table td:nth-child(2)::before{content:"Value";}.decisive-table td:nth-child(3)::before{content:"What it means";}
|
|
1145
|
+
.options-table td:nth-child(1)::before{content:"Path";}.options-table td:nth-child(2)::before{content:"Commitment";}.options-table td:nth-child(3)::before{content:"Basis";}.options-table td:nth-child(4)::before{content:"Trade-off";}
|
|
1146
|
+
.payback-result{grid-template-columns:1fr;}.payback-result p{grid-column:1;}
|
|
1147
|
+
.milestone-list>li{grid-template-columns:64px 1fr;gap:10px;}.experiment-card dl{grid-template-columns:1fr;}.experiment-card>p,.experiment-card dl{margin-left:0;}
|
|
835
1148
|
}
|
|
836
1149
|
@media (prefers-reduced-motion:reduce){.reveal{opacity:1;transform:none;animation:none;}}
|
|
837
1150
|
@media print{.reveal{opacity:1;transform:none;animation:none;}.fold[open]{break-inside:avoid;}}
|
|
@@ -840,7 +1153,7 @@ footer{margin-top:56px;padding-top:18px;border-top:1px solid var(--line);font-fa
|
|
|
840
1153
|
<body>
|
|
841
1154
|
<div class="bar">
|
|
842
1155
|
<span class="bar-label">${escapeHtml(kicker)}</span>
|
|
843
|
-
<button
|
|
1156
|
+
<button id="copy-report" class="copy-btn">Copy report (Markdown)</button>
|
|
844
1157
|
</div>
|
|
845
1158
|
<main>
|
|
846
1159
|
<header class="mast">
|
|
@@ -849,16 +1162,32 @@ footer{margin-top:56px;padding-top:18px;border-top:1px solid var(--line);font-fa
|
|
|
849
1162
|
${flags}
|
|
850
1163
|
</header>
|
|
851
1164
|
${body}
|
|
852
|
-
<footer>Generated by aiki · ${escapeHtml(view.runId)} · a local model council. This is analysis, not advice — verify before acting.</footer>
|
|
1165
|
+
<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
1166
|
</main>
|
|
854
1167
|
<script>
|
|
855
1168
|
const REPORT_MD = ${md};
|
|
856
|
-
function copyReport(btn){
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1169
|
+
async function copyReport(btn){
|
|
1170
|
+
var original=btn.textContent;
|
|
1171
|
+
try {
|
|
1172
|
+
var copied=false;
|
|
1173
|
+
if(navigator.clipboard && navigator.clipboard.writeText){
|
|
1174
|
+
try{ await navigator.clipboard.writeText(REPORT_MD); copied=true; }catch(error){}
|
|
1175
|
+
}
|
|
1176
|
+
if(!copied){
|
|
1177
|
+
var field=document.createElement('textarea');
|
|
1178
|
+
field.value=REPORT_MD; field.setAttribute('readonly','');
|
|
1179
|
+
field.style.position='fixed'; field.style.opacity='0';
|
|
1180
|
+
document.body.appendChild(field); field.select();
|
|
1181
|
+
copied=document.execCommand('copy'); field.remove();
|
|
1182
|
+
if(!copied) throw new Error('copy unavailable');
|
|
1183
|
+
}
|
|
1184
|
+
btn.textContent='✓ Copied'; btn.classList.add('ok');
|
|
1185
|
+
}catch(error){
|
|
1186
|
+
btn.textContent='Copy failed — select the text manually';
|
|
1187
|
+
}
|
|
1188
|
+
setTimeout(function(){ btn.textContent=original; btn.classList.remove('ok'); }, 1800);
|
|
861
1189
|
}
|
|
1190
|
+
document.getElementById('copy-report').addEventListener('click',function(){ copyReport(this); });
|
|
862
1191
|
</script>
|
|
863
1192
|
</body>
|
|
864
1193
|
</html>`;
|