aiki-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -7
- package/README.md +109 -30
- package/dist/bench/arms.js +10 -9
- package/dist/bench/harness.js +13 -10
- package/dist/bench/idea-lane-rotation.js +237 -0
- package/dist/bench/idea-v3-bench.js +506 -0
- package/dist/bench/idea-v3-rating.js +582 -0
- package/dist/bench/results.js +10 -3
- package/dist/bench/scoring/decision-insights.js +112 -0
- package/dist/bench/scoring/seeded-bugs.js +4 -0
- package/dist/cli/bench.js +180 -3
- package/dist/cli/doctor.js +56 -24
- package/dist/cli/index.js +32 -7
- package/dist/cli/resolve.js +7 -6
- package/dist/cli/resume.js +18 -0
- package/dist/cli/run.js +63 -8
- package/dist/cli/version.js +8 -0
- package/dist/council/view.js +378 -109
- package/dist/orchestration/calculations.js +97 -0
- package/dist/orchestration/context.js +37 -9
- package/dist/orchestration/decision-dossier.js +262 -0
- package/dist/orchestration/decision-graph.js +256 -0
- package/dist/orchestration/engine.js +5 -2
- package/dist/orchestration/evidence-pack.js +46 -0
- package/dist/orchestration/idea-lanes.js +20 -0
- package/dist/orchestration/jsonStage.js +72 -0
- package/dist/orchestration/legacy-idea-adapter.js +102 -0
- package/dist/orchestration/modes.js +33 -0
- package/dist/orchestration/preflight.js +183 -0
- package/dist/orchestration/quick-analysis.js +81 -0
- package/dist/orchestration/stages/cr-ladder.js +80 -0
- package/dist/orchestration/stages/s10-render.js +562 -150
- package/dist/orchestration/stages/s4-analyze.js +12 -7
- package/dist/orchestration/stages/s5-drift.js +9 -9
- package/dist/orchestration/stages/s6-positions.js +10 -0
- package/dist/orchestration/stages/s7-decision-graph.js +76 -0
- package/dist/orchestration/stages/s8-verify.js +153 -46
- package/dist/orchestration/stages/s8b-rebuttal.js +208 -0
- package/dist/orchestration/stages/s9-judge.js +329 -108
- package/dist/orchestration/stages/s9b-plan.js +85 -75
- package/dist/providers/codex.js +2 -1
- package/dist/providers/spawn.js +5 -0
- package/dist/schemas/index.js +572 -13
- package/dist/skills/idea-refinement/analyst.md +18 -14
- package/dist/skills/idea-refinement/economics-delivery.md +7 -0
- package/dist/skills/idea-refinement/market-adoption.md +7 -0
- package/dist/storage/runs.js +11 -4
- package/dist/tui/app.js +37 -13
- package/dist/tui/format.js +4 -5
- package/dist/tui/smart-entry.js +17 -1
- package/dist/tui/timeline.js +2 -4
- package/dist/workflows/code-review.js +4 -2
- package/dist/workflows/idea-refinement.js +110 -46
- package/package.json +12 -4
- package/dist/orchestration/stages/s0-grill.js +0 -79
- package/dist/orchestration/stages/s1-intent.js +0 -25
- package/dist/orchestration/stages/s2-misread.js +0 -76
- package/dist/orchestration/stages/s3-prompts.js +0 -55
- package/dist/orchestration/stages/s6-claims.js +0 -56
- package/dist/orchestration/stages/s7-disagreement.js +0 -134
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const UNIT_ALIASES = {
|
|
2
|
+
ratios: '1', ratio: '1', percentage: '1', percent: '1', '%': '1', dimensionless: '1',
|
|
3
|
+
visitors: 'visitor', orders: 'order', customers: 'customer', users: 'user',
|
|
4
|
+
months: 'month', years: 'year', units: 'unit', items: 'item',
|
|
5
|
+
};
|
|
6
|
+
/** Keep unit meaning atomic. Model ledgers sometimes decorate currency tokens ("INR revenue") or
|
|
7
|
+
* use ordinary plurals; those are the same physical dimensions and must cancel algebraically. */
|
|
8
|
+
function canonicalUnitToken(token) {
|
|
9
|
+
const normalized = token.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
10
|
+
const currency = normalized.match(/^(inr|usd|eur|gbp|jpy|aud|cad|cny|sgd|aed)(?:\b|$)/)?.[1];
|
|
11
|
+
if (currency)
|
|
12
|
+
return currency;
|
|
13
|
+
return UNIT_ALIASES[normalized] ?? normalized;
|
|
14
|
+
}
|
|
15
|
+
function unitPowers(unit) {
|
|
16
|
+
const powers = new Map();
|
|
17
|
+
let sign = 1;
|
|
18
|
+
for (const token of unit.split(/([*/])/).map((part) => part.trim()).filter(Boolean)) {
|
|
19
|
+
if (token === '*') {
|
|
20
|
+
sign = 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (token === '/') {
|
|
24
|
+
sign = -1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const canonical = canonicalUnitToken(token);
|
|
28
|
+
if (canonical !== '1')
|
|
29
|
+
powers.set(canonical, (powers.get(canonical) ?? 0) + sign);
|
|
30
|
+
}
|
|
31
|
+
return new Map([...powers].filter(([, power]) => power !== 0));
|
|
32
|
+
}
|
|
33
|
+
function sameUnits(left, right) {
|
|
34
|
+
return left.size === right.size && [...left].every(([unit, power]) => right.get(unit) === power);
|
|
35
|
+
}
|
|
36
|
+
function formatUnits(powers) {
|
|
37
|
+
const numerator = [];
|
|
38
|
+
const denominator = [];
|
|
39
|
+
for (const [unit, power] of [...powers].sort(([a], [b]) => a.localeCompare(b))) {
|
|
40
|
+
for (let i = 0; i < Math.abs(power); i++)
|
|
41
|
+
(power > 0 ? numerator : denominator).push(unit);
|
|
42
|
+
}
|
|
43
|
+
const top = numerator.join('*') || '1';
|
|
44
|
+
return denominator.length ? `${top}/${denominator.join('/')}` : top;
|
|
45
|
+
}
|
|
46
|
+
function resultUnit(operation, left, right) {
|
|
47
|
+
const leftPowers = unitPowers(left);
|
|
48
|
+
const rightPowers = unitPowers(right);
|
|
49
|
+
if (operation === 'ADD' || operation === 'SUBTRACT')
|
|
50
|
+
return sameUnits(leftPowers, rightPowers) ? formatUnits(leftPowers) : null;
|
|
51
|
+
const powers = new Map(leftPowers);
|
|
52
|
+
for (const [unit, power] of rightPowers) {
|
|
53
|
+
powers.set(unit, (powers.get(unit) ?? 0) + (operation === 'MULTIPLY' ? power : -power));
|
|
54
|
+
if (powers.get(unit) === 0)
|
|
55
|
+
powers.delete(unit);
|
|
56
|
+
}
|
|
57
|
+
return formatUnits(powers);
|
|
58
|
+
}
|
|
59
|
+
/** Recompute a declared ledger without eval/shell execution and report value/unit mismatches. */
|
|
60
|
+
export function evaluateCalculation(calculation) {
|
|
61
|
+
const values = new Map(calculation.inputs.map((input) => [input.id, { value: input.value, unit: input.unit }]));
|
|
62
|
+
const issues = [];
|
|
63
|
+
for (const step of calculation.steps) {
|
|
64
|
+
const left = values.get(step.left);
|
|
65
|
+
const right = values.get(step.right);
|
|
66
|
+
if (!left || !right) {
|
|
67
|
+
issues.push(`${step.id}: unknown operand reference`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (step.operation === 'DIVIDE' && right.value === 0) {
|
|
71
|
+
issues.push(`${step.id}: division by zero`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const expected = step.operation === 'ADD' ? left.value + right.value
|
|
75
|
+
: step.operation === 'SUBTRACT' ? left.value - right.value
|
|
76
|
+
: step.operation === 'MULTIPLY' ? left.value * right.value
|
|
77
|
+
: left.value / right.value;
|
|
78
|
+
const unit = resultUnit(step.operation, left.unit, right.unit);
|
|
79
|
+
if (!unit)
|
|
80
|
+
issues.push(`${step.id}: ${step.operation} requires matching units (${left.unit} vs ${right.unit})`);
|
|
81
|
+
else if (!sameUnits(unitPowers(step.unit), unitPowers(unit)))
|
|
82
|
+
issues.push(`${step.id}: unit ${step.unit} does not match ${unit}`);
|
|
83
|
+
if (Math.abs(step.result - expected) > 1e-9 * Math.max(1, Math.abs(expected))) {
|
|
84
|
+
issues.push(`${step.id}: result ${step.result} does not match ${expected}`);
|
|
85
|
+
}
|
|
86
|
+
values.set(step.id, { value: expected, unit: unit ?? step.unit });
|
|
87
|
+
}
|
|
88
|
+
if (!calculation.steps.some((step) => step.id === calculation.result_step)) {
|
|
89
|
+
issues.push(`result_step: unknown step ${calculation.result_step}`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
calculation_id: calculation.id,
|
|
93
|
+
claim_id: calculation.claim_id,
|
|
94
|
+
status: issues.length ? 'FAIL' : 'PASS',
|
|
95
|
+
issues,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -15,6 +15,7 @@ import { extractJson } from '../providers/adapter-core.js';
|
|
|
15
15
|
import { detect } from '../providers/detect.js';
|
|
16
16
|
import { probeFlags } from '../providers/probe.js';
|
|
17
17
|
import { replayKey } from '../storage/replay.js';
|
|
18
|
+
import { callCategory, defaultBudgetFor, IDEA_MODE_PLANS, isOptionalStage, LEGACY_DEFAULT_BUDGET } from './modes.js';
|
|
18
19
|
/** Bracket a stage call with the TUI's start/end events (no-op headless). A thrown StageError marks
|
|
19
20
|
* the row `failed` and re-propagates unchanged — the engine's failure handling is untouched. */
|
|
20
21
|
export async function runStage(ctx, id, fn) {
|
|
@@ -29,17 +30,16 @@ export async function runStage(ctx, id, fn) {
|
|
|
29
30
|
throw e;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// a live run spent 3 repairs and died at 13 before S9b. 18 = 12 + ~6 repair headroom so the validation
|
|
35
|
-
// plan actually renders; a true repair-storm still fails gracefully. Overridable via `--budget`/config.
|
|
33
|
+
/** Legacy/code-review fallback. Idea-refinement defaults are adaptive by explicit mode (R6). */
|
|
34
|
+
export const DEFAULT_BUDGET = LEGACY_DEFAULT_BUDGET;
|
|
36
35
|
// §19 was 10 min; raised to 20 (user-authorized 2026-07-06). Evidence: a real Opus judge (S9) on a
|
|
37
36
|
// 9-dispute idea ran ~360s and the whole run was ~14 min → the 10-min wall-clock aborted a legitimate run.
|
|
38
37
|
export const DEFAULT_DEADLINE_MS = 20 * 60 * 1000; // wall-clock cap
|
|
39
|
-
// §7.1 was 180s; raised to 300 (user-authorized 2026-07-06)
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
38
|
+
// §7.1 was 180s; raised to 300 (user-authorized 2026-07-06) for the deep-reasoning judge; raised to 900
|
|
39
|
+
// (2026-07-13) after the spawn timeout became actually enforced and killed a LEGITIMATE deep call: codex's
|
|
40
|
+
// S4 analysis of a hard build case ran ~10 min to a valid output (run 20260713-1341, 13:44→13:54). 900s
|
|
41
|
+
// per attempt covers observed deep work; the wall-clock deadline above remains the outer bound.
|
|
42
|
+
const DEFAULT_CALL_TIMEOUT_MS = 900_000;
|
|
43
43
|
export class BudgetExceeded extends Error {
|
|
44
44
|
constructor(limit) {
|
|
45
45
|
super(`call budget exhausted (limit ${limit})`);
|
|
@@ -68,12 +68,16 @@ export class StageError extends Error {
|
|
|
68
68
|
export class RunCtx {
|
|
69
69
|
runId;
|
|
70
70
|
workflow;
|
|
71
|
+
mode;
|
|
71
72
|
roles;
|
|
72
73
|
writer;
|
|
73
74
|
cwd;
|
|
74
75
|
events;
|
|
76
|
+
evidencePack;
|
|
75
77
|
budget;
|
|
76
78
|
calls = [];
|
|
79
|
+
/** Logical provider-call stages, including resume cache hits. Used by bounded protocol caps. */
|
|
80
|
+
attemptedStages = [];
|
|
77
81
|
/** §16 report-header flags accumulated by stages (S4 → low_diversity, S7 → low_diversity,
|
|
78
82
|
* S9 → synthesis_suspect). Folded into meta.json at finalize by `buildMeta`. */
|
|
79
83
|
flags = new Set();
|
|
@@ -87,11 +91,13 @@ export class RunCtx {
|
|
|
87
91
|
constructor(opts) {
|
|
88
92
|
this.runId = opts.runId;
|
|
89
93
|
this.workflow = opts.workflow;
|
|
94
|
+
this.mode = opts.mode ?? 'council';
|
|
90
95
|
this.roles = opts.roles;
|
|
91
96
|
this.writer = opts.writer;
|
|
92
97
|
this.cwd = opts.cwd;
|
|
93
98
|
this.events = opts.events;
|
|
94
|
-
this.
|
|
99
|
+
this.evidencePack = opts.evidencePack;
|
|
100
|
+
this.budget = { limit: opts.budget ?? defaultBudgetFor(opts.workflow, this.mode), used: 0 };
|
|
95
101
|
this.handles = new Map(opts.handles.map((h) => [h.id, h]));
|
|
96
102
|
this.signal = opts.signal;
|
|
97
103
|
this.deadlineMs = opts.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
@@ -125,12 +131,30 @@ export class RunCtx {
|
|
|
125
131
|
if (this.now() > this.deadlineAt)
|
|
126
132
|
throw new DeadlineExceeded(this.deadlineMs);
|
|
127
133
|
}
|
|
134
|
+
/** Calls optional stages may still spend after preserving this mode's required tail calls. */
|
|
135
|
+
optionalCallsRemaining() {
|
|
136
|
+
if (this.workflow !== 'idea-refinement')
|
|
137
|
+
return 0;
|
|
138
|
+
const plan = IDEA_MODE_PLANS[this.mode];
|
|
139
|
+
const logicalUsed = this.attemptedStages.filter(isOptionalStage).length;
|
|
140
|
+
const protocolRoom = Math.max(0, plan.optionalCalls - logicalUsed);
|
|
141
|
+
const budgetRoom = Math.max(0, this.budget.limit - this.budget.used - plan.reservedCalls);
|
|
142
|
+
return Math.min(protocolRoom, budgetRoom);
|
|
143
|
+
}
|
|
144
|
+
/** Actual-call receipt. Resume replay is free and therefore intentionally absent. */
|
|
145
|
+
receipt() {
|
|
146
|
+
const receipt = { discovery: 0, verification: 0, repair: 0, planning: 0 };
|
|
147
|
+
for (const call of this.calls)
|
|
148
|
+
receipt[call.category ?? callCategory(call.stage)]++;
|
|
149
|
+
return receipt;
|
|
150
|
+
}
|
|
128
151
|
/**
|
|
129
152
|
* Make one budgeted provider call. Decrements budget (throws `BudgetExceeded` if it would go
|
|
130
153
|
* over), records a `CallRecord`, and dumps the exact prompt + raw output under `raw/` (§15).
|
|
131
154
|
*/
|
|
132
155
|
async call(handle, req, stage) {
|
|
133
156
|
this.guard();
|
|
157
|
+
this.attemptedStages.push(stage);
|
|
134
158
|
const seq = ++this.seq;
|
|
135
159
|
await this.writer.writeRaw(`${stage}-${handle.id}-${seq}.prompt.txt`, req.prompt);
|
|
136
160
|
// Resume (V6.3): if this exact (provider, prompt) already succeeded in the run we're resuming,
|
|
@@ -150,6 +174,7 @@ export class RunCtx {
|
|
|
150
174
|
timeoutMs: req.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS,
|
|
151
175
|
expectJson: req.expectJson,
|
|
152
176
|
readOnly: true,
|
|
177
|
+
research: this.mode === 'research' && stage.startsWith('S4'),
|
|
153
178
|
signal: this.signal, // Ctrl+C kills the in-flight child (T8); undefined headless
|
|
154
179
|
}, handle.flags);
|
|
155
180
|
// Only REAL calls count toward the ledger/budget; a replayed call is free and already recorded in
|
|
@@ -157,6 +182,7 @@ export class RunCtx {
|
|
|
157
182
|
this.calls.push({
|
|
158
183
|
provider: handle.id,
|
|
159
184
|
stage,
|
|
185
|
+
category: callCategory(stage),
|
|
160
186
|
durationMs: res.durationMs,
|
|
161
187
|
...(res.ok ? {} : { error: res.error }),
|
|
162
188
|
});
|
|
@@ -188,6 +214,7 @@ export class RunCtx {
|
|
|
188
214
|
return {
|
|
189
215
|
run_id: this.runId,
|
|
190
216
|
workflow: this.workflow,
|
|
217
|
+
...(this.workflow === 'idea-refinement' ? { mode: this.mode } : {}),
|
|
191
218
|
provider_versions,
|
|
192
219
|
flag_profiles,
|
|
193
220
|
roles,
|
|
@@ -195,6 +222,7 @@ export class RunCtx {
|
|
|
195
222
|
calls: this.calls,
|
|
196
223
|
call_count: this.calls.length,
|
|
197
224
|
budget: { limit: this.budget.limit, used: this.budget.used },
|
|
225
|
+
receipt: this.receipt(),
|
|
198
226
|
exit_status: exitStatus,
|
|
199
227
|
aborted,
|
|
200
228
|
...(allFlags.length ? { flags: allFlags } : {}),
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { DISPLAY_NAME } from '../providers/types.js';
|
|
2
|
+
function cell(value) {
|
|
3
|
+
return value.replaceAll('\n', ' ').replaceAll('|', '\\|');
|
|
4
|
+
}
|
|
5
|
+
function refs(ids) {
|
|
6
|
+
return ids.length ? ids.map((id) => `\`${id}\``).join(', ') : 'none recorded';
|
|
7
|
+
}
|
|
8
|
+
function providerName(id) {
|
|
9
|
+
return id in DISPLAY_NAME ? DISPLAY_NAME[id] : id;
|
|
10
|
+
}
|
|
11
|
+
function degradation(report, flags) {
|
|
12
|
+
const active = flags.filter((flag) => report.flags.includes(flag));
|
|
13
|
+
return active.length ? [`> ⚠ DEGRADED: ${active.join(', ')}`, ''] : [];
|
|
14
|
+
}
|
|
15
|
+
function pct(value) {
|
|
16
|
+
return `${Math.round(value * 100)}%`;
|
|
17
|
+
}
|
|
18
|
+
function coverageLabel(value) {
|
|
19
|
+
return value >= 0.75 ? 'High' : value >= 0.5 ? 'Medium' : 'Low';
|
|
20
|
+
}
|
|
21
|
+
function councilRead(report) {
|
|
22
|
+
if (report.mode === 'quick')
|
|
23
|
+
return 'One structured analyst produced this result; no council, consensus, or independent-verification claim is being made.';
|
|
24
|
+
const scouts = report.models.filter((model) => model.roles.includes('scout')).length;
|
|
25
|
+
if (report.disagreements.length === 0) {
|
|
26
|
+
return `${scouts || 'The'} independent scout ${scouts === 1 ? 'analysis produced' : 'analyses produced'} no genuine opposing claim; the chair had less contested material to resolve.`;
|
|
27
|
+
}
|
|
28
|
+
const resolved = report.disagreements.filter((item) => item.status === 'RESOLVED').length;
|
|
29
|
+
return `${report.disagreements.length} genuine disagreement${report.disagreements.length === 1 ? '' : 's'} reached the chair; ${resolved} ${resolved === 1 ? 'was' : 'were'} resolved.`;
|
|
30
|
+
}
|
|
31
|
+
/** Canonical R7 Markdown. HTML and Copy-Markdown consume the same persisted dossier object. */
|
|
32
|
+
export function renderDecisionDossierMarkdown(report) {
|
|
33
|
+
const { dossier } = report;
|
|
34
|
+
const keyFindings = report.keyFindings?.length ? report.keyFindings : [dossier.recommendation.reason];
|
|
35
|
+
const criticalUnknowns = report.criticalUnknowns?.length ? report.criticalUnknowns : report.openQuestions.slice(0, 3);
|
|
36
|
+
const coverage = report.confidenceBreakdown.verificationCoverage;
|
|
37
|
+
const L = [report.mode === 'quick' ? '# Single-Model Decision Report' : '# Multi-Model Decision Report', ''];
|
|
38
|
+
L.push('## 1. Decision', '', ...degradation(report, ['synthesis_suspect']));
|
|
39
|
+
if (report.decisionSnapshot) {
|
|
40
|
+
L.push('### Decisive numbers', '', '| Metric | Value | What it means | Evidence |', '|---|---:|---|---|');
|
|
41
|
+
for (const item of report.decisionSnapshot.decisiveNumbers) {
|
|
42
|
+
L.push(`| ${cell(item.label)} | ${cell(item.value)} | ${cell(item.meaning)} | ${refs(item.claimIds)} |`);
|
|
43
|
+
}
|
|
44
|
+
if (report.decisionSnapshot.payback) {
|
|
45
|
+
const payback = report.decisionSnapshot.payback;
|
|
46
|
+
L.push('', `**Payback — ${payback.status.replaceAll('_', ' ')}:** ${payback.result}`);
|
|
47
|
+
L.push(`Basis: ${payback.basis} (${refs(payback.claimIds)})`, '');
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
L.push('');
|
|
51
|
+
}
|
|
52
|
+
L.push(`**Recommendation:** ${dossier.recommendation.summary}`, '');
|
|
53
|
+
L.push(`**Evidence coverage:** ${coverageLabel(coverage)} — ${pct(coverage)} of load-bearing claims independently verified.`);
|
|
54
|
+
L.push(coverage < 0.5
|
|
55
|
+
? '> Low coverage means important inputs remain unchecked; it is not a probability that the recommendation is correct.'
|
|
56
|
+
: '> Evidence coverage measures independent checking; it is not a probability that the recommendation is correct.', '');
|
|
57
|
+
if (!report.decisionSnapshot) {
|
|
58
|
+
L.push('### Decisive findings', '');
|
|
59
|
+
for (const finding of keyFindings.slice(0, 3))
|
|
60
|
+
L.push(`- ${finding}`);
|
|
61
|
+
}
|
|
62
|
+
L.push('', '### Do this first', '', dossier.experiments.actions[0]?.action ?? 'No executable next step was produced.', '');
|
|
63
|
+
if (report.decisionSnapshot) {
|
|
64
|
+
L.push('### Options at a glance', '', '| Path | Commitment | Basis | Trade-off | Evidence |', '|---|---:|---|---|---|');
|
|
65
|
+
for (const option of report.decisionSnapshot.options) {
|
|
66
|
+
L.push(`| ${cell(option.label)} | ${cell(option.commitment)} | ${option.commitmentKind.replace('_', ' ')} | ${cell(option.tradeoff)} | ${refs(option.claimIds)} |`);
|
|
67
|
+
}
|
|
68
|
+
if (report.decisionSnapshot.tripwire) {
|
|
69
|
+
const tripwire = report.decisionSnapshot.tripwire;
|
|
70
|
+
L.push('', '### Go/no-go tripwire', '', `**${tripwire.metric}: ${tripwire.threshold}** — ${tripwire.decisionRule} (${refs(tripwire.claimIds)})`, '');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
L.push('### What could overturn this', '', dossier.counterCase.available ? dossier.counterCase.reasoning : dossier.counterCase.reasoning, '');
|
|
74
|
+
L.push('### Critical unknowns', '');
|
|
75
|
+
if (criticalUnknowns.length)
|
|
76
|
+
for (const unknown of criticalUnknowns)
|
|
77
|
+
L.push(`- ${unknown}`);
|
|
78
|
+
else
|
|
79
|
+
L.push('- None recorded.');
|
|
80
|
+
L.push('', `**Critical warning:** ${report.verdict.criticalWarning ?? 'None recorded.'}`);
|
|
81
|
+
L.push(`**Council read:** ${councilRead(report)}`);
|
|
82
|
+
L.push(`Evidence anchors: ${refs(dossier.recommendation.claimIds)}`, '');
|
|
83
|
+
if (!dossier.recommendation.claimIds.length)
|
|
84
|
+
L.push('> ⚠ DEGRADED: recommendation has no stored graph anchor.', '');
|
|
85
|
+
L.push('<details>', '<summary>Decision conditions and technical confidence</summary>', '');
|
|
86
|
+
L.push(`- Decision state: ${dossier.recommendation.status}`);
|
|
87
|
+
L.push(`- Structural score: ${report.confidenceBreakdown.score}/100 (${report.confidenceBreakdown.label}); heuristic, not benchmark-calibrated.`);
|
|
88
|
+
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}`);
|
|
89
|
+
if (dossier.recommendation.conditions.length) {
|
|
90
|
+
L.push('', 'Conditions:');
|
|
91
|
+
for (const condition of dossier.recommendation.conditions) {
|
|
92
|
+
L.push(`- ${condition.text} (${refs(condition.claimIds)})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
L.push('', '</details>', '');
|
|
96
|
+
L.push('## 2. Action plan', '', ...degradation(report, ['plan_fallback', 'plan_skipped']));
|
|
97
|
+
if (dossier.experiments.status === 'DEGRADED')
|
|
98
|
+
L.push(`> ⚠ DEGRADED: ${dossier.experiments.note}`, '');
|
|
99
|
+
if (dossier.experiments.actions.length) {
|
|
100
|
+
L.push('| # | Experiment | Why | Validates | Effort | Kill signal |', '|---|---|---|---|---|---|');
|
|
101
|
+
for (const action of dossier.experiments.actions) {
|
|
102
|
+
L.push(`| ${action.order} | ${cell(action.action)} | ${cell(action.why)} | ${action.validates} | ${action.effort} | ${cell(action.killSignal)} |`);
|
|
103
|
+
}
|
|
104
|
+
L.push('', dossier.experiments.note);
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
L.push('No executable experiment was produced.');
|
|
108
|
+
L.push('');
|
|
109
|
+
L.push('## 3. Why this decision', '');
|
|
110
|
+
if (dossier.claimChain.length) {
|
|
111
|
+
L.push('| Claim ID | Claim | Ruling | Evidence status | Depends on |', '|---|---|---|---|---|');
|
|
112
|
+
for (const claim of dossier.claimChain) {
|
|
113
|
+
L.push(`| ${claim.claimId} | ${cell(claim.text)} | ${claim.ruling} | ${claim.evidenceStatus} | ${refs(claim.dependsOn)} |`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else
|
|
117
|
+
L.push('No graph-anchored decision chain was recorded.');
|
|
118
|
+
L.push('');
|
|
119
|
+
L.push('## 4. What could change the decision', '', '### Decision-sensitive facts', '');
|
|
120
|
+
if (dossier.sensitivity.length) {
|
|
121
|
+
L.push('| Claim ID | Fact | Sensitivity | If false | What would change it | Linked claims |', '|---|---|---|---|---|---|');
|
|
122
|
+
for (const item of dossier.sensitivity) {
|
|
123
|
+
L.push(`| ${item.claimId} | ${cell(item.fact)} | ${item.sensitivity} | ${item.impactIfFalse} | ${cell(item.whatWouldChangeIt)} | ${refs(item.linkedClaimIds)} |`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
L.push('No verdict-sensitive graph node was recorded.');
|
|
128
|
+
L.push('', '### Strongest counter-case', '');
|
|
129
|
+
if (dossier.counterCase.available) {
|
|
130
|
+
L.push(dossier.counterCase.reasoning, '', `Evidence anchors: ${refs(dossier.counterCase.claimIds)}`);
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
L.push(`> ⚠ DEGRADED: ${dossier.counterCase.reasoning}`);
|
|
134
|
+
L.push('');
|
|
135
|
+
L.push('## 5. Evidence and verification', '', ...degradation(report, ['verification_skipped', 'research_ungrounded']));
|
|
136
|
+
if (dossier.evidence.length) {
|
|
137
|
+
L.push('| Evidence ID | Source | Date | Freshness | Verification | Linked claims |', '|---|---|---|---|---|---|');
|
|
138
|
+
for (const evidence of dossier.evidence) {
|
|
139
|
+
L.push(`| ${evidence.id} | ${cell(evidence.source)} (${evidence.sourceKind}) | ${evidence.date} | ${evidence.freshness} | ${evidence.verificationStatus} | ${refs(evidence.claimIds)} |`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
L.push('No evidence cards were stored.');
|
|
144
|
+
L.push('');
|
|
145
|
+
L.push('## 6. Risks, gaps, and open questions', '', '### Risks', '');
|
|
146
|
+
if (report.risks.length) {
|
|
147
|
+
L.push('| Risk | Severity |', '|---|---|');
|
|
148
|
+
for (const risk of report.risks)
|
|
149
|
+
L.push(`| ${cell(risk.risk)} | ${risk.severity} |`);
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
L.push('No material risk was recorded.');
|
|
153
|
+
L.push('', '### Coverage', '');
|
|
154
|
+
if (dossier.coverage.length) {
|
|
155
|
+
L.push('| Dimension | Status | Claim IDs |', '|---|---|---|');
|
|
156
|
+
for (const item of dossier.coverage)
|
|
157
|
+
L.push(`| ${cell(item.label)} | ${item.status} | ${refs(item.claimIds)} |`);
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
L.push('No rubric coverage ledger was recorded.');
|
|
161
|
+
L.push('', '### Open questions', '');
|
|
162
|
+
if (report.openQuestions.length) {
|
|
163
|
+
for (const question of report.openQuestions)
|
|
164
|
+
L.push(`- ${question}`);
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
L.push('No verdict-flipping open question was recorded.');
|
|
168
|
+
L.push('');
|
|
169
|
+
L.push('## 7. Disagreement and dissent', '', ...degradation(report, ['single_model', 'low_diversity']));
|
|
170
|
+
if (report.disagreements.length) {
|
|
171
|
+
for (const disagreement of report.disagreements) {
|
|
172
|
+
L.push(`- **${disagreement.id}: ${disagreement.topic}** — ${disagreement.status}; ${disagreement.ruling}.`);
|
|
173
|
+
for (const side of disagreement.sides)
|
|
174
|
+
L.push(` - ${side.stance} (${side.providers.map(providerName).join(', ')}): ${side.reasoning.join(' ')}`);
|
|
175
|
+
if (disagreement.reasoning)
|
|
176
|
+
L.push(` - Why: ${disagreement.reasoning}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else
|
|
180
|
+
L.push(report.mode === 'quick' ? 'No cross-model disagreement analysis runs in quick mode.' : 'No genuine disagreements were stored.');
|
|
181
|
+
L.push('', '### Position changes', '');
|
|
182
|
+
if (dossier.positionChanges.length) {
|
|
183
|
+
L.push('| Event | Claim | Responder | Change | Evidence | Detail |', '|---|---|---|---|---|---|');
|
|
184
|
+
for (const event of dossier.positionChanges) {
|
|
185
|
+
L.push(`| ${event.eventId} | ${event.claimId} | ${providerName(event.responder)} | ${event.response} | ${refs(event.evidenceIds)} | ${cell(event.narrowedProposition ?? event.reasoning)} |`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
L.push('No `CONCEDE`, `COUNTER`, or `NARROW` event was recorded.');
|
|
190
|
+
L.push('', '### Minority report', '');
|
|
191
|
+
if (report.minority.dissent.length) {
|
|
192
|
+
for (const item of report.minority.dissent)
|
|
193
|
+
L.push(`- ${item}`);
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
L.push('No minority dissent was recorded.');
|
|
197
|
+
for (const item of report.minority.uniqueOppositions)
|
|
198
|
+
L.push(`- ${providerName(item.provider)} uniquely opposed: ${item.proposition}`);
|
|
199
|
+
L.push(`- Decision-blocking status: ${report.minority.blocksDecision}`, '');
|
|
200
|
+
L.push('## 8. What the council added', '');
|
|
201
|
+
if (dossier.sharedConcerns.length) {
|
|
202
|
+
L.push('Shared concerns:');
|
|
203
|
+
for (const item of dossier.sharedConcerns)
|
|
204
|
+
L.push(`- **${item.claimId}** ${item.text} — ${item.evidenceStatus}; ${item.providerIds.map(providerName).join(', ')}.`);
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
L.push('Shared concerns: none recorded.');
|
|
208
|
+
L.push('');
|
|
209
|
+
if (dossier.uniqueSupportedInsights.length) {
|
|
210
|
+
L.push('Unique supported insights:');
|
|
211
|
+
for (const item of dossier.uniqueSupportedInsights)
|
|
212
|
+
L.push(`- **${item.claimId}** ${item.text} — ${providerName(item.providerId)}; ${item.verificationStatus}.`);
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
L.push('Unique supported insights: none recorded.');
|
|
216
|
+
L.push('', '### Verified unique contributions', '', ...degradation(report, ['verification_skipped', 'single_model', 'low_diversity']));
|
|
217
|
+
L.push('Only unique claims that survived independent verification receive credit.', '');
|
|
218
|
+
L.push('| Provider | Verified unique claim IDs | Count |', '|---|---|---|');
|
|
219
|
+
for (const contribution of dossier.contributions) {
|
|
220
|
+
L.push(`| ${contribution.name} | ${refs(contribution.verifiedUniqueClaimIds)} | ${contribution.verifiedUniqueClaimIds.length} |`);
|
|
221
|
+
}
|
|
222
|
+
L.push('');
|
|
223
|
+
L.push('## 9. Run details', '');
|
|
224
|
+
L.push(`- Report ID: \`${report.reportId}\``);
|
|
225
|
+
L.push(`- Generated: ${report.generatedAt}`);
|
|
226
|
+
L.push(`- Original task: ${report.task.original}`);
|
|
227
|
+
L.push(`- Normalized question: ${report.task.normalized}`);
|
|
228
|
+
if (report.task.confirmation)
|
|
229
|
+
L.push(`- User confirmation: ${report.task.confirmation}`);
|
|
230
|
+
L.push(`- Constraints: ${report.task.constraints.join('; ') || 'none recorded'}`);
|
|
231
|
+
L.push(`- Success criteria: ${report.task.successCriteria.join('; ') || 'none recorded'}`);
|
|
232
|
+
L.push(`- Models and roles: ${report.models.map((model) => `${model.name} (${model.roles.join(', ')})`).join(' · ') || 'none recorded'}`);
|
|
233
|
+
L.push(`- Mode: ${report.mode}`);
|
|
234
|
+
L.push(`- Provider calls: ${report.receipt.calls}/${report.receipt.budget}`);
|
|
235
|
+
L.push(`- Categories: discovery ${report.receipt.categories.discovery} · verification ${report.receipt.categories.verification} · repair ${report.receipt.categories.repair} · planning ${report.receipt.categories.planning}`);
|
|
236
|
+
L.push(`- By provider: ${Object.entries(report.receipt.byProvider).map(([provider, count]) => `${providerName(provider)} ${count}`).join(', ') || 'none'}`);
|
|
237
|
+
L.push(`- Recorded model time: ${(report.receipt.modelTimeMs / 1000).toFixed(1)}s`);
|
|
238
|
+
L.push(`- Degradation flags: ${report.flags.join(', ') || 'none'}`, '');
|
|
239
|
+
L.push('## 10. Technical audit', '');
|
|
240
|
+
L.push('<details>', '<summary>Original submissions and graph events</summary>', '');
|
|
241
|
+
for (const submission of dossier.technical.submissions) {
|
|
242
|
+
L.push(`- **${submission.name}:** ${submission.strongestVersion} (${refs(submission.positionIds)})`);
|
|
243
|
+
}
|
|
244
|
+
L.push('', 'Original positions:');
|
|
245
|
+
for (const position of dossier.technical.positions) {
|
|
246
|
+
L.push(`- ${position.id} [${providerName(position.provider)} ${position.stance}] ${position.proposition}; evidence ${refs(position.evidenceIds)}`);
|
|
247
|
+
}
|
|
248
|
+
if (!dossier.technical.positions.length)
|
|
249
|
+
L.push('- none');
|
|
250
|
+
L.push('', 'Graph edges:');
|
|
251
|
+
for (const edge of dossier.technical.edges)
|
|
252
|
+
L.push(`- ${edge.from} —${edge.type}→ ${edge.to}`);
|
|
253
|
+
if (!dossier.technical.edges.length)
|
|
254
|
+
L.push('- none');
|
|
255
|
+
L.push('', 'Position-change events:');
|
|
256
|
+
for (const event of dossier.technical.events)
|
|
257
|
+
L.push(`- ${event.eventId}: ${providerName(event.responder)} ${event.response} ${event.claimId} — ${event.reasoning}`);
|
|
258
|
+
if (!dossier.technical.events.length)
|
|
259
|
+
L.push('- none');
|
|
260
|
+
L.push('', '</details>', '');
|
|
261
|
+
return L.join('\n');
|
|
262
|
+
}
|