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.
Files changed (60) hide show
  1. package/CHANGELOG.md +113 -6
  2. package/README.md +119 -30
  3. package/dist/bench/arms.js +10 -9
  4. package/dist/bench/harness.js +13 -10
  5. package/dist/bench/idea-lane-rotation.js +237 -0
  6. package/dist/bench/idea-v3-bench.js +506 -0
  7. package/dist/bench/idea-v3-rating.js +582 -0
  8. package/dist/bench/results.js +10 -3
  9. package/dist/bench/scoring/decision-insights.js +112 -0
  10. package/dist/bench/scoring/seeded-bugs.js +4 -0
  11. package/dist/cli/bench.js +180 -3
  12. package/dist/cli/doctor.js +56 -24
  13. package/dist/cli/index.js +31 -6
  14. package/dist/cli/resolve.js +7 -6
  15. package/dist/cli/resume.js +18 -0
  16. package/dist/cli/run.js +66 -8
  17. package/dist/council/view.js +446 -117
  18. package/dist/orchestration/calculations.js +97 -0
  19. package/dist/orchestration/context.js +37 -9
  20. package/dist/orchestration/decision-dossier.js +320 -0
  21. package/dist/orchestration/decision-graph.js +256 -0
  22. package/dist/orchestration/engine.js +5 -2
  23. package/dist/orchestration/evidence-pack.js +46 -0
  24. package/dist/orchestration/idea-lanes.js +20 -0
  25. package/dist/orchestration/jsonStage.js +72 -0
  26. package/dist/orchestration/legacy-idea-adapter.js +102 -0
  27. package/dist/orchestration/modes.js +39 -0
  28. package/dist/orchestration/preflight.js +203 -0
  29. package/dist/orchestration/quick-analysis.js +93 -0
  30. package/dist/orchestration/stages/cr-ladder.js +80 -0
  31. package/dist/orchestration/stages/s10-render.js +574 -150
  32. package/dist/orchestration/stages/s4-analyze.js +12 -7
  33. package/dist/orchestration/stages/s5-drift.js +9 -9
  34. package/dist/orchestration/stages/s6-positions.js +10 -0
  35. package/dist/orchestration/stages/s7-decision-graph.js +76 -0
  36. package/dist/orchestration/stages/s8-verify.js +153 -46
  37. package/dist/orchestration/stages/s8b-rebuttal.js +208 -0
  38. package/dist/orchestration/stages/s9-judge.js +329 -108
  39. package/dist/orchestration/stages/s9b-plan.js +118 -85
  40. package/dist/orchestration/url-sources.js +200 -0
  41. package/dist/providers/codex.js +2 -1
  42. package/dist/providers/spawn.js +5 -0
  43. package/dist/schemas/index.js +638 -15
  44. package/dist/skills/idea-refinement/analyst.md +18 -14
  45. package/dist/skills/idea-refinement/economics-delivery.md +7 -0
  46. package/dist/skills/idea-refinement/market-adoption.md +7 -0
  47. package/dist/storage/runs.js +12 -4
  48. package/dist/tui/app.js +53 -14
  49. package/dist/tui/format.js +4 -5
  50. package/dist/tui/smart-entry.js +17 -1
  51. package/dist/tui/timeline.js +2 -4
  52. package/dist/workflows/code-review.js +4 -2
  53. package/dist/workflows/idea-refinement.js +118 -46
  54. package/package.json +12 -4
  55. package/dist/orchestration/stages/s0-grill.js +0 -79
  56. package/dist/orchestration/stages/s1-intent.js +0 -25
  57. package/dist/orchestration/stages/s2-misread.js +0 -76
  58. package/dist/orchestration/stages/s3-prompts.js +0 -55
  59. package/dist/orchestration/stages/s6-claims.js +0 -56
  60. package/dist/orchestration/stages/s7-disagreement.js +0 -134
@@ -7,22 +7,22 @@ where it breaks. Steelman it once, then attack it honestly.
7
7
  - Before attacking, state the best honest version of the idea: who it is for and why it would win.
8
8
  - Attack THAT version, not a weak strawman. If the steelman is thin, that itself is a finding.
9
9
 
10
- ## Assumptionssurface the load-bearing ones
11
- - Name the assumptions the idea silently depends on. Mark each VERIFIABLE (checkable with data or a
12
- test) or JUDGMENT (a bet on taste, market, or behavior).
13
- - Flag which are load-bearing: the ones where, if false, the whole idea collapses. Spend effort
14
- there, not on cosmetic assumptions.
15
-
16
- ## Attacks anchored and severe
17
- - Every attack must target a specific assumption id. An attack that floats free of an assumption is
18
- noise discard it yourself.
19
- - Rank by severity: HIGH = kills or reshapes the idea; MED = forces a costly change; LOW = a caveat.
20
- - Prefer the attack a smart skeptic would actually make: no strawmen, no nitpicks dressed as risks.
10
+ ## Positionsmake the stance explicit
11
+ - State each decision-critical proposition and take an explicit SUPPORT, OPPOSE, MIXED, or UNKNOWN
12
+ stance. Mark whether the basis is EVIDENCE, INFERENCE, or ASSUMPTION.
13
+ - Flag load-bearing positions and say whether falsity means STOP, PIVOT, CONDITION, or MINOR.
14
+
15
+ ## Evidence — anchored and honest
16
+ - Anchor evidence cards to position IDs. Distinguish user material, primary sources, secondary
17
+ sources, and model knowledge; never invent a source or URL.
18
+ - Model knowledge cannot settle current, numeric, legal, medical, financial, market, or regulatory
19
+ facts. Mark uncertainty instead of laundering memory into evidence.
20
+ - Prefer the objection a smart skeptic would actually make: no strawmen or cosmetic caveats.
21
21
 
22
22
  ## MANDATORY coverage — address every dimension below
23
23
  Do not leave a dimension unexamined; an unaddressed dimension is a hole in your analysis, not a
24
24
  neutral omission. For each, either raise an assumption/attack or state plainly why it is not a
25
- concern for THIS idea:
25
+ concern for THIS idea using a reasoned NOT_APPLICABLE coverage entry:
26
26
  1. Target user / who this is actually for.
27
27
  2. Existing alternatives — including "do nothing" and the status quo.
28
28
  3. Differentiation — why this wins over those alternatives.
@@ -35,11 +35,15 @@ concern for THIS idea:
35
35
  10. Timing — why now, and what makes the market ready or not.
36
36
  11. Scalability — what breaks as it grows.
37
37
  12. Key risks / assumptions that must be validated first.
38
+ 13. Team / founder capability against the execution requirements.
39
+
40
+ Also cover every domain-specific dimension listed in the task contract. The lane prompt assigns
41
+ ownership; emit one explicit coverage entry for every owned dimension.
38
42
 
39
- ## Open questions that move the verdict
43
+ ## Decision questions that move the verdict
40
44
  - List only questions whose ANSWER would change the decision. "Nice to know" is not one of them.
41
45
  - Phrase each so that a yes/no or a single number would actually flip your assessment.
42
46
 
43
47
  ## Do not
44
48
  - No motivational framing, no pitch language, no summarizing your own output.
45
- - No attack without an assumption to anchor it. No assumption you cannot tie to the idea.
49
+ - No unanchored position or evidence. No proposition you cannot tie to the decision.
@@ -0,0 +1,7 @@
1
+ # Economics and delivery lane
2
+
3
+ Own unit economics and capital needs, technical and operational feasibility, supply constraints,
4
+ legal and compliance exposure, scalability, failure modes, and the cheapest decisive kill criteria.
5
+
6
+ Steelman the idea first. Cross into another lane only for a load-bearing issue. For every owned
7
+ dimension, emit a `COVERED` entry anchored to positions or a reasoned `NOT_APPLICABLE` entry.
@@ -0,0 +1,7 @@
1
+ # Market and adoption lane
2
+
3
+ Own target-user urgency, alternatives and status quo, differentiation, distribution and procurement,
4
+ timing, and the team's ability to execute the required adoption motion.
5
+
6
+ Steelman the idea first. Cross into another lane only for a load-bearing issue. For every owned
7
+ dimension, emit a `COVERED` entry anchored to positions or a reasoned `NOT_APPLICABLE` entry.
@@ -12,7 +12,7 @@
12
12
  // an invalid payload throws and writes nothing.
13
13
  import { mkdir, rename, writeFile } from 'node:fs/promises';
14
14
  import { dirname, join } from 'node:path';
15
- import { ActionPlan, DisagreementMap, IntentContract, JudgeReport, ReviewMap, RoleOutput, RunBrief, RunMeta, VerificationSet } from '../schemas/index.js';
15
+ import { ActionPlanArtifact, DecisionContractArtifact, DecisionGraph, DisagreementMap, IdeaRoleOutput, JudgeReport, PreflightArtifact, RebuttalEventSet, ReviewMap, RoleOutput, RunBrief, RunMeta, UrlSourceSet, VerificationArtifact } from '../schemas/index.js';
16
16
  export class OutOfOrderWriteError extends Error {
17
17
  constructor(slot, ord, maxOrd) {
18
18
  super(`out-of-order write: "${slot}" (stage ${ord}) after stage ${maxOrd} already written`);
@@ -28,18 +28,26 @@ export class DuplicateWriteError extends Error {
28
28
  /** JSON stage slots. Composites without a T4 core schema (misunderstanding-guard, drift, claims)
29
29
  * are written as-is; their schemas land with S2/S5/S6 (T5–T6). */
30
30
  const JSON_SLOTS = {
31
+ 'url-sources': { ord: 0.25, path: '00a-url-sources.json', schema: UrlSourceSet },
31
32
  'run-brief': { ord: 0.5, path: '00b-run-brief.json', schema: RunBrief },
32
- 'intent-contract': { ord: 1, path: '01-intent-contract.json', schema: IntentContract },
33
+ 'intent-contract': { ord: 1, path: '01-intent-contract.json', schema: DecisionContractArtifact },
34
+ 'preflight-readings': { ord: 2, path: '02-preflight-readings.json', schema: PreflightArtifact },
33
35
  'misunderstanding-guard': { ord: 2, path: '02-misunderstanding-guard.json', schema: null },
34
36
  'drift-report': { ord: 5, path: '05-drift-report.json', schema: null },
37
+ positions: { ord: 6, path: '06-positions.json', schema: null },
38
+ 'coverage-fill': { ord: 6.5, path: '06b-coverage-fill.json', schema: IdeaRoleOutput },
35
39
  claims: { ord: 6, path: '06-claims.json', schema: null },
40
+ 'decision-graph': { ord: 7, path: '07-decision-graph.json', schema: DecisionGraph },
36
41
  'disagreement-map': { ord: 7, path: '07-disagreement-map.json', schema: DisagreementMap },
37
42
  // code-review's stage-7 artifact (ord 7, distinct path). A run writes one of {disagreement-map,
38
43
  // review-map} depending on its workflow, so the shared ord never collides within a run.
39
44
  'review-map': { ord: 7, path: '07-review-map.json', schema: ReviewMap },
40
- verifications: { ord: 8, path: '08-verifications.json', schema: VerificationSet },
45
+ verifications: { ord: 8, path: '08-verifications.json', schema: VerificationArtifact },
46
+ rebuttals: { ord: 8.5, path: '08b-rebuttals.json', schema: RebuttalEventSet },
41
47
  'judge-report': { ord: 9, path: '09-judge-report.json', schema: JudgeReport },
42
- 'action-plan': { ord: 9.5, path: '09b-action-plan.json', schema: ActionPlan },
48
+ 'action-plan': { ord: 9.5, path: '09b-action-plan.json', schema: ActionPlanArtifact },
49
+ // machine-readable final report (three-level report v4); schema-less: derived output, never re-read by stages
50
+ 'decision-report': { ord: 10, path: '10-decision-report.json', schema: null },
43
51
  };
44
52
  /** Text (markdown) stage slots (§15). */
45
53
  const TEXT_SLOTS = {
package/dist/tui/app.js CHANGED
@@ -8,8 +8,9 @@ import Spinner from 'ink-spinner';
8
8
  import TextInput from 'ink-text-input';
9
9
  import { readFile } from 'node:fs/promises';
10
10
  import { basename, dirname, join, resolve } from 'node:path';
11
- import { DISPLAY_NAME } from '../providers/types.js';
12
- import { RunCtx, DEFAULT_BUDGET, makeRunId, resolveRoles, setupProviders, } from '../orchestration/context.js';
11
+ import { DISPLAY_NAME, PROVIDER_IDS } from '../providers/types.js';
12
+ import { preflightLine, runDoctorChecks } from '../cli/doctor.js';
13
+ import { RunCtx, makeRunId, resolveRoles, setupProviders, } from '../orchestration/context.js';
13
14
  import { executeRun } from '../orchestration/engine.js';
14
15
  import { RunWriter } from '../storage/runs.js';
15
16
  import { recordSession, updateSessionStatus, readSessions, findSession } from '../storage/sessions.js';
@@ -21,6 +22,8 @@ import { CR_STAGES, runCodeReview } from '../workflows/code-review.js';
21
22
  import { computeDiff, computeWorkingTreeDiff, detectRepoStatus } from '../orchestration/git.js';
22
23
  import { loadCouncilView } from '../council/view.js';
23
24
  import { openCouncilHtml } from '../council/open.js';
25
+ import { readJsonArtifact } from '../storage/runs-read.js';
26
+ import { defaultBudgetFor, defaultDeadlineFor, inferIdeaMode } from '../orchestration/modes.js';
24
27
  import { GLYPH, displayNames, elapsedLabel, initTimeline, markEnd, markStart, progressBar, runningPhrase, totalElapsed } from './timeline.js';
25
28
  import { formatCompletion, formatError } from './format.js';
26
29
  import { COMMANDS, PRODUCT_LINE, filterCommands, parseCommand, routeInput, scopeRedirect, suggestCommand } from './smart-entry.js';
@@ -28,7 +31,7 @@ async function loadCompletion(dir) {
28
31
  try {
29
32
  const [judge, map] = await Promise.all([
30
33
  readFile(join(dir, '09-judge-report.json'), 'utf8').then((s) => JSON.parse(s)),
31
- readFile(join(dir, '07-disagreement-map.json'), 'utf8').then((s) => JSON.parse(s)),
34
+ readFile(join(dir, '07-decision-graph.json'), 'utf8').then((s) => JSON.parse(s)),
32
35
  ]);
33
36
  return formatCompletion(dir, judge, map);
34
37
  }
@@ -67,16 +70,24 @@ export function App(props) {
67
70
  const [aborted, setAborted] = useState(false);
68
71
  const abortRef = useRef(null);
69
72
  const ctxRef = useRef(null);
70
- // Detect providers + repo context once, up front.
73
+ // Startup preflight (full doctor: CLI presence, version, auth/quota smoke — smoke cached ≤6h).
74
+ const [checks, setChecks] = useState({});
75
+ const [preflightFail, setPreflightFail] = useState(null);
76
+ // Full doctor preflight + provider handles + repo context once, up front. The menu only shows
77
+ // when ≥2 providers pass; a failure stays on this screen with the exact fix per provider.
71
78
  useEffect(() => {
72
79
  let alive = true;
73
- void Promise.all([setupProviders(providerModels), detectRepoStatus(process.cwd())]).then(([hs, repoStatus]) => {
80
+ void Promise.all([
81
+ setupProviders(providerModels),
82
+ runDoctorChecks({ onRow: (row) => { if (alive)
83
+ setChecks((prev) => ({ ...prev, [row.det.id]: preflightLine(row) })); } }),
84
+ detectRepoStatus(process.cwd()),
85
+ ]).then(([hs, report, repoStatus]) => {
74
86
  if (!alive)
75
87
  return;
76
88
  setRepo(repoStatus);
77
- if (hs.length < 2) {
78
- setErrorView(formatError('QUORUM'));
79
- setPhase('finished');
89
+ if (report.ready < 2 || hs.length < 2) {
90
+ setPreflightFail(report);
80
91
  return;
81
92
  }
82
93
  setHandles(hs);
@@ -93,6 +104,8 @@ export function App(props) {
93
104
  const t = setInterval(() => setNow(Date.now()), 500);
94
105
  return () => clearInterval(t);
95
106
  }, [phase]);
107
+ // Preflight rows that failed but left quorum intact — surfaced as a warning on the home screen.
108
+ const degraded = Object.values(checks).filter((line) => !!line && !line.ok);
96
109
  // V10 command palette: matches for what's being typed (only on the input screen, not mid-confirm).
97
110
  const paletteMatches = phase === 'input' && pendingIdea === null ? filterCommands(idea) : [];
98
111
  const selIdx = Math.min(sel, Math.max(paletteMatches.length - 1, 0));
@@ -130,6 +143,11 @@ export function App(props) {
130
143
  exit();
131
144
  return;
132
145
  }
146
+ // Failed preflight: any of q / Esc / Enter leaves — there is nothing else to do here.
147
+ if (phase === 'detecting' && preflightFail && (input === 'q' || key.escape || key.return)) {
148
+ exit();
149
+ return;
150
+ }
133
151
  // V10 confirm gate: the TextInput is unmounted while pending, so Enter/Esc arrive here only.
134
152
  if (phase === 'input' && pendingIdea !== null) {
135
153
  if (key.return) {
@@ -198,7 +216,7 @@ export function App(props) {
198
216
  if (phase === 'finished')
199
217
  exit();
200
218
  });
201
- const startRun = (wf, text, cwd, runner, stages, replay) => {
219
+ const startRun = (wf, text, cwd, runner, stages, replay, mode) => {
202
220
  const trimmed = text.trim();
203
221
  if (!trimmed)
204
222
  return;
@@ -242,7 +260,20 @@ export function App(props) {
242
260
  setPhase('clarify');
243
261
  }),
244
262
  };
245
- const ctx = new RunCtx({ runId, workflow: wf, handles, roles: rs, writer, cwd: cwd ?? writer.dir, budget: budgetOverride, signal: controller.signal, events, replay });
263
+ const ctx = new RunCtx({
264
+ runId,
265
+ workflow: wf,
266
+ mode,
267
+ handles,
268
+ roles: rs,
269
+ writer,
270
+ cwd: cwd ?? writer.dir,
271
+ budget: budgetOverride,
272
+ deadlineMs: defaultDeadlineFor(wf, mode),
273
+ signal: controller.signal,
274
+ events,
275
+ replay,
276
+ });
246
277
  ctxRef.current = ctx;
247
278
  setWorkflow(wf);
248
279
  setRows(initTimeline(stages, rs, available));
@@ -268,7 +299,9 @@ export function App(props) {
268
299
  setPhase('finished');
269
300
  });
270
301
  };
271
- const startIdea = (text) => startRun('idea-refinement', text, null, runIdeaRefinement, IDEA_STAGES);
302
+ const startIdea = (text) => {
303
+ startRun('idea-refinement', text, null, runIdeaRefinement, IDEA_STAGES, undefined, inferIdeaMode(text));
304
+ };
272
305
  const startCodeReview = async (action) => {
273
306
  if (!repo || !repo.defaultBranch) {
274
307
  setRouterMessage('code review needs a git repo with a detectable default branch');
@@ -341,7 +374,8 @@ export function App(props) {
341
374
  if (replay.size === 0)
342
375
  return void setRouterMessage(`no completed calls for ${sess.id} — start fresh`);
343
376
  const [runner, stages] = wf === 'code-review' ? [runCodeReview, CR_STAGES] : [runIdeaRefinement, IDEA_STAGES];
344
- startRun(wf, input, wf === 'code-review' ? sess.cwd : null, runner, stages, replay);
377
+ const meta = await readJsonArtifact(oldDir, 'meta.json');
378
+ startRun(wf, input, wf === 'code-review' ? sess.cwd : null, runner, stages, replay, meta?.mode);
345
379
  };
346
380
  const runCommand = async (p) => {
347
381
  setRouterMessage('');
@@ -421,9 +455,14 @@ export function App(props) {
421
455
  setPendingIdea(text);
422
456
  };
423
457
  // ── render ────────────────────────────────────────────────────────────────
424
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: "cyan", children: "aiki" }), version ? _jsxs(Text, { dimColor: true, children: [" v", version] }) : null, _jsxs(Text, { dimColor: true, children: [" \u00B7 ", phase === 'input' ? 'local model council — ideas & code review' : workflow] })] }), phase === 'detecting' && (_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), ' ', "detecting providers\u2026"] })), (phase === 'input' || phase === 'running' || phase === 'grill' || phase === 'clarify' || phase === 'finished') && handles.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [repo && (_jsxs(Text, { children: ["repo: ", repo.name, " \u2014 ", repo.changedFiles, " changed files vs ", repo.defaultBranch ?? 'unknown default branch'] })), _jsxs(Text, { children: [handles.map((h, i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { dimColor: true, children: " \u00B7 " }) : null, _jsx(Text, { color: "green", children: "\u2714" }), " ", DISPLAY_NAME[h.id], h.version ? _jsxs(Text, { dimColor: true, children: [" ", h.version] }) : null] }, h.id))), _jsx(Text, { dimColor: true, children: " \u2014 council ready" })] })] })), phase === 'input' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "/idea " }), _jsx(Text, { dimColor: true, children: '<text>' }), ' stress-test an idea'] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "/review " }), _jsx(Text, { dimColor: true, children: "[--branch]" }), ' review your changes', repo ? '' : _jsx(Text, { dimColor: true, children: " (needs a git repo)" })] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "/resume " }), _jsx(Text, { dimColor: true, children: '<id>' }), ' continue a stopped run · ', _jsx(Text, { color: "cyan", children: "/sessions" }), ' ', _jsx(Text, { color: "cyan", children: "/models" }), ' ', _jsx(Text, { color: "cyan", children: "/config" }), ' ', _jsx(Text, { color: "cyan", children: "/help" })] })] }), pendingIdea !== null ? (
458
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: "cyan", children: "aiki" }), version ? _jsxs(Text, { dimColor: true, children: [" v", version] }) : null, _jsxs(Text, { dimColor: true, children: [" \u00B7 ", phase === 'input' ? 'local model council — ideas & code review' : workflow] })] }), phase === 'detecting' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "preflight \u2014 checking provider CLIs, auth & quota (smoke cached \u22646h)" }), PROVIDER_IDS.map((id) => {
459
+ const line = checks[id];
460
+ return (_jsxs(Text, { children: [line
461
+ ? line.ok ? _jsx(Text, { color: "green", children: "\u2714" }) : _jsx(Text, { color: "red", children: "\u2716" })
462
+ : _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), ' ', line ? line.label : `${DISPLAY_NAME[id]} — checking…`, line?.fix ? _jsxs(Text, { dimColor: true, children: [" \u2192 ", line.fix] }) : null] }, id));
463
+ }), preflightFail && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "red", bold: true, children: ["preflight failed \u2014 ", preflightFail.ready, "/3 providers ready (aiki needs 2)"] }), _jsx(Text, { dimColor: true, children: "apply the fix shown next to each \u2716, then run `aiki` again \u00B7 `aiki doctor --fresh` re-checks live \u00B7 q quits" })] }))] })), (phase === 'input' || phase === 'running' || phase === 'grill' || phase === 'clarify' || phase === 'finished') && handles.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [repo && (_jsxs(Text, { children: ["repo: ", repo.name, " \u2014 ", repo.changedFiles, " changed files vs ", repo.defaultBranch ?? 'unknown default branch'] })), _jsxs(Text, { children: [handles.map((h, i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { dimColor: true, children: " \u00B7 " }) : null, _jsx(Text, { color: "green", children: "\u2714" }), " ", DISPLAY_NAME[h.id], h.version ? _jsxs(Text, { dimColor: true, children: [" ", h.version] }) : null] }, h.id))), _jsx(Text, { dimColor: true, children: " \u2014 council ready" })] }), degraded.length > 0 && (_jsxs(Text, { color: "yellow", children: ["\u26A0 ", degraded.map((line) => `${line.label}${line.fix ? ` (${line.fix})` : ''}`).join(' · '), " \u2014 continuing with ", Math.max(handles.length - degraded.length, 0), "/3 providers"] }))] })), phase === 'input' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "/idea " }), _jsx(Text, { dimColor: true, children: '<text>' }), ' stress-test an idea'] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "/review " }), _jsx(Text, { dimColor: true, children: "[--branch]" }), ' review your changes', repo ? '' : _jsx(Text, { dimColor: true, children: " (needs a git repo)" })] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "/resume " }), _jsx(Text, { dimColor: true, children: '<id>' }), ' continue a stopped run · ', _jsx(Text, { color: "cyan", children: "/sessions" }), ' ', _jsx(Text, { color: "cyan", children: "/models" }), ' ', _jsx(Text, { color: "cyan", children: "/config" }), ' ', _jsx(Text, { color: "cyan", children: "/help" })] })] }), pendingIdea !== null ? (
425
464
  /* V10 confirm gate — nothing spends model calls until Enter. */
426
- _jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "Run the idea council on:" }), _jsxs(Text, { color: "cyan", children: [" \u201C", pendingIdea.length > 100 ? `${pendingIdea.slice(0, 97)}…` : pendingIdea, "\u201D"] }), _jsxs(Text, { dimColor: true, children: [" 12-stage pipeline \u00B7 up to ", budgetOverride ?? DEFAULT_BUDGET, " model calls \u00B7 Ctrl+C aborts mid-run"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "enter" }), " run \u00B7 ", _jsx(Text, { color: "yellow", children: "esc" }), " cancel"] })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "Type a command, or just describe your idea and press Enter:" }), _jsxs(Box, { borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "\u25B8 " }), _jsx(TextInput, { value: idea, onChange: (v) => setIdea(v.replace(/\s*[\r\n]+\s*/g, ' ').replace(/\t/g, '')), onSubmit: submitInput }, inputEpoch)] })] })), paletteMatches.length > 0 && (
465
+ _jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "Run the idea council on:" }), _jsxs(Text, { color: "cyan", children: [" \u201C", pendingIdea.length > 100 ? `${pendingIdea.slice(0, 97)}…` : pendingIdea, "\u201D"] }), _jsxs(Text, { dimColor: true, children: [" 10-stage pipeline \u00B7 ", inferIdeaMode(pendingIdea), " mode \u00B7 up to ", budgetOverride ?? defaultBudgetFor('idea-refinement', inferIdeaMode(pendingIdea)), " model calls \u00B7 Ctrl+C aborts mid-run"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "enter" }), " run \u00B7 ", _jsx(Text, { color: "yellow", children: "esc" }), " cancel"] })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "Type a command, or just describe your idea and press Enter:" }), _jsxs(Box, { borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "\u25B8 " }), _jsx(TextInput, { value: idea, onChange: (v) => setIdea(v.replace(/\s*[\r\n]+\s*/g, ' ').replace(/\t/g, '')), onSubmit: submitInput }, inputEpoch)] })] })), paletteMatches.length > 0 && (
427
466
  /* V10 live command palette — filtered as you type; ↑↓ move, Tab completes, Enter runs. */
428
467
  _jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [paletteMatches.map((m, i) => (_jsxs(Text, { children: [_jsxs(Text, { color: i === selIdx ? 'cyan' : undefined, bold: i === selIdx, children: [i === selIdx ? '▸ ' : ' ', "/", m.name] }), _jsx(Text, { dimColor: true, children: ` ${m.usage.replace(`/${m.name}`, '').trim().padEnd(10)} ${m.help}` })] }, m.name))), _jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u00B7 tab complete \u00B7 enter run" })] })), routerMessage ? _jsx(Text, { color: "yellow", children: routerMessage }) : null, panel ? (_jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", paddingX: 1, children: panel.split('\n').map((l, i) => (_jsx(Text, { children: l }, i))) })) : (_jsx(Text, { dimColor: true, children: "new here? /help explains how aiki works \u00B7 long idea? `aiki run idea-refinement ./idea.md`" }))] })), (phase === 'running' || phase === 'grill' || phase === 'clarify' || phase === 'finished') && rows.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [rows.map((r) => {
429
468
  const color = r.status === 'done' ? 'green' : r.status === 'failed' ? 'red' : r.status === 'running' ? 'yellow' : 'gray';
@@ -1,11 +1,10 @@
1
1
  // Pure formatters for the completion + error screens (T8, §4.3, §471). No Ink — unit-tested directly.
2
2
  /** Completion summary (§4.3): verdict + the top-N contradictions with their adjudication ruling. */
3
- export function formatCompletion(dir, judge, map, topN = 3) {
3
+ export function formatCompletion(dir, judge, graph, topN = 3) {
4
4
  const ruling = new Map(judge.adjudications.map((a) => [a.id, a.ruling]));
5
- const disagreements = map.contradictions.slice(0, topN).map((d) => {
6
- const r = ruling.get(d.id) ?? 'UNRESOLVED';
7
- const arg = d.attacks[0]?.argument ?? '';
8
- return `${d.id} → ${r}: ${arg}`;
5
+ const disagreements = graph.claims.filter((claim) => claim.state === 'DISAGREEMENT').slice(0, topN).map((claim) => {
6
+ const result = ruling.get(claim.id) ?? 'UNRESOLVED';
7
+ return `${claim.id} ${result}: ${claim.proposition}`;
9
8
  });
10
9
  return { verdict: judge.verdict, disagreements, reportPath: `${dir}/final-report.md`, rawPath: `${dir}/raw/` };
11
10
  }
@@ -1,6 +1,22 @@
1
1
  export const PRODUCT_LINE = 'aiki stress-tests ideas and reviews code; for general questions use a single model — a council adds cost, not accuracy, when there is one right answer.';
2
2
  const QUESTION_START = /^(what|why|how|who|when|where|is|are|can|could|should|would|do|does|did|will)\b/i;
3
- const CODE_MARKER = /(diff --git|^@@|\+\+\+ b\/|--- a\/|```|[A-Za-z0-9_-]+\/[A-Za-z0-9_./-]+\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|css|html|md)\b|\b(function|const|let|class|import|export)\b|[{};])/m;
3
+ // Detect PASTED code / a diff, NOT prose that merely contains a code-ish word. Bare keywords
4
+ // (`class`, `export`, `import`) and a lone `;`/`{`/`}` collide with ordinary English — "a class
5
+ // scheduling app; export our data" is an idea, not a diff — so every alternative below requires code
6
+ // *structure*: a diff/fence marker, a file path with a code extension, arrow/`){`, or a keyword in
7
+ // real syntax (`class Foo {`, `const x =`, `import { … }` / `import x from '…'`, `export default/const…`).
8
+ const CODE_MARKER = new RegExp([
9
+ 'diff --git', '^@@', '^\\+\\+\\+ b/', '^--- a/', // unified-diff headers
10
+ '```', // markdown code fence
11
+ '[A-Za-z0-9_-]+/[A-Za-z0-9_./-]+\\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|css|html|md)\\b', // path/to/file.ext
12
+ '=>', // arrow function
13
+ '\\)\\s*\\{', // ) { — block open
14
+ '\\bfunction\\s+[A-Za-z_$][\\w$]*\\s*\\(|\\bfunction\\s*\\(', // function foo( / function(
15
+ '\\bclass\\s+[A-Za-z_$][\\w$]*\\s*(?:\\{|extends\\b)', // class Foo { / class Foo extends
16
+ '\\b(?:const|let|var)\\s+[A-Za-z_$][\\w$]*\\s*=', // const x =
17
+ "\\bimport\\s+(?:\\{|[\\w$]+\\s+from\\s+['\"]|['\"])", // import { … } / import x from '…' / import '…'
18
+ '\\bexport\\s+(?:default|const|let|var|function|class|async|\\{)', // export default / export const / …
19
+ ].join('|'), 'm');
4
20
  // ── Scope redirect (V10.2) — catch "explore my whole codebase / brainstorm features for me" asks and
5
21
  // point them at the right door, instead of silently stress-testing the request sentence as an "idea".
6
22
  // aiki reviews a DIFF and vets a STATED idea; it does not roam a repo (§3/§22). Pure + deterministic. ──
@@ -56,15 +56,13 @@ export const displayNames = (ps) => ps.map((p) => DISPLAY_NAME[p]).join(', ');
56
56
  // ── V10 run-screen life: rotating status phrases + progress bar + total time. Pure; unit-tested. ────
57
57
  /** Stage-flavored status phrases, rotated every 4s so long stages feel alive (not stuck). */
58
58
  const PHRASES = {
59
- S0: ['grilling the intent', 'sharpening the run brief'],
60
- S1: ['pinning down what you actually asked', 'writing the task contract'],
61
- S2: ['checking every model read it the same way', 'guarding against a misread'],
62
- S3: ["writing each seat's brief", 'tailoring the role prompts'],
59
+ S0: ['comparing two readings of your decision', 'sharpening one decision contract'],
63
60
  S4: ['council working in separate rooms', 'independent takes incoming'],
64
61
  S5: ['checking nobody drifted off-task', 'comparing echoes against the contract'],
65
62
  S6: ['boiling the output down to claims', 'extracting the claims'],
66
63
  S7: ['mapping where they disagree', 'drawing the disagreement map'],
67
64
  S8: ['cross-examining the claims', 'stress-testing the evidence'],
65
+ S8b: ['rebutting only what could flip the verdict', 'testing the decisive conflict'],
68
66
  S9: ['the judge is deliberating', 'weighing evidence over confidence'],
69
67
  S9b: ['planning decisive validation', 'ordering the next tests'],
70
68
  S10: ['writing the report', 'assembling the decision brief'],
@@ -10,6 +10,7 @@ import { resolve } from 'node:path';
10
10
  import { runStage } from '../orchestration/context.js';
11
11
  import { parseDiffFiles } from '../orchestration/git.js';
12
12
  import { s4Review } from '../orchestration/stages/cr-s4-review.js';
13
+ import { mergeCoverageHunt, runCoverageHunts } from '../orchestration/stages/cr-ladder.js';
13
14
  import { s8CrossExam } from '../orchestration/stages/cr-s8-crossexam.js';
14
15
  import { buildReviewMap } from '../orchestration/stages/cr-map.js';
15
16
  import { s9ReviewJudge } from '../orchestration/stages/cr-s9-judge.js';
@@ -44,7 +45,7 @@ export const CR_STAGES = [
44
45
  { id: 'S9', label: 'Judge adjudication', role: 'judge' },
45
46
  { id: 'S10', label: 'Report', role: null },
46
47
  ];
47
- export async function runCodeReview(ctx, input) {
48
+ export async function runCodeReview(ctx, input, opts = {}) {
48
49
  // `input` is the unified diff. Persist it where the reviewer prompt points; parse the touched files.
49
50
  await ctx.writer.writeInput('diff.patch', input);
50
51
  const diffPath = resolve(ctx.writer.dir, 'inputs', 'diff.patch');
@@ -63,10 +64,11 @@ export async function runCodeReview(ctx, input) {
63
64
  const prompt = buildReviewerPrompt(diffPath, loadSkill('code-review', 'reviewer'));
64
65
  await ctx.writer.writePrompt('reviewer.md', prompt);
65
66
  const reviewers = await runStage(ctx, 'S4', () => s4Review(ctx, prompt, files));
67
+ const ladder = opts.ladder ? await runStage(ctx, 'L', () => runCoverageHunts(ctx, input, reviewers)) : null;
66
68
  const cross = await runStage(ctx, 'S8', () => s8CrossExam(ctx, reviewers));
67
69
  // S7 — build + persist the ReviewMap (07), then the cross-exam verifications (08). Order: 07 < 08.
68
70
  const map = await runStage(ctx, 'S7', async () => {
69
- const m = buildReviewMap(reviewers, cross.byKey);
71
+ const m = mergeCoverageHunt(buildReviewMap(reviewers, cross.byKey), ladder);
70
72
  await ctx.writer.writeJson('review-map', m);
71
73
  await ctx.writer.writeJson('verifications', { verifications: cross.verifications });
72
74
  return m;
@@ -1,25 +1,23 @@
1
- // idea-refinement workflow (§12.1). Stage COMPOSITION only no orchestration mechanics (those
2
- // live in the engine) and, for now, prompts inline here rather than in skills/<workflow>/ (the
3
- // full skill loader + registry, §11, is deferred; it must not register without bench/ + validate.ts).
4
- //
5
- // v1 T5 scope: S1 → S2 → S3. S4–S10 are added as later tasks extend this composition.
1
+ // idea-refinement composition. R6 collapses the old S0/S1/S2 model chain into two parallel
2
+ // preflight readings and fills the S4 prompt deterministically; later stages remain typed boundaries.
6
3
  import { resolve } from 'node:path';
7
- import { runStage } from '../orchestration/context.js';
8
- import { renderGrilledInput, s0Grill } from '../orchestration/stages/s0-grill.js';
9
- import { s1Intent } from '../orchestration/stages/s1-intent.js';
10
- import { s2Misread } from '../orchestration/stages/s2-misread.js';
11
- import { s3Prompts } from '../orchestration/stages/s3-prompts.js';
4
+ import { runStage, StageError } from '../orchestration/context.js';
12
5
  import { s4Analyze } from '../orchestration/stages/s4-analyze.js';
13
6
  import { s5Drift } from '../orchestration/stages/s5-drift.js';
14
- import { s6Claims } from '../orchestration/stages/s6-claims.js';
15
- import { s7Disagreement } from '../orchestration/stages/s7-disagreement.js';
7
+ import { s6Positions } from '../orchestration/stages/s6-positions.js';
8
+ import { s7DecisionGraph } from '../orchestration/stages/s7-decision-graph.js';
16
9
  import { s8Verify } from '../orchestration/stages/s8-verify.js';
10
+ import { s8bRebuttal } from '../orchestration/stages/s8b-rebuttal.js';
17
11
  import { s9Judge } from '../orchestration/stages/s9-judge.js';
18
12
  import { s9bPlan } from '../orchestration/stages/s9b-plan.js';
19
13
  import { s10Render } from '../orchestration/stages/s10-render.js';
20
14
  import { loadSkill } from '../orchestration/skills.js';
21
- /** §12.1 idea-vetting rubric: 12 mandatory coverage items. S7 flags any item no analyst addressed
22
- * as a blind spot. Inlined here (like the S4 template) while the skill/`rubric.json` loader (§11)
15
+ import { buildLanePrompts } from '../orchestration/idea-lanes.js';
16
+ import { preflight, renderDecisionInput } from '../orchestration/preflight.js';
17
+ import { buildQuickPrompt, quickActionPlan, quickJudgeReport, s4QuickAnalyze } from '../orchestration/quick-analysis.js';
18
+ import { snapshotUrlSources } from '../orchestration/url-sources.js';
19
+ /** Idea-vetting core rubric: 13 mandatory coverage items. S0 adds 3-5 domain dimensions per run.
20
+ * Inlined here (like the S4 template) while the skill/`rubric.json` loader (§11)
23
21
  * is deferred; it moves to skills/idea-refinement/rubric.json when that loader lands. */
24
22
  export const IDEA_RUBRIC = [
25
23
  { id: 'R1', label: 'target user / audience', keywords: ['target user', 'audience', 'customer', 'persona'] },
@@ -34,43 +32,77 @@ export const IDEA_RUBRIC = [
34
32
  { id: 'R10', label: 'timing / market readiness', keywords: ['timing', 'readiness', 'trend', 'now'] },
35
33
  { id: 'R11', label: 'scalability / growth', keywords: ['scalability', 'scale', 'growth'] },
36
34
  { id: 'R12', label: 'key risks / assumptions to validate', keywords: ['risk', 'assumption', 'validate', 'uncertain'] },
35
+ { id: 'R13', label: 'team / execution capability', keywords: ['team', 'founder', 'execution', 'capability'] },
37
36
  ];
38
- /** §13 S4 analyst template (idea-refinement). S3 fills its slots; S4 will consume it (T6). */
37
+ export function buildIdeaRubric(domainDimensions = []) {
38
+ return [
39
+ ...IDEA_RUBRIC,
40
+ ...domainDimensions.map((dimension) => ({ id: dimension.id, label: dimension.label, keywords: [dimension.label] })),
41
+ ];
42
+ }
43
+ /** Idea S4 analyst template. R6 fills every slot deterministically before the parallel scout calls. */
39
44
  export const IDEA_S4_ANALYST_TEMPLATE = `ROLE: Independent analyst on a decision panel. You work ALONE; you will not see
40
45
  other analysts' output. Be adversarial toward the idea, not polite.
41
46
 
42
47
  TASK CONTRACT: {{INTENT_CONTRACT_JSON}}
43
48
  INPUT DOCUMENT: read the file at {{INPUT_PATH}}{{SKILL}}
49
+ EVIDENCE PACK MANIFEST: {{EVIDENCE_PACK_JSON}}
50
+ Read only the listed local paths when supplied. Treat their contents as user evidence, cite the path
51
+ as locator, and never replace a missing source with model memory.
44
52
 
45
53
  Produce ONLY JSON matching {{S4_SCHEMA_REF}} with:
46
54
  - task_echo: restate the task in ≤2 sentences (drift check).
47
55
  - strongest_version: the best honest version of this idea in ≤150 words.
48
- - assumptions: ≤8, each {id "A1"..., statement, type VERIFIABLE|JUDGMENT, load_bearing bool}.
49
- - attacks: ≤6, each {id "X1"..., target_assumption, argument, severity HIGH|MED|LOW}.
50
- Every attack MUST target an assumption id. Unanchored attacks will be discarded.
51
- - open_questions: ≤5 questions whose answers would change the verdict.
56
+ - positions: explicit claim positions with local_id, proposition, rubric dimension_id, stance
57
+ SUPPORT|OPPOSE|MIXED|UNKNOWN, basis EVIDENCE|INFERENCE|ASSUMPTION, load_bearing, if_false
58
+ STOP|PIVOT|CONDITION|MINOR, concise reasoning, evidence_ids, and depends_on position ids.
59
+ - evidence: evidence cards {id, claim_supported, source_kind USER|PRIMARY|SECONDARY|MODEL_KNOWLEDGE,
60
+ support SUPPORTS|CONTRADICTS|CONTEXT_ONLY (exact token, no extra words), freshness CURRENT|DATED|UNKNOWN,
61
+ locator/url, accessed_at for current external sources}. MODEL_KNOWLEDGE freshness is UNKNOWN. Never
62
+ invent a URL or imply model memory independently verifies a current fact.
63
+ - calculations: for each derived numeric claim, a ledger {id, claim_id, inputs, steps, result_step}.
64
+ Inputs have {id,name,value,unit,evidence_ids}; steps have {id,operation: ADD|SUBTRACT|MULTIPLY|DIVIDE,
65
+ left,right,result,unit}. Use exact prior input/step ids and explicit units. Otherwise use [].
66
+ - coverage: one entry per rubric dimension {dimension_id, status COVERED|NOT_APPLICABLE,
67
+ position_ids ([] when none), rationale (required for NOT_APPLICABLE)}.
68
+ - decision_questions: questions {question, claim_ids} whose answers could change the verdict.
69
+ Caps: at most 12 positions, 20 evidence cards, 8 calculations, 8 decision_questions.
52
70
  Rules: no motivation, no summaries of your own output, no markdown, JSON only.`;
53
71
  /**
54
- * Resolve the {{SKILL}} slot in the analyst template BEFORE S3 (S3 is a model call that tailors the
55
- * template + errors on any leftover {{...}}). An empty skill collapses the slot → exact pre-skill
56
- * baseline; the S3 deterministic fallback then preserves the embedded playbook verbatim.
72
+ * Resolve the {{SKILL}} slot before deterministic structural fill. An empty skill collapses the slot
73
+ * to the pre-skill baseline.
57
74
  */
58
75
  export function buildAnalystTemplate(skill) {
59
76
  return IDEA_S4_ANALYST_TEMPLATE.replace('{{SKILL}}', skill ? `\n\n${skill}` : '');
60
77
  }
78
+ /** R6 deterministic S4 prompt fill. No model-authored prompt generation remains. */
79
+ export function buildAnalystPrompt(contract, inputPath, evidencePack, mode, skill) {
80
+ const modeRules = mode === 'research'
81
+ ? `\n\nMODE: research. Use provider-native read-only source investigation when available. Every current
82
+ fact must have an independently checkable locator and access date. If investigation is unavailable,
83
+ leave the claim unverified; never invent a source.`
84
+ : `\n\nMODE: council. Analyze independently; do not assume another seat will cover your lane.`;
85
+ const prompt = buildAnalystTemplate(skill)
86
+ .replace('{{INTENT_CONTRACT_JSON}}', JSON.stringify(contract))
87
+ .replace('{{INPUT_PATH}}', inputPath)
88
+ .replace('{{EVIDENCE_PACK_JSON}}', JSON.stringify(evidencePack ?? { files: [] }))
89
+ .replace('{{S4_SCHEMA_REF}}', 'the idea-refinement S4 RoleOutput schema')
90
+ + modeRules;
91
+ if (/\{\{[^}]+\}\}/.test(prompt))
92
+ throw new StageError('S3', 'BAD_OUTPUT', 'deterministic analyst prompt has an unresolved slot');
93
+ return prompt;
94
+ }
61
95
  /** Timeline manifest (T8): the stages in order, each with the provider-role its row displays.
62
96
  * The TUI draws the pending skeleton from this and resolves chips from `ctx.roles`. Ids match the
63
97
  * `runStage` calls below. S7 shows the judge (it makes the grouping call); S5/S6/S10 are pure (—). */
64
98
  export const IDEA_STAGES = [
65
- { id: 'S0', label: 'Intent preflight', role: 'analyst' },
66
- { id: 'S1', label: 'Intent contract', role: 'analyst' },
67
- { id: 'S2', label: 'Misunderstanding guard', role: 'all' },
68
- { id: 'S3', label: 'Prompt generation', role: 'analyst' },
99
+ { id: 'S0', label: 'Two-view preflight', role: 's4' },
69
100
  { id: 'S4', label: 'Parallel analysis', role: 's4' },
70
101
  { id: 'S5', label: 'Drift check', role: null },
71
- { id: 'S6', label: 'Claim extraction', role: null },
72
- { id: 'S7', label: 'Disagreement map', role: 'judge' },
102
+ { id: 'S6', label: 'Position collection', role: null },
103
+ { id: 'S7', label: 'Decision graph', role: null },
73
104
  { id: 'S8', label: 'Verifier loop', role: 'verifier' },
105
+ { id: 'S8b', label: 'Selective rebuttal', role: 's4' },
74
106
  { id: 'S9', label: 'Judge synthesis', role: 'judge' },
75
107
  { id: 'S9b', label: 'Validation plan', role: 'judge' },
76
108
  { id: 'S10', label: 'Report', role: null },
@@ -79,27 +111,67 @@ export const IDEA_STAGES = [
79
111
  * `executeRun` wrapper turns that into a graceful failure + meta. Each stage is wrapped in
80
112
  * `runStage` so the TUI timeline (T8) gets start/end events; headless, that's a no-op. */
81
113
  export async function runIdeaRefinement(ctx, input) {
82
- const brief = await runStage(ctx, 'S0', () => s0Grill(ctx, input));
83
- const grilledInput = renderGrilledInput(input, brief);
84
- const contract = await runStage(ctx, 'S1', () => s1Intent(ctx, grilledInput));
85
- const guard = await runStage(ctx, 'S2', () => s2Misread(ctx, contract, grilledInput));
114
+ if (ctx.evidencePack)
115
+ await ctx.writer.writeInput('evidence-pack.json', JSON.stringify(ctx.evidencePack, null, 2));
116
+ const urlSources = await snapshotUrlSources(input);
117
+ await ctx.writer.writeJson('url-sources', urlSources);
118
+ const unreadableSources = urlSources.sources.filter((source) => source.status !== 'FETCHED');
119
+ if (ctx.mode === 'research' && unreadableSources.length > 0) {
120
+ const details = unreadableSources.map((source) => `${source.url} (${source.status}: ${source.error})`).join('; ');
121
+ throw new StageError('S0', 'SOURCE_UNREADABLE', `research stopped before model calls because a supplied source could not be read: ${details}. Paste the relevant text or provide a public export, then rerun.`);
122
+ }
123
+ const { contract, brief } = await runStage(ctx, 'S0', () => preflight(ctx, input, IDEA_RUBRIC.map((item) => item.label), urlSources));
124
+ const grilledInput = renderDecisionInput(input, brief, urlSources);
86
125
  // Persist the input as a file so S4's "read the file at {{INPUT_PATH}}" resolves (not a stage).
87
126
  await ctx.writer.writeInput('idea.md', input);
88
127
  await ctx.writer.writeInput('idea-brief.md', grilledInput);
89
128
  const inputPath = resolve(ctx.writer.dir, 'inputs', 'idea-brief.md');
90
- const stagePrompts = await runStage(ctx, 'S3', () => s3Prompts(ctx, {
91
- contract,
92
- interpretation: guard.chosen.my_interpretation,
93
- templates: { analyst: buildAnalystTemplate(loadSkill('idea-refinement', 'analyst')) },
94
- slots: { INPUT_PATH: inputPath, S4_SCHEMA_REF: 'the idea-refinement S4 RoleOutput schema' },
95
- }));
96
- // s3Prompts guarantees an entry for every template key (it iterates them), so `analyst` is present.
97
- const seats = await runStage(ctx, 'S4', () => s4Analyze(ctx, stagePrompts.prompts.analyst));
129
+ const rubric = buildIdeaRubric(contract.domain_dimensions);
130
+ const analystSkill = loadSkill('idea-refinement', 'analyst');
131
+ if (ctx.mode === 'quick') {
132
+ ctx.addFlag('single_model');
133
+ const quickPrompt = buildQuickPrompt(contract, inputPath, ctx.evidencePack, analystSkill);
134
+ await ctx.writer.writePrompt('quick-analyst.md', quickPrompt);
135
+ const quick = await runStage(ctx, 'S4', () => s4QuickAnalyze(ctx, quickPrompt));
136
+ const { kept } = await runStage(ctx, 'S5', () => s5Drift(ctx, contract, [quick.seat], 1));
137
+ const positions = await runStage(ctx, 'S6', () => s6Positions(ctx, kept));
138
+ const graph = await runStage(ctx, 'S7', () => s7DecisionGraph(ctx, positions, rubric, contract.task));
139
+ const verifications = await runStage(ctx, 'S8', async () => {
140
+ const result = { verifications: [] };
141
+ await ctx.writer.writeJson('verifications', result);
142
+ return result;
143
+ });
144
+ const rebuttals = await runStage(ctx, 'S8b', async () => {
145
+ const result = { round: 1, selected_claim_ids: [], events: [], stop_reason: 'NO_ESCALATIONS' };
146
+ await ctx.writer.writeJson('rebuttals', result);
147
+ return result;
148
+ });
149
+ const judgeReport = await runStage(ctx, 'S9', async () => {
150
+ const report = quickJudgeReport(quick.decision, graph);
151
+ await ctx.writer.writeJson('judge-report', report);
152
+ return report;
153
+ });
154
+ const actionPlan = await runStage(ctx, 'S9b', async () => {
155
+ const plan = quickActionPlan(ctx, quick.seat.provider, quick.decision, graph, contract);
156
+ await ctx.writer.writeJson('action-plan', plan);
157
+ return plan;
158
+ });
159
+ await runStage(ctx, 'S10', () => s10Render(ctx, { contract, seats: kept, graph, verifications, rebuttals, judgeReport, actionPlan, rubric, original: input }));
160
+ return;
161
+ }
162
+ if (ctx.mode === 'research' && !ctx.evidencePack && !ctx.available().includes('codex')) {
163
+ ctx.addFlag('research_ungrounded');
164
+ }
165
+ const analystPrompt = buildAnalystPrompt(contract, inputPath, ctx.evidencePack, ctx.mode, analystSkill);
166
+ await ctx.writer.writePrompt('analyst.md', analystPrompt);
167
+ const lanePrompts = buildLanePrompts(analystPrompt, rubric);
168
+ const seats = await runStage(ctx, 'S4', () => s4Analyze(ctx, lanePrompts));
98
169
  const { kept } = await runStage(ctx, 'S5', () => s5Drift(ctx, contract, seats));
99
- const claimSet = await runStage(ctx, 'S6', () => s6Claims(ctx, kept));
100
- const map = await runStage(ctx, 'S7', () => s7Disagreement(ctx, claimSet, kept, IDEA_RUBRIC));
101
- const verifications = await runStage(ctx, 'S8', () => s8Verify(ctx, map));
102
- const judgeReport = await runStage(ctx, 'S9', () => s9Judge(ctx, contract, map, verifications, IDEA_RUBRIC));
103
- const actionPlan = await runStage(ctx, 'S9b', () => s9bPlan(ctx, contract, kept, map, judgeReport));
104
- await runStage(ctx, 'S10', () => s10Render(ctx, { contract, seats: kept, map, verifications, judgeReport, actionPlan, rubric: IDEA_RUBRIC }));
170
+ const positions = await runStage(ctx, 'S6', () => s6Positions(ctx, kept));
171
+ const graph = await runStage(ctx, 'S7', () => s7DecisionGraph(ctx, positions, rubric, contract.task));
172
+ const verifications = await runStage(ctx, 'S8', () => s8Verify(ctx, graph));
173
+ const rebuttals = await runStage(ctx, 'S8b', () => s8bRebuttal(ctx, graph, verifications, ctx.mode));
174
+ const judgeReport = await runStage(ctx, 'S9', () => s9Judge(ctx, contract, graph, verifications, rubric, rebuttals));
175
+ const actionPlan = await runStage(ctx, 'S9b', () => s9bPlan(ctx, contract, kept, graph, judgeReport));
176
+ await runStage(ctx, 'S10', () => s10Render(ctx, { contract, seats: kept, graph, verifications, rebuttals, judgeReport, actionPlan, rubric, original: input }));
105
177
  }