aiki-cli 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -5
- package/README.md +9 -11
- package/dist/bench/idea-v3-rating.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/run.js +1 -1
- package/dist/council/view.js +52 -12
- package/dist/orchestration/context.js +3 -3
- package/dist/orchestration/decision-dossier.js +417 -88
- package/dist/orchestration/decision-graph.js +31 -0
- package/dist/orchestration/legacy-idea-adapter.js +3 -0
- package/dist/orchestration/modes.js +15 -9
- package/dist/orchestration/preflight.js +17 -3
- package/dist/orchestration/quick-analysis.js +21 -4
- package/dist/orchestration/stages/s10-render.js +167 -79
- package/dist/orchestration/stages/s4-analyze.js +3 -1
- package/dist/orchestration/stages/s6-positions.js +18 -0
- package/dist/orchestration/stages/s7-decision-graph.js +3 -0
- package/dist/orchestration/stages/s8-verify.js +26 -9
- package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
- package/dist/orchestration/stages/s9-judge.js +36 -13
- package/dist/orchestration/stages/s9b-plan.js +192 -42
- package/dist/schemas/index.js +67 -3
- package/dist/skills/idea-refinement/analyst.md +5 -5
- package/dist/skills/idea-refinement/chair.md +18 -0
- package/dist/skills/idea-refinement/economics-delivery.md +11 -0
- package/dist/skills/idea-refinement/market-adoption.md +12 -0
- package/dist/skills/idea-refinement/planner.md +25 -19
- package/dist/skills/idea-refinement/rebuttal.md +15 -0
- package/dist/skills/idea-refinement/verifier.md +17 -0
- package/dist/workflows/idea-refinement.js +42 -14
- package/package.json +1 -1
|
@@ -12,33 +12,62 @@ function clipClaim(text, max = 96) {
|
|
|
12
12
|
const boundary = clipped.lastIndexOf(' ');
|
|
13
13
|
return `${clipped.slice(0, boundary > max * 0.65 ? boundary : max - 1).trimEnd()}…`;
|
|
14
14
|
}
|
|
15
|
+
/** ≤60-char noun-ish handle for a claim: drop a leading "Verdict:", keep the first clause, clip at a word. */
|
|
16
|
+
export function claimShortLabel(text, max = 60) {
|
|
17
|
+
const base = text.replace(/^\s*Verdict:\s*/i, '').split(/(?<=\S)[;—]|(?<=\.)\s/)[0].trim();
|
|
18
|
+
if (base.length <= max)
|
|
19
|
+
return base;
|
|
20
|
+
const clipped = base.slice(0, max);
|
|
21
|
+
const boundary = clipped.lastIndexOf(' ');
|
|
22
|
+
return `${clipped.slice(0, boundary > max * 0.6 ? boundary : max).trimEnd()}…`;
|
|
23
|
+
}
|
|
24
|
+
/** id → claim text lookup for stripReaderClaimIds substitution; shared by readerClaimLabel's fallback and the Markdown/HTML renderers. */
|
|
25
|
+
export function claimLookup(report) {
|
|
26
|
+
return (id) => report.claims.find((claim) => claim.id === id)?.text ?? null;
|
|
27
|
+
}
|
|
15
28
|
/** Human-readable labels for reader-facing evidence links. Raw ids stay in the technical audit/JSON. */
|
|
16
29
|
export function readerClaimLabel(report, id) {
|
|
17
30
|
const claim = report.claims.find((item) => item.id === id);
|
|
18
31
|
if (claim)
|
|
19
|
-
return
|
|
20
|
-
return /^G\d+$/.test(id) ? 'Supporting claim' : clipClaim(stripReaderClaimIds(id));
|
|
32
|
+
return claimShortLabel(claim.text);
|
|
33
|
+
return /^G\d+$/.test(id) ? 'Supporting claim' : clipClaim(stripReaderClaimIds(id, claimLookup(report)));
|
|
21
34
|
}
|
|
22
35
|
export function readerClaimRefs(report, ids) {
|
|
23
36
|
return ids.length ? ids.map((id) => readerClaimLabel(report, id)).join('; ') : 'none recorded';
|
|
24
37
|
}
|
|
25
|
-
/** Remove internal graph notation
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
/** Remove or substitute internal graph notation in reader prose. With a lookup, ids become quoted short labels
|
|
39
|
+
* instead of vanishing (never bare-delete); without one (no lookup available), ids are dropped — the legacy
|
|
40
|
+
* behavior, kept only for callers that have no claim table to look up against. `sanitizeLabel` escapes each
|
|
41
|
+
* injected label for the caller's output format (markdown passes `cell`; HTML passes nothing — raw). */
|
|
42
|
+
export function stripReaderClaimIds(text, labelFor, sanitizeLabel = (label) => label) {
|
|
43
|
+
const label = (id) => {
|
|
44
|
+
const hit = labelFor?.(id);
|
|
45
|
+
return hit ? `"${sanitizeLabel(claimShortLabel(hit))}"` : 'a related claim';
|
|
46
|
+
};
|
|
47
|
+
let out = text
|
|
48
|
+
// whole parenthetical citation groups — comma OR slash separated — vanish entirely: (G19), (G7/G13), (G1, G2)
|
|
49
|
+
.replace(/\s*\((?:\s*`?G\d+`?\s*[,/]?\s*)+\)/g, '')
|
|
50
|
+
// "G21's kill criteria" → `"the spike kill-gate"'s kill criteria` — keep the possessive on the label itself
|
|
51
|
+
// rather than injecting filler noun text, so it reads correctly for any noun that follows.
|
|
52
|
+
.replace(/\bG(\d+)'s\b/g, (_, n) => (labelFor ? `${label(`G${n}`)}'s` : ''));
|
|
53
|
+
out = labelFor
|
|
54
|
+
? out.replace(/\bG\d+\b/g, (id) => label(id))
|
|
55
|
+
: out.replace(/\bG\d+\s+assumes\b/g, 'This assumes').replace(/\bG\d+\b/g, '');
|
|
56
|
+
return out.replace(/\s{2,}/g, ' ').replace(/\s+([.,;:])/g, '$1').trim();
|
|
35
57
|
}
|
|
36
58
|
function providerName(id) {
|
|
37
59
|
return id in DISPLAY_NAME ? DISPLAY_NAME[id] : id;
|
|
38
60
|
}
|
|
61
|
+
const FLAG_EXPLAIN = {
|
|
62
|
+
synthesis_suspect: 'the chair output needed a deterministic repair; phrasing may be less reliable than the underlying graph',
|
|
63
|
+
headless_intent: 'no human confirmed the interpretation; documented defaults were used',
|
|
64
|
+
weak_seat: 'one scout seat contributed far less evidenced material than the other; treat convergence cautiously',
|
|
65
|
+
source_fallback_search: 'a supplied page blocked direct reading; the source-investigation seat searched for accessible sources instead',
|
|
66
|
+
deliverable_gap: 'one or more surviving scouts omitted a requested feature or implementation proposal',
|
|
67
|
+
};
|
|
39
68
|
function degradation(report, flags) {
|
|
40
69
|
const active = flags.filter((flag) => report.flags.includes(flag));
|
|
41
|
-
return active.
|
|
70
|
+
return active.flatMap((flag) => [`> ⚠ DEGRADED: ${flag}${FLAG_EXPLAIN[flag] ? ` — ${FLAG_EXPLAIN[flag]}.` : ''}`, '']);
|
|
42
71
|
}
|
|
43
72
|
function pct(value) {
|
|
44
73
|
return `${Math.round(value * 100)}%`;
|
|
@@ -56,12 +85,239 @@ function councilRead(report) {
|
|
|
56
85
|
const resolved = report.disagreements.filter((item) => item.status === 'RESOLVED').length;
|
|
57
86
|
return `${report.disagreements.length} genuine disagreement${report.disagreements.length === 1 ? '' : 's'} reached the chair; ${resolved} ${resolved === 1 ? 'was' : 'were'} resolved.`;
|
|
58
87
|
}
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
export function sanitizeReaderText(text, labelFor) {
|
|
89
|
+
return stripReaderClaimIds(text, labelFor)
|
|
90
|
+
.replace(/\b(?:G|C|D)\d+\b/g, '')
|
|
91
|
+
.replace(/\bUNVERIFIABLE\b/gi, 'not independently confirmed')
|
|
92
|
+
.replace(/\bUNVERIFIED\b/gi, 'not yet confirmed')
|
|
93
|
+
.replace(/\bPARTIAL\b/gi, 'partly supported')
|
|
94
|
+
.replace(/\bCONFLICTED\b/gi, 'supported by conflicting evidence')
|
|
95
|
+
.replace(/\bstructural score\b/gi, 'confidence estimate')
|
|
96
|
+
.replace(/\bevidence coverage\b/gi, 'source strength')
|
|
97
|
+
.replace(/\bverification coverage\b/gi, 'source strength')
|
|
98
|
+
.replace(/\b(?:claim|graph) ids?\b/gi, 'internal references')
|
|
99
|
+
.replace(/\b(?:provider|model) calls?\b/gi, 'analysis steps')
|
|
100
|
+
.replace(/\banswer editor\b/gi, 'answer synthesis')
|
|
101
|
+
.replace(/\breader_brief\b/gi, 'reader summary')
|
|
102
|
+
.replace(/\s{2,}/g, ' ')
|
|
103
|
+
.replace(/\s+([.,;:])/g, '$1')
|
|
104
|
+
.trim();
|
|
105
|
+
}
|
|
106
|
+
export function cleanReaderText(report, text) {
|
|
107
|
+
return sanitizeReaderText(text, claimLookup(report));
|
|
108
|
+
}
|
|
109
|
+
const MATERIAL_WARNING_ORDER = [
|
|
110
|
+
'synthesis_suspect', 'low_diversity', 'weak_seat', 'deliverable_gap', 'plan_skipped',
|
|
111
|
+
'plan_fallback', 'headless_intent', 'verification_skipped', 'research_ungrounded',
|
|
112
|
+
];
|
|
113
|
+
const READER_WARNING = {
|
|
114
|
+
synthesis_suspect: 'The final synthesis needed repair; treat its wording cautiously.',
|
|
115
|
+
low_diversity: 'Independent diversity was reduced because one scout seat had to be resampled.',
|
|
116
|
+
weak_seat: 'One scout contributed much less evidenced material; treat convergence cautiously.',
|
|
117
|
+
deliverable_gap: 'At least one scout omitted a requested feature or implementation proposal.',
|
|
118
|
+
plan_skipped: 'The answer-planning step was skipped because its call budget was unavailable.',
|
|
119
|
+
plan_fallback: 'The answer planner failed validation; this report uses a deterministic fallback.',
|
|
120
|
+
headless_intent: 'No person confirmed the interpreted request; documented defaults were used.',
|
|
121
|
+
verification_skipped: 'Independent verification was skipped; important factual claims may remain unchecked.',
|
|
122
|
+
research_ungrounded: 'Source investigation did not produce a cited public source; treat current claims as ungrounded.',
|
|
123
|
+
};
|
|
124
|
+
export function isDecisionSnapshotRelevant(snapshot) {
|
|
125
|
+
if (!snapshot)
|
|
126
|
+
return false;
|
|
127
|
+
const allUnknown = snapshot.options.every((option) => option.commitmentKind === 'UNKNOWN');
|
|
128
|
+
const paybackUnavailable = !snapshot.payback || snapshot.payback.status === 'NOT_COMPUTABLE';
|
|
129
|
+
return !(allUnknown && paybackUnavailable);
|
|
130
|
+
}
|
|
131
|
+
function safeHttpUrl(raw) {
|
|
132
|
+
if (!raw)
|
|
133
|
+
return undefined;
|
|
134
|
+
try {
|
|
135
|
+
const url = new URL(raw);
|
|
136
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : undefined;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function looksLocalPath(value) {
|
|
143
|
+
if (!value)
|
|
144
|
+
return false;
|
|
145
|
+
return /^file:/i.test(value)
|
|
146
|
+
|| /^(?:\/|\.\.?[\\/]|[A-Za-z]:[\\/])/.test(value)
|
|
147
|
+
|| /^(?:[^:/\\]+[\\/])+[^/\\]+$/.test(value);
|
|
148
|
+
}
|
|
149
|
+
/** One cleaned reader contract consumed by Markdown, HTML, and terminal renderers. */
|
|
150
|
+
export function buildReaderProjection(report) {
|
|
151
|
+
const brief = report.dossier.readerBrief;
|
|
152
|
+
const clean = (text) => cleanReaderText(report, text);
|
|
153
|
+
const sources = [];
|
|
154
|
+
const sourceIndexByKey = new Map();
|
|
155
|
+
for (const id of brief.source_ids) {
|
|
156
|
+
const source = report.dossier.evidence.find((item) => item.id === id);
|
|
157
|
+
if (!source)
|
|
158
|
+
continue;
|
|
159
|
+
const url = safeHttpUrl(source.url) ?? safeHttpUrl(source.source);
|
|
160
|
+
const title = source.title && !looksLocalPath(source.title) ? clean(source.title) : undefined;
|
|
161
|
+
const label = title ?? (url ? url
|
|
162
|
+
: source.sourceKind === 'MODEL_KNOWLEDGE' ? 'Background model knowledge'
|
|
163
|
+
: source.sourceKind === 'USER' || looksLocalPath(source.source) ? 'User-supplied material' : 'Source material');
|
|
164
|
+
const key = url ? `url:${url.toLowerCase()}` : title ? `id:${source.id}` : `label:${label.toLowerCase()}`;
|
|
165
|
+
const citedFor = [...new Set(source.claimIds.flatMap((claimId) => {
|
|
166
|
+
const claim = report.claims.find((item) => item.id === claimId);
|
|
167
|
+
return claim ? [claimShortLabel(clean(claim.text))] : [];
|
|
168
|
+
}))].slice(0, 3);
|
|
169
|
+
const existingIndex = sourceIndexByKey.get(key);
|
|
170
|
+
if (existingIndex !== undefined) {
|
|
171
|
+
sources[existingIndex].citedFor = [...new Set([...sources[existingIndex].citedFor, ...citedFor])].slice(0, 3);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
sourceIndexByKey.set(key, sources.length);
|
|
175
|
+
sources.push({ id: source.id, label, ...(url ? { url } : {}), citedFor });
|
|
176
|
+
}
|
|
177
|
+
const cleanBacklog = report.dossier.featureBacklog ? {
|
|
178
|
+
must: report.dossier.featureBacklog.must.map((item) => ({ ...item, feature: clean(item.feature), user_value: clean(item.user_value), rationale: clean(item.rationale) })),
|
|
179
|
+
should: report.dossier.featureBacklog.should.map((item) => ({ ...item, feature: clean(item.feature), user_value: clean(item.user_value), rationale: clean(item.rationale) })),
|
|
180
|
+
later: report.dossier.featureBacklog.later.map((item) => ({ ...item, feature: clean(item.feature), user_value: clean(item.user_value), rationale: clean(item.rationale) })),
|
|
181
|
+
wont: report.dossier.featureBacklog.wont.map((item) => ({ feature: clean(item.feature), reason: clean(item.reason) })),
|
|
182
|
+
} : undefined;
|
|
183
|
+
const cleanPlan = report.dossier.implementationPlan ? {
|
|
184
|
+
milestones: report.dossier.implementationPlan.milestones.map((item) => ({
|
|
185
|
+
...item,
|
|
186
|
+
timebox: clean(item.timebox),
|
|
187
|
+
outcome: clean(item.outcome),
|
|
188
|
+
tasks: item.tasks.map(clean),
|
|
189
|
+
acceptance_test: clean(item.acceptance_test),
|
|
190
|
+
})),
|
|
191
|
+
} : undefined;
|
|
192
|
+
const snapshot = isDecisionSnapshotRelevant(report.decisionSnapshot) ? {
|
|
193
|
+
decisiveNumbers: report.decisionSnapshot.decisiveNumbers.map((item) => ({ label: clean(item.label), value: clean(item.value), meaning: clean(item.meaning) })),
|
|
194
|
+
...(report.decisionSnapshot.payback ? { payback: {
|
|
195
|
+
status: report.decisionSnapshot.payback.status,
|
|
196
|
+
result: clean(report.decisionSnapshot.payback.result),
|
|
197
|
+
basis: clean(report.decisionSnapshot.payback.basis),
|
|
198
|
+
} } : {}),
|
|
199
|
+
options: report.decisionSnapshot.options.map((item) => ({
|
|
200
|
+
label: clean(item.label), commitment: clean(item.commitment), commitmentKind: item.commitmentKind, tradeoff: clean(item.tradeoff),
|
|
201
|
+
})),
|
|
202
|
+
...(report.decisionSnapshot.tripwire ? { tripwire: {
|
|
203
|
+
metric: clean(report.decisionSnapshot.tripwire.metric),
|
|
204
|
+
threshold: clean(report.decisionSnapshot.tripwire.threshold),
|
|
205
|
+
decisionRule: clean(report.decisionSnapshot.tripwire.decisionRule),
|
|
206
|
+
} } : {}),
|
|
207
|
+
} : undefined;
|
|
208
|
+
return {
|
|
209
|
+
headline: clean(brief.headline),
|
|
210
|
+
bottomLine: clean(brief.bottom_line),
|
|
211
|
+
sections: brief.sections.map((section) => ({ heading: clean(section.heading), summary: clean(section.summary), bullets: section.bullets.map(clean) })),
|
|
212
|
+
featureBacklog: cleanBacklog,
|
|
213
|
+
implementationPlan: cleanPlan,
|
|
214
|
+
caveats: brief.caveats.map(clean),
|
|
215
|
+
warnings: MATERIAL_WARNING_ORDER.filter((flag) => report.flags.includes(flag)).map((flag) => ({ flag, message: READER_WARNING[flag] })),
|
|
216
|
+
notices: report.flags.includes('source_fallback_search')
|
|
217
|
+
? [{ flag: 'source_fallback_search', message: 'Search attempted: a supplied page could not be read directly, so the source-investigation scout searched for accessible sources.' }]
|
|
218
|
+
: [],
|
|
219
|
+
snapshot,
|
|
220
|
+
sources,
|
|
221
|
+
nextStep: clean(brief.next_step),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function renderReaderBriefMarkdown(report) {
|
|
225
|
+
const projection = buildReaderProjection(report);
|
|
226
|
+
const L = [`# ${projection.headline}`, '', projection.bottomLine, ''];
|
|
227
|
+
for (const warning of projection.warnings)
|
|
228
|
+
L.push(`> ⚠ ${warning.message}`, '');
|
|
229
|
+
for (const notice of projection.notices)
|
|
230
|
+
L.push(`> ℹ ${notice.message}`, '');
|
|
231
|
+
if (projection.snapshot) {
|
|
232
|
+
L.push('## Decision numbers', '', '### Decisive numbers', '', '| Metric | Value | What it means |', '|---|---:|---|');
|
|
233
|
+
for (const item of projection.snapshot.decisiveNumbers)
|
|
234
|
+
L.push(`| ${cell(item.label)} | ${cell(item.value)} | ${cell(item.meaning)} |`);
|
|
235
|
+
if (projection.snapshot.payback) {
|
|
236
|
+
L.push('', `**Payback — ${projection.snapshot.payback.status.replaceAll('_', ' ')}:** ${projection.snapshot.payback.result}`);
|
|
237
|
+
L.push(`Basis: ${projection.snapshot.payback.basis}`, '');
|
|
238
|
+
}
|
|
239
|
+
else
|
|
240
|
+
L.push('');
|
|
241
|
+
L.push('### Options at a glance', '', '| Path | Commitment | Basis | Trade-off |', '|---|---:|---|---|');
|
|
242
|
+
for (const option of projection.snapshot.options) {
|
|
243
|
+
L.push(`| ${cell(option.label)} | ${cell(option.commitment)} | ${option.commitmentKind.replace('_', ' ')} | ${cell(option.tradeoff)} |`);
|
|
244
|
+
}
|
|
245
|
+
if (projection.snapshot.tripwire) {
|
|
246
|
+
const tripwire = projection.snapshot.tripwire;
|
|
247
|
+
L.push('', '### Go/no-go tripwire', '', `**${tripwire.metric}: ${tripwire.threshold}** — ${tripwire.decisionRule}`, '');
|
|
248
|
+
}
|
|
249
|
+
else
|
|
250
|
+
L.push('');
|
|
251
|
+
}
|
|
252
|
+
for (const section of projection.sections) {
|
|
253
|
+
L.push(`## ${section.heading}`, '', section.summary, '');
|
|
254
|
+
for (const bullet of section.bullets)
|
|
255
|
+
L.push(`- ${bullet}`);
|
|
256
|
+
if (section.bullets.length)
|
|
257
|
+
L.push('');
|
|
258
|
+
}
|
|
259
|
+
if (projection.featureBacklog) {
|
|
260
|
+
L.push('## Feature priorities', '', '| Priority | Feature | User value | Why now | Effort |', '|---|---|---|---|---|');
|
|
261
|
+
for (const [priority, items] of [
|
|
262
|
+
['MUST', projection.featureBacklog.must],
|
|
263
|
+
['SHOULD', projection.featureBacklog.should],
|
|
264
|
+
['LATER', projection.featureBacklog.later],
|
|
265
|
+
]) {
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
L.push(`| ${priority} | ${cell(item.feature)} | ${cell(item.user_value)} | ${cell(item.rationale)} | ${item.effort} |`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (projection.featureBacklog.wont.length) {
|
|
271
|
+
L.push('', '**Not in this scope**', '');
|
|
272
|
+
for (const item of projection.featureBacklog.wont)
|
|
273
|
+
L.push(`- **${item.feature}:** ${item.reason}`);
|
|
274
|
+
}
|
|
275
|
+
L.push('');
|
|
276
|
+
}
|
|
277
|
+
if (projection.implementationPlan) {
|
|
278
|
+
L.push('## Build plan', '', '| # | Timebox | Outcome | Work | Done when |', '|---|---|---|---|---|');
|
|
279
|
+
for (const milestone of projection.implementationPlan.milestones) {
|
|
280
|
+
L.push(`| ${milestone.order} | ${cell(milestone.timebox)} | ${cell(milestone.outcome)} | ${cell(milestone.tasks.join('; '))} | ${cell(milestone.acceptance_test)} |`);
|
|
281
|
+
}
|
|
282
|
+
L.push('');
|
|
283
|
+
}
|
|
284
|
+
if (projection.caveats.length) {
|
|
285
|
+
L.push('## Top caveats', '');
|
|
286
|
+
for (const caveat of projection.caveats)
|
|
287
|
+
L.push(`- ${caveat}`);
|
|
288
|
+
L.push('');
|
|
289
|
+
}
|
|
290
|
+
if (projection.sources.length) {
|
|
291
|
+
L.push('## Sources', '');
|
|
292
|
+
for (const source of projection.sources) {
|
|
293
|
+
const citationScope = source.citedFor.length ? ` — Cited for: ${source.citedFor.join('; ')}` : '';
|
|
294
|
+
L.push(`${source.url ? `- [${source.label.replace(/[\[\]]/g, '')}](${source.url})` : `- ${source.label}`}${citationScope}`);
|
|
295
|
+
}
|
|
296
|
+
L.push('');
|
|
297
|
+
}
|
|
298
|
+
L.push('## Next step', '', projection.nextStep, '');
|
|
299
|
+
const legacy = renderLegacyDecisionDossierMarkdown(report).split('\n').slice(2).join('\n');
|
|
300
|
+
L.push('## Council audit', '', '<details>', '<summary>Show the council reasoning, evidence ledger, dissent, and run receipt</summary>', '', legacy, '', '</details>', '');
|
|
301
|
+
return L.join('\n');
|
|
302
|
+
}
|
|
303
|
+
/** Pre-v5 deterministic report, retained as the replay fallback and the collapsed v5 audit body. */
|
|
304
|
+
function renderLegacyDecisionDossierMarkdown(report) {
|
|
61
305
|
const { dossier } = report;
|
|
306
|
+
const labelFor = claimLookup(report);
|
|
62
307
|
const keyFindings = report.keyFindings?.length ? report.keyFindings : [dossier.recommendation.reason];
|
|
63
308
|
const criticalUnknowns = report.criticalUnknowns?.length ? report.criticalUnknowns : report.openQuestions.slice(0, 3);
|
|
64
309
|
const coverage = report.confidenceBreakdown.verificationCoverage;
|
|
310
|
+
const allConditions = [...new Set(dossier.recommendation.conditions.map((condition) => stripReaderClaimIds(condition.text, labelFor)))];
|
|
311
|
+
const conditions = allConditions.slice(0, 4);
|
|
312
|
+
const verdictClaimId = report.claims.find((claim) => /^Verdict:/i.test(claim.text))?.id;
|
|
313
|
+
const seenQuestions = new Set();
|
|
314
|
+
const openQuestions = report.openQuestions.filter((question) => {
|
|
315
|
+
const key = question.slice(0, 60).toLowerCase();
|
|
316
|
+
if (seenQuestions.has(key))
|
|
317
|
+
return false;
|
|
318
|
+
seenQuestions.add(key);
|
|
319
|
+
return true;
|
|
320
|
+
});
|
|
65
321
|
const L = [report.mode === 'quick' ? '# Single-Model Decision Report' : '# Multi-Model Decision Report', ''];
|
|
66
322
|
L.push('## 1. Decision', '', ...degradation(report, ['synthesis_suspect']));
|
|
67
323
|
if (report.decisionSnapshot) {
|
|
@@ -78,7 +334,9 @@ export function renderDecisionDossierMarkdown(report) {
|
|
|
78
334
|
L.push('');
|
|
79
335
|
}
|
|
80
336
|
L.push(`**Recommendation:** ${dossier.recommendation.summary}`, '');
|
|
81
|
-
L.push(
|
|
337
|
+
L.push(report.confidenceBreakdown.verificationScope === 'FACTUAL'
|
|
338
|
+
? `**Evidence coverage:** ${coverageLabel(coverage)} — ${pct(coverage)} of checkable factual claims independently verified; design judgments are adjudicated by the chair, not verified.`
|
|
339
|
+
: `**Evidence coverage:** ${coverageLabel(coverage)} — ${pct(coverage)} of load-bearing claims independently verified.`);
|
|
82
340
|
L.push(coverage < 0.5
|
|
83
341
|
? '> Low coverage means important inputs remain unchecked; it is not a probability that the recommendation is correct.'
|
|
84
342
|
: '> Evidence coverage measures independent checking; it is not a probability that the recommendation is correct.', '');
|
|
@@ -107,21 +365,10 @@ export function renderDecisionDossierMarkdown(report) {
|
|
|
107
365
|
L.push('- None recorded.');
|
|
108
366
|
L.push('', `**Critical warning:** ${report.verdict.criticalWarning ?? 'None recorded.'}`);
|
|
109
367
|
L.push(`**Council read:** ${councilRead(report)}`);
|
|
110
|
-
L.push(
|
|
368
|
+
L.push('');
|
|
111
369
|
if (!dossier.recommendation.claimIds.length)
|
|
112
370
|
L.push('> ⚠ DEGRADED: recommendation has no stored graph anchor.', '');
|
|
113
|
-
L.push('
|
|
114
|
-
L.push(`- Decision state: ${dossier.recommendation.status}`);
|
|
115
|
-
L.push(`- Structural score: ${report.confidenceBreakdown.score}/100 (${report.confidenceBreakdown.label}); heuristic, not benchmark-calibrated.`);
|
|
116
|
-
L.push(`- Basis: verification ${pct(coverage)} · independent convergence ${pct(report.confidenceBreakdown.independentConvergence)} · evidence quality ${pct(report.confidenceBreakdown.evidenceQuality)} · stability ${pct(report.confidenceBreakdown.stability)} · critical-risk penalty −${report.confidenceBreakdown.criticalRiskPenalty}`);
|
|
117
|
-
if (dossier.recommendation.conditions.length) {
|
|
118
|
-
L.push('', 'Conditions:');
|
|
119
|
-
for (const condition of dossier.recommendation.conditions) {
|
|
120
|
-
L.push(`- ${condition.text}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
L.push('', '</details>', '');
|
|
124
|
-
L.push('## 2. Action plan', '', ...degradation(report, ['plan_fallback', 'plan_skipped']));
|
|
371
|
+
L.push('## 2. Deliverables and action plan', '', ...degradation(report, ['plan_fallback', 'plan_skipped']));
|
|
125
372
|
if ((dossier.missingRequestedOutputs ?? []).length) {
|
|
126
373
|
L.push(`> ⚠ DEGRADED: requested output missing: ${dossier.missingRequestedOutputs.join(', ')}`, '');
|
|
127
374
|
}
|
|
@@ -163,66 +410,58 @@ export function renderDecisionDossierMarkdown(report) {
|
|
|
163
410
|
L.push('No executable experiment was produced.');
|
|
164
411
|
L.push('');
|
|
165
412
|
L.push('## 3. Why this decision', '');
|
|
166
|
-
|
|
167
|
-
L.push(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
413
|
+
for (const point of keyFindings)
|
|
414
|
+
L.push(`- ${stripReaderClaimIds(point, labelFor)}`);
|
|
415
|
+
if (!keyFindings.length)
|
|
416
|
+
L.push('No chair reasoning was recorded.');
|
|
417
|
+
if (conditions.length) {
|
|
418
|
+
L.push('', '### Conditions', '');
|
|
419
|
+
for (const condition of conditions)
|
|
420
|
+
L.push(`- ${condition}`);
|
|
421
|
+
if (allConditions.length > conditions.length)
|
|
422
|
+
L.push('', `${allConditions.length - conditions.length} additional condition${allConditions.length - conditions.length === 1 ? '' : 's'} remain in the stored audit JSON.`);
|
|
171
423
|
}
|
|
172
|
-
else
|
|
173
|
-
L.push('No graph-anchored decision chain was recorded.');
|
|
174
424
|
L.push('');
|
|
175
425
|
L.push('## 4. What could change the decision', '', '### Decision-sensitive facts', '');
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
426
|
+
const decisiveFacts = dossier.sensitivity.filter((item) => item.sensitivity === 'DECISIVE').slice(0, 5);
|
|
427
|
+
if (decisiveFacts.length) {
|
|
428
|
+
L.push('| Fact | If false | What settles it |', '|---|---|---|');
|
|
429
|
+
for (const item of decisiveFacts) {
|
|
430
|
+
L.push(`| ${cell(claimShortLabel(item.fact))} | ${item.impactIfFalse} | ${cell(item.whatWouldChangeIt)} |`);
|
|
180
431
|
}
|
|
181
432
|
}
|
|
182
433
|
else
|
|
183
434
|
L.push('No verdict-sensitive graph node was recorded.');
|
|
184
435
|
L.push('', '### Strongest counter-case', '');
|
|
185
436
|
if (dossier.counterCase.available) {
|
|
186
|
-
|
|
437
|
+
const counterIds = dossier.counterCase.claimIds.filter((id) => id !== verdictClaimId);
|
|
438
|
+
L.push(dossier.counterCase.reasoning, '', `Evidence behind this counter-case: ${readerClaimRefs(report, counterIds)}`);
|
|
187
439
|
}
|
|
188
440
|
else
|
|
189
441
|
L.push(`> ⚠ DEGRADED: ${dossier.counterCase.reasoning}`);
|
|
190
442
|
L.push('');
|
|
191
|
-
L.push('## 5.
|
|
192
|
-
if (dossier.evidence.length) {
|
|
193
|
-
L.push('| Evidence ID | Source | Date | Freshness | Verification | Linked claims |', '|---|---|---|---|---|---|');
|
|
194
|
-
for (const evidence of dossier.evidence) {
|
|
195
|
-
L.push(`| ${evidence.id} | ${cell(evidence.source)} (${evidence.sourceKind}) | ${evidence.date} | ${evidence.freshness} | ${evidence.verificationStatus} | ${cell(readerClaimRefs(report, evidence.claimIds))} |`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else
|
|
199
|
-
L.push('No evidence cards were stored.');
|
|
200
|
-
L.push('');
|
|
201
|
-
L.push('## 6. Risks, gaps, and open questions', '', '### Risks', '');
|
|
443
|
+
L.push('## 5. Risks and open questions', '', ...degradation(report, ['verification_skipped', 'research_ungrounded']), '### Risks', '');
|
|
202
444
|
if (report.risks.length) {
|
|
445
|
+
const shownRisks = report.risks.slice(0, 8);
|
|
203
446
|
L.push('| Risk | Severity |', '|---|---|');
|
|
204
|
-
for (const risk of
|
|
447
|
+
for (const risk of shownRisks)
|
|
205
448
|
L.push(`| ${cell(risk.risk)} | ${risk.severity} |`);
|
|
449
|
+
if (report.risks.length > shownRisks.length)
|
|
450
|
+
L.push('', `${report.risks.length - shownRisks.length} lower-severity items — more in the technical audit (full list in the stored JSON).`);
|
|
206
451
|
}
|
|
207
452
|
else
|
|
208
453
|
L.push('No material risk was recorded.');
|
|
209
|
-
L.push('', '### Coverage', '');
|
|
210
|
-
if (dossier.coverage.length) {
|
|
211
|
-
L.push('| Dimension | Status | Related claims |', '|---|---|---|');
|
|
212
|
-
for (const item of dossier.coverage)
|
|
213
|
-
L.push(`| ${cell(item.label)} | ${item.status} | ${cell(readerClaimRefs(report, item.claimIds))} |`);
|
|
214
|
-
}
|
|
215
|
-
else
|
|
216
|
-
L.push('No rubric coverage ledger was recorded.');
|
|
217
454
|
L.push('', '### Open questions', '');
|
|
218
|
-
if (
|
|
219
|
-
for (const question of
|
|
455
|
+
if (openQuestions.length) {
|
|
456
|
+
for (const question of openQuestions.slice(0, 5))
|
|
220
457
|
L.push(`- ${question}`);
|
|
458
|
+
if (openQuestions.length > 5)
|
|
459
|
+
L.push('', `Showing 5 of ${openQuestions.length} — the rest are in the technical audit.`);
|
|
221
460
|
}
|
|
222
461
|
else
|
|
223
462
|
L.push('No verdict-flipping open question was recorded.');
|
|
224
463
|
L.push('');
|
|
225
|
-
L.push('##
|
|
464
|
+
L.push('## 6. Disagreement and dissent', '', ...degradation(report, ['single_model', 'low_diversity']));
|
|
226
465
|
if (report.disagreements.length) {
|
|
227
466
|
for (const disagreement of report.disagreements) {
|
|
228
467
|
L.push(`- **${disagreement.topic}** — ${disagreement.status}; ${disagreement.ruling}.`);
|
|
@@ -253,30 +492,61 @@ export function renderDecisionDossierMarkdown(report) {
|
|
|
253
492
|
for (const item of report.minority.uniqueOppositions)
|
|
254
493
|
L.push(`- ${providerName(item.provider)} uniquely opposed: ${item.proposition}`);
|
|
255
494
|
L.push(`- Decision-blocking status: ${report.minority.blocksDecision}`, '');
|
|
256
|
-
L.push('##
|
|
257
|
-
if (dossier.
|
|
258
|
-
|
|
259
|
-
for (const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
495
|
+
L.push('## 7. What the council added', '', ...degradation(report, ['weak_seat']));
|
|
496
|
+
if (dossier.seatStats) {
|
|
497
|
+
const weakSeat = dossier.seatStats.some((seat) => seat.positions < 3 || seat.evidenced / seat.positions < 0.5);
|
|
498
|
+
for (const seat of dossier.seatStats) {
|
|
499
|
+
const weak = seat.positions < 3 || seat.evidenced / seat.positions < 0.5;
|
|
500
|
+
L.push(`- ${providerName(seat.provider)}: ${seat.positions} position${seat.positions === 1 ? '' : 's'}, ${seat.evidenced} with evidence${weak ? ' — weak seat this run.' : '.'}`);
|
|
501
|
+
}
|
|
502
|
+
for (const chair of report.models.filter((model) => model.roles.includes('judge') && !dossier.seatStats.some((seat) => seat.provider === model.provider))) {
|
|
503
|
+
L.push(`- ${chair.name}: chaired the decision and authored no scout claims (clean adjudication).`);
|
|
504
|
+
}
|
|
505
|
+
if (dossier.sharedConcerns.length) {
|
|
506
|
+
L.push('', 'Shared concerns:');
|
|
507
|
+
for (const item of dossier.sharedConcerns)
|
|
508
|
+
L.push(`- ${item.text} — ${item.evidenceStatus}; ${item.providerIds.map(providerName).join(', ')}.`);
|
|
509
|
+
}
|
|
510
|
+
L.push('', report.disagreements.length
|
|
511
|
+
? councilRead(report)
|
|
512
|
+
: 'No genuine disagreement survived to the chair.');
|
|
513
|
+
if (weakSeat || coverage < 0.5)
|
|
514
|
+
L.push('Convergence with weak verification lowers confidence; it does not raise it.');
|
|
515
|
+
const verifiedContributions = dossier.contributions.filter((item) => item.verifiedUniqueClaimIds.length > 0);
|
|
516
|
+
if (verifiedContributions.length) {
|
|
517
|
+
L.push('', '### Verified unique contributions', '', ...degradation(report, ['verification_skipped', 'single_model', 'low_diversity']));
|
|
518
|
+
L.push('Only unique claims that survived independent verification receive credit.', '');
|
|
519
|
+
L.push('| Provider | Verified unique contributions | Count |', '|---|---|---|');
|
|
520
|
+
for (const contribution of verifiedContributions) {
|
|
521
|
+
L.push(`| ${contribution.name} | ${cell(readerClaimRefs(report, contribution.verifiedUniqueClaimIds))} | ${contribution.verifiedUniqueClaimIds.length} |`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
269
524
|
}
|
|
270
|
-
else
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
525
|
+
else {
|
|
526
|
+
if (dossier.sharedConcerns.length) {
|
|
527
|
+
L.push('Shared concerns:');
|
|
528
|
+
for (const item of dossier.sharedConcerns)
|
|
529
|
+
L.push(`- ${item.text} — ${item.evidenceStatus}; ${item.providerIds.map(providerName).join(', ')}.`);
|
|
530
|
+
}
|
|
531
|
+
else
|
|
532
|
+
L.push('Shared concerns: none recorded.');
|
|
533
|
+
L.push('');
|
|
534
|
+
if (dossier.uniqueSupportedInsights.length) {
|
|
535
|
+
L.push('Unique supported insights:');
|
|
536
|
+
for (const item of dossier.uniqueSupportedInsights)
|
|
537
|
+
L.push(`- ${item.text} — ${providerName(item.providerId)}; ${item.verificationStatus}.`);
|
|
538
|
+
}
|
|
539
|
+
else
|
|
540
|
+
L.push('Unique supported insights: none recorded.');
|
|
541
|
+
L.push('', '### Verified unique contributions', '', ...degradation(report, ['verification_skipped', 'single_model', 'low_diversity']));
|
|
542
|
+
L.push('Only unique claims that survived independent verification receive credit.', '');
|
|
543
|
+
L.push('| Provider | Verified unique contributions | Count |', '|---|---|---|');
|
|
544
|
+
for (const contribution of dossier.contributions) {
|
|
545
|
+
L.push(`| ${contribution.name} | ${cell(readerClaimRefs(report, contribution.verifiedUniqueClaimIds))} | ${contribution.verifiedUniqueClaimIds.length} |`);
|
|
546
|
+
}
|
|
277
547
|
}
|
|
278
548
|
L.push('');
|
|
279
|
-
L.push('##
|
|
549
|
+
L.push('## 8. Run details', '');
|
|
280
550
|
L.push(`- Report ID: \`${report.reportId}\``);
|
|
281
551
|
L.push(`- Generated: ${report.generatedAt}`);
|
|
282
552
|
L.push(`- Original task: ${report.task.original}`);
|
|
@@ -292,10 +562,65 @@ export function renderDecisionDossierMarkdown(report) {
|
|
|
292
562
|
L.push(`- By provider: ${Object.entries(report.receipt.byProvider).map(([provider, count]) => `${providerName(provider)} ${count}`).join(', ') || 'none'}`);
|
|
293
563
|
L.push(`- Recorded model time: ${(report.receipt.modelTimeMs / 1000).toFixed(1)}s`);
|
|
294
564
|
L.push(`- Degradation flags: ${report.flags.join(', ') || 'none'}`, '');
|
|
565
|
+
// This pass runs over already-built (cell-escaped) table rows, so injected labels must be cell-escaped too.
|
|
295
566
|
for (let index = 0; index < L.length; index++)
|
|
296
|
-
L[index] = stripReaderClaimIds(L[index]);
|
|
297
|
-
L.push('##
|
|
298
|
-
L.push('<details>', '<summary>
|
|
567
|
+
L[index] = stripReaderClaimIds(L[index], labelFor, cell);
|
|
568
|
+
L.push('## 9. Technical audit', '');
|
|
569
|
+
L.push('<details>', '<summary>Evidence, coverage, claims, and graph events</summary>', '');
|
|
570
|
+
L.push('### Decision confidence', '');
|
|
571
|
+
L.push(`- Decision state: ${dossier.recommendation.status}`);
|
|
572
|
+
L.push(`- Structural score: ${report.confidenceBreakdown.score}/100 (${report.confidenceBreakdown.label}); heuristic, not benchmark-calibrated.`);
|
|
573
|
+
L.push(`- Basis: verification ${pct(coverage)} · independent convergence ${pct(report.confidenceBreakdown.independentConvergence)} · evidence quality ${pct(report.confidenceBreakdown.evidenceQuality)} · stability ${pct(report.confidenceBreakdown.stability)} · critical-risk penalty −${report.confidenceBreakdown.criticalRiskPenalty}`, '');
|
|
574
|
+
L.push('### Full claim chain', '');
|
|
575
|
+
if (dossier.claimChain.length) {
|
|
576
|
+
L.push('| Claim | Ruling | Evidence status | Depends on |', '|---|---|---|---|');
|
|
577
|
+
for (const claim of dossier.claimChain) {
|
|
578
|
+
L.push(`| ${cell(claim.text)} | ${claim.ruling} | ${claim.evidenceStatus} | ${cell(readerClaimRefs(report, claim.dependsOn))} |`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else
|
|
582
|
+
L.push('No graph-anchored decision chain was recorded.');
|
|
583
|
+
L.push('', '### Full decision-sensitivity ledger', '');
|
|
584
|
+
if (dossier.sensitivity.length) {
|
|
585
|
+
L.push('| Fact | Sensitivity | If false | What would change it | Linked claims |', '|---|---|---|---|---|');
|
|
586
|
+
for (const item of dossier.sensitivity) {
|
|
587
|
+
L.push(`| ${cell(item.fact)} | ${item.sensitivity} | ${item.impactIfFalse} | ${cell(item.whatWouldChangeIt)} | ${cell(readerClaimRefs(report, item.linkedClaimIds))} |`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else
|
|
591
|
+
L.push('No verdict-sensitive graph node was recorded.');
|
|
592
|
+
L.push('', '### Evidence and verification', '');
|
|
593
|
+
if (dossier.evidence.length) {
|
|
594
|
+
L.push('| Evidence ID | Source | Date | Freshness | Verification | Linked claims |', '|---|---|---|---|---|---|');
|
|
595
|
+
for (const evidence of dossier.evidence) {
|
|
596
|
+
L.push(`| ${evidence.id} | ${cell(evidence.source)} (${evidence.sourceKind}) | ${evidence.date} | ${evidence.freshness} | ${evidence.verificationStatus} | ${cell(readerClaimRefs(report, evidence.claimIds))} |`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
else
|
|
600
|
+
L.push('No evidence cards were stored.');
|
|
601
|
+
L.push('', '### Coverage ledger', '');
|
|
602
|
+
if (dossier.coverage.length) {
|
|
603
|
+
L.push('| Dimension | Status | Related claims |', '|---|---|---|');
|
|
604
|
+
for (const item of dossier.coverage)
|
|
605
|
+
L.push(`| ${cell(item.label)} | ${item.status} | ${cell(readerClaimRefs(report, item.claimIds))} |`);
|
|
606
|
+
}
|
|
607
|
+
else
|
|
608
|
+
L.push('No rubric coverage ledger was recorded.');
|
|
609
|
+
L.push('', '### Full risk ledger', '');
|
|
610
|
+
if (report.risks.length) {
|
|
611
|
+
L.push('| Risk | Severity |', '|---|---|');
|
|
612
|
+
for (const risk of report.risks)
|
|
613
|
+
L.push(`| ${cell(risk.risk)} | ${risk.severity} |`);
|
|
614
|
+
}
|
|
615
|
+
else
|
|
616
|
+
L.push('No material risk was recorded.');
|
|
617
|
+
L.push('', '### Full open-question ledger', '');
|
|
618
|
+
if (report.openQuestions.length)
|
|
619
|
+
for (const question of report.openQuestions)
|
|
620
|
+
L.push(`- ${question}`);
|
|
621
|
+
else
|
|
622
|
+
L.push('No verdict-flipping open question was recorded.');
|
|
623
|
+
L.push('', '### Original submissions and graph events', '');
|
|
299
624
|
for (const submission of dossier.technical.submissions) {
|
|
300
625
|
L.push(`- **${submission.name}:** ${submission.strongestVersion} (${refs(submission.positionIds)})`);
|
|
301
626
|
}
|
|
@@ -318,3 +643,7 @@ export function renderDecisionDossierMarkdown(report) {
|
|
|
318
643
|
L.push('', '</details>', '');
|
|
319
644
|
return L.join('\n');
|
|
320
645
|
}
|
|
646
|
+
/** Canonical Markdown. HTML and Copy-Markdown consume the same persisted reader brief. */
|
|
647
|
+
export function renderDecisionDossierMarkdown(report) {
|
|
648
|
+
return report.dossier.readerBrief ? renderReaderBriefMarkdown(report) : renderLegacyDecisionDossierMarkdown(report);
|
|
649
|
+
}
|