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.
Files changed (31) hide show
  1. package/CHANGELOG.md +12 -5
  2. package/README.md +9 -11
  3. package/dist/bench/idea-v3-rating.js +1 -1
  4. package/dist/cli/index.js +1 -1
  5. package/dist/cli/run.js +1 -1
  6. package/dist/council/view.js +52 -12
  7. package/dist/orchestration/context.js +3 -3
  8. package/dist/orchestration/decision-dossier.js +417 -88
  9. package/dist/orchestration/decision-graph.js +31 -0
  10. package/dist/orchestration/legacy-idea-adapter.js +3 -0
  11. package/dist/orchestration/modes.js +15 -9
  12. package/dist/orchestration/preflight.js +17 -3
  13. package/dist/orchestration/quick-analysis.js +21 -4
  14. package/dist/orchestration/stages/s10-render.js +167 -79
  15. package/dist/orchestration/stages/s4-analyze.js +3 -1
  16. package/dist/orchestration/stages/s6-positions.js +18 -0
  17. package/dist/orchestration/stages/s7-decision-graph.js +3 -0
  18. package/dist/orchestration/stages/s8-verify.js +26 -9
  19. package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
  20. package/dist/orchestration/stages/s9-judge.js +36 -13
  21. package/dist/orchestration/stages/s9b-plan.js +192 -42
  22. package/dist/schemas/index.js +67 -3
  23. package/dist/skills/idea-refinement/analyst.md +5 -5
  24. package/dist/skills/idea-refinement/chair.md +18 -0
  25. package/dist/skills/idea-refinement/economics-delivery.md +11 -0
  26. package/dist/skills/idea-refinement/market-adoption.md +12 -0
  27. package/dist/skills/idea-refinement/planner.md +25 -19
  28. package/dist/skills/idea-refinement/rebuttal.md +15 -0
  29. package/dist/skills/idea-refinement/verifier.md +17 -0
  30. package/dist/workflows/idea-refinement.js +42 -14
  31. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,18 +1,25 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.3.2 — 2026-07-17
4
4
 
5
5
  ### Added
6
6
  - Public URL snapshots for idea prompts, including an npm registry adapter, private-network rejection,
7
- auditable `FETCHED` / `BLOCKED` / `FAILED` status, and a research-mode gate that stops before model calls
7
+ auditable `FETCHED` / `BLOCKED` / `FAILED` status, and a full-council gate that stops before model calls
8
8
  when a supplied source cannot be read.
9
9
  - Requested-output planning: prompts that ask for a feature list or implementation plan now carry that
10
10
  requirement through the decision contract, the existing planner call, machine JSON, Markdown, and HTML.
11
11
 
12
12
  ### Changed
13
- - Explicit research wording selects research mode deterministically when `--mode` is absent; an explicit
14
- mode remains authoritative. Preflight asks zero to four questions and may not repeat facts already present
15
- in the user prompt or a fetched source.
13
+ - **Report v4 answer-first decisions** reports now lead with the requested deliverables and chair reasoning,
14
+ substitute readable claim labels for internal ids, dedupe and cap claim-named conditions, frame/cap risks,
15
+ summarize each council seat (including a plain-language `weak_seat` warning), and distinguish FACTUAL
16
+ claims from JUDGMENT calls so verification priority and coverage describe checkable facts honestly.
17
+ Deliverable detection now combines model-backed readings with a widened fallback that hears requests for
18
+ standout or "ultra-level" features.
19
+ - `council` now includes the complete source-investigation path, 8–10-call allowance, 12-call repair budget,
20
+ and 45-minute deadline. `research` remains accepted as a compatibility alias with identical behavior.
21
+ Preflight asks zero to four questions and may not repeat facts already present in the user prompt or a
22
+ fetched source.
16
23
  - Reader-facing report sections use human claim text instead of internal graph ids such as `G1`; raw ids
17
24
  remain in machine artifacts and the Markdown technical audit.
18
25
 
package/README.md CHANGED
@@ -50,9 +50,8 @@ stop copy-pasting between them by hand.
50
50
  - **Evidence-grounded decisions.** Supply local sources with `--evidence`; Aiki records their paths and
51
51
  hashes, checks freshness and citations, independently verifies selected load-bearing claims, and shows the
52
52
  remaining coverage gaps before the chair.
53
- - **Three bounded modes.** Use `quick` for one structured analyst, `council` for bounded multi-model
54
- deliberation, or `research` for current-fact work. Explicit research wording selects `research`
55
- deterministically; `--mode` always overrides it.
53
+ - **Two bounded behaviors.** Use `quick` for one structured analyst or `council` for the full multi-model
54
+ decision council with source investigation. `research` remains accepted as an alias for `council`.
56
55
  - **A decision dossier, not an essay.** Reports lead with the recommendation, verified evidence coverage,
57
56
  decisive facts, first action, strongest counter-case, and critical unknowns. Financial and threshold-heavy
58
57
  decisions can include graph-anchored numbers, payback, option commitments, and a go/no-go tripwire.
@@ -236,7 +235,7 @@ Plain text is never charged silently — you get a confirm step before any run s
236
235
  aiki run idea-refinement "a fridge-photo-to-recipe app for busy parents"
237
236
  aiki run idea-refinement "an early idea" --mode quick # one structured analyst; no council claim
238
237
  aiki run idea-refinement ./idea.md
239
- aiki run idea-refinement ./idea.md --mode research --evidence ./research/ # grounded, source-verifying council
238
+ aiki run idea-refinement ./idea.md --mode council --evidence ./research/ # grounded, source-verifying council
240
239
  aiki run code-review --base main # review this branch vs main
241
240
  aiki run code-review --diff ./changes.patch # review a patch file
242
241
  aiki run code-review --cheap # Gemini+Codex review, Claude judges only disputes (~⅓ the Opus)
@@ -251,7 +250,7 @@ reads provider credential directories.
251
250
 
252
251
  Public `http(s)` links in an idea are snapshotted once before model calls and stored in
253
252
  `00a-url-sources.json`; every preflight reader and analyst sees that same text. npm package links use the
254
- registry metadata API. A snapshot records `FETCHED`, `BLOCKED`, or `FAILED` honestly. In research mode,
253
+ registry metadata API. A snapshot records `FETCHED`, `BLOCKED`, or `FAILED` honestly. In full council mode,
255
254
  an unreadable supplied link stops before paid calls and asks for pasted text or a public export instead of
256
255
  letting the council guess or grill you for facts that should have come from the link.
257
256
 
@@ -269,10 +268,9 @@ only the disputes → report.
269
268
  complementary analyst lanes → deterministic claim/evidence graph audit → only decision-critical verification
270
269
  or rebuttal → evidence-linked chair → validation planner. The report is a graph-backed **decision dossier**,
271
270
  not an essay.
272
- Choose `--mode quick` for one structured analyst, `--mode council` for the full decision council, or
273
- `--mode research` for source-grounded current-fact work. Without a flag, the default is council unless the
274
- request explicitly asks to research, browse, look up, or check current sources; that conservative rule is
275
- deterministic, and an explicit flag wins:
271
+ Choose `--mode quick` for one structured analyst or `--mode council` for the full source-investigating decision
272
+ council. `--mode research` is retained as a compatibility alias and runs the same council. Without a flag, the
273
+ default is the full council:
276
274
 
277
275
  - a **reader-first decision card** — recommendation, verified evidence coverage, decisive facts, first action,
278
276
  strongest counter-case, and critical unknowns before audit detail
@@ -360,8 +358,8 @@ This is the part that makes aiki trustworthy to point at a real repo:
360
358
  ## Costs & limits
361
359
 
362
360
  - **Runs cost real model calls** against your existing CLI subscriptions/quota. Idea refinement is nominally
363
- **3 calls in quick**, **6–8 in council**, or **8–10 in research** (schema repairs can add calls within the
364
- mode-aware budget); code review is about **5**. `aiki run` shows the mode, range, budget, and reserved
361
+ **3 calls in quick** or **8–10 in the full council** (`research` is the same behavior; schema repairs can add
362
+ calls within the mode-aware budget); code review is about **5**. `aiki run` shows the mode, range, budget, and reserved
365
363
  chair/planner calls before asking to confirm (skip with `--yes`).
366
364
  - **Not a general assistant.** Questions and "explore my whole codebase" requests are redirected, not answered
367
365
  — aiki reviews a *diff* and vets a *stated idea*.
@@ -118,7 +118,7 @@ function blindId(seed) {
118
118
  return createHash('sha256').update(seed).digest('hex').slice(0, 12).toUpperCase();
119
119
  }
120
120
  /** Remove the explicit identifiers forbidden by BENCHMARK-IDEA-V3.md §4 before human rating. §4 bars
121
- * provider/model names, run ids, arm labels, AND costs — so the R7 dossier's §9 "Run details" cost
121
+ * provider/model names, run ids, arm labels, AND costs — so the dossier's "Run details" cost
122
122
  * block (mode, call counts, categories, per-provider calls, model time, degradation flags) and the
123
123
  * inline `> ⚠ DEGRADED: <flag tokens>` callouts are redacted too; the DEGRADED marker and any prose
124
124
  * note stay, since those are quality self-assessments raters legitimately read. */
package/dist/cli/index.js CHANGED
@@ -51,7 +51,7 @@ program
51
51
  .option('--head <ref>', 'code-review: head git ref to diff to (default HEAD)')
52
52
  .option('--diff <file>', 'code-review: review a patch file instead of computing a git diff')
53
53
  .option('--evidence <path>', 'idea-refinement: local source file/directory (stores paths + hashes, not copies)')
54
- .option('--mode <mode>', 'idea-refinement: quick | council | research (default council; explicit research wording selects research)')
54
+ .option('--mode <mode>', 'idea-refinement: quick | council (research is an alias for council)')
55
55
  .option('--cheap', 'code-review: Gemini+Codex review, Claude judges only disputes (~⅓ the Opus; experimental)')
56
56
  .option('--yes', 'skip the run-cost confirmation prompt')
57
57
  .action(async (workflow, input, opts) => {
package/dist/cli/run.js CHANGED
@@ -21,7 +21,7 @@ export function estimateRun(workflow, opts = {}) {
21
21
  return {
22
22
  calls: plan.maxCalls,
23
23
  opus: mode === 'quick' ? 1 : 2,
24
- minCalls: mode === 'research' ? 8 : plan.baseCalls,
24
+ minCalls: plan.maxCalls - plan.reservedCalls,
25
25
  reserved: plan.reservedCalls,
26
26
  };
27
27
  }
@@ -3,7 +3,7 @@ import { basename, join } from 'node:path';
3
3
  import { DISPLAY_NAME } from '../providers/types.js';
4
4
  import { RoleOutput as RoleOutputSchema } from '../schemas/index.js';
5
5
  import { deriveAudit, deriveScorecard, mergeOpenQuestions } from '../orchestration/stages/s10-render.js';
6
- import { readerClaimLabel, readerClaimRefs, renderDecisionDossierMarkdown, stripReaderClaimIds } from '../orchestration/decision-dossier.js';
6
+ import { buildReaderProjection, claimLookup, readerClaimLabel, readerClaimRefs, renderDecisionDossierMarkdown, stripReaderClaimIds } from '../orchestration/decision-dossier.js';
7
7
  import { IDEA_RUBRIC } from '../workflows/idea-refinement.js';
8
8
  import { listArtifacts, readJsonArtifact } from '../storage/runs-read.js';
9
9
  import { adaptIdeaOutput, adaptLegacyDecisionGraph } from '../orchestration/legacy-idea-adapter.js';
@@ -377,12 +377,16 @@ function renderDossierIdeaBody(report) {
377
377
  const criticalUnknowns = report.criticalUnknowns?.length ? report.criticalUnknowns : report.openQuestions.slice(0, 3);
378
378
  const tone = dossier.recommendation.status === 'ACCEPTED' ? 'good'
379
379
  : dossier.recommendation.status === 'REJECTED' ? 'risk' : 'caution';
380
- const conditions = dossier.recommendation.conditions.length
381
- ? `<details class="decision-details"><summary>Conditions and decision state</summary><div><p><strong>Internal state:</strong> ${escapeHtml(dossier.recommendation.status)}</p><ul>${dossier.recommendation.conditions.map((condition) => `<li>${escapeHtml(condition.text)}</li>`).join('')}</ul></div></details>`
380
+ const labelFor = claimLookup(report);
381
+ // Substitute ids then dedupe before escaping, mirroring the markdown renderer: old stored artifacts
382
+ // may carry duplicate / bare-G# condition strings.
383
+ const conditionItems = [...new Set(dossier.recommendation.conditions.map((condition) => stripReaderClaimIds(condition.text, labelFor)))];
384
+ const conditions = conditionItems.length
385
+ ? `<details class="decision-details"><summary>Conditions and decision state</summary><div><p><strong>Internal state:</strong> ${escapeHtml(dossier.recommendation.status)}</p><ul>${conditionItems.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul></div></details>`
382
386
  : '';
383
387
  const startHere = dossier.experiments.actions[0]?.action ?? 'No executable next step was produced.';
384
- const recommendationLead = stripReaderClaimIds(dossier.recommendation.reason);
385
- const recommendationDetail = stripReaderClaimIds(dossier.recommendation.summary);
388
+ const recommendationLead = stripReaderClaimIds(dossier.recommendation.reason, labelFor);
389
+ const recommendationDetail = stripReaderClaimIds(dossier.recommendation.summary, labelFor);
386
390
  const factLabels = ['Decisive result', 'Consequence', 'Supporting signal'];
387
391
  const facts = keyFindings.slice(0, 3).map((finding, index) => `<article class="decision-fact"><span>${factLabels[index]}</span><p>${escapeHtml(finding)}</p></article>`).join('');
388
392
  const snapshot = report.decisionSnapshot;
@@ -470,7 +474,10 @@ function renderDossierIdeaBody(report) {
470
474
  <span>by provider ${escapeHtml(Object.entries(report.receipt.byProvider).map(([provider, count]) => `${providerName(provider)} ${count}`).join(', ') || 'none')}</span>
471
475
  <span>model time ${(report.receipt.modelTimeMs / 1000).toFixed(1)}s</span>
472
476
  </div>${report.flags.length ? `<div class="warns">${report.flags.map((flag) => `<span class="warn">⚑ ${escapeHtml(flag)}</span>`).join('')}</div>` : '<p class="muted">No degradation flags.</p>'}`;
473
- const risks = dossierTable(['Risk', 'Severity'], report.risks.map((item) => [item.risk, item.severity]));
477
+ const shownRisks = report.risks.slice(0, 8);
478
+ const risks = `${dossierTable(['Risk', 'Severity'], shownRisks.map((item) => [item.risk, item.severity]))}${report.risks.length > shownRisks.length
479
+ ? `<p class="muted">${report.risks.length - shownRisks.length} lower-severity items — more in the technical audit (full list in the stored JSON).</p>`
480
+ : ''}`;
474
481
  const questions = report.openQuestions.length
475
482
  ? `<ul class="checks">${report.openQuestions.map((question) => `<li>${escapeHtml(question)}</li>`).join('')}</ul>`
476
483
  : '<p class="muted">No verdict-flipping open question was recorded.</p>';
@@ -500,7 +507,37 @@ function renderDossierIdeaBody(report) {
500
507
  section('08', 'What the council added', `${shared}${unique}<h3>Verified unique contributions</h3>${contributions}`, 340),
501
508
  section('09', 'Run details', runDetails, 380),
502
509
  ].join('');
503
- return `${stripReaderClaimIds(readerBody)}${section('10', 'Technical audit', technical, 420)}`;
510
+ return `${stripReaderClaimIds(readerBody, labelFor)}${section('10', 'Technical audit', technical, 420)}`;
511
+ }
512
+ function renderReaderBriefIdeaBody(report) {
513
+ const dossier = report.dossier;
514
+ const projection = buildReaderProjection(report);
515
+ const tone = dossier.recommendation.status === 'ACCEPTED' ? 'good'
516
+ : dossier.recommendation.status === 'REJECTED' ? 'risk' : 'caution';
517
+ const hero = `<section class="verdict tone-${tone} reveal" style="animation-delay:60ms">
518
+ <span class="pill">Recommendation</span>
519
+ <p class="verdict-text">${escapeHtml(projection.headline)}</p>
520
+ <p class="verdict-detail">${escapeHtml(projection.bottomLine)}</p>
521
+ </section>`;
522
+ const warnings = projection.warnings.length || projection.notices.length
523
+ ? `<div class="warns">${projection.warnings.map((warning) => `<span class="warn">⚑ ${escapeHtml(warning.message)}</span>`).join('')}${projection.notices.map((notice) => `<span class="warn">ⓘ ${escapeHtml(notice.message)}</span>`).join('')}</div>`
524
+ : '';
525
+ const snapshot = projection.snapshot ? section('', 'Decision numbers', `${dossierTable(['Metric', 'Value', 'What it means'], projection.snapshot.decisiveNumbers.map((item) => [item.label, item.value, item.meaning]))}${projection.snapshot.payback ? `<div class="action-callout"><span>Payback · ${escapeHtml(projection.snapshot.payback.status.replaceAll('_', ' '))}</span><p>${escapeHtml(projection.snapshot.payback.result)} — ${escapeHtml(projection.snapshot.payback.basis)}</p></div>` : ''}<h3>Options at a glance</h3>${dossierTable(['Path', 'Commitment', 'Basis', 'Trade-off'], projection.snapshot.options.map((item) => [item.label, item.commitment, item.commitmentKind.replace('_', ' '), item.tradeoff]))}${projection.snapshot.tripwire ? `<div class="action-callout"><span>Go/no-go tripwire</span><p><strong>${escapeHtml(projection.snapshot.tripwire.metric)}: ${escapeHtml(projection.snapshot.tripwire.threshold)}</strong> — ${escapeHtml(projection.snapshot.tripwire.decisionRule)}</p></div>` : ''}`, 80) : '';
526
+ const editorial = projection.sections.map((item, index) => section(String(index + 1).padStart(2, '0'), item.heading, `<p class="lede">${escapeHtml(item.summary)}</p>${item.bullets.length
527
+ ? `<ul class="reasons">${item.bullets.map((bullet) => `<li>${escapeHtml(bullet)}</li>`).join('')}</ul>`
528
+ : ''}`, 100 + index * 40)).join('');
529
+ const backlog = projection.featureBacklog;
530
+ const features = backlog ? section('', 'Feature priorities', `<div class="feature-groups">${[
531
+ ['MUST', 'Build for the first useful release', backlog.must],
532
+ ['SHOULD', 'Add after the golden path is stable', backlog.should],
533
+ ['LATER', 'Keep outside the current build', backlog.later],
534
+ ].filter(([, , items]) => items.length).map(([priority, description, items]) => `<section class="feature-group priority-${priority.toLowerCase()}"><header><div><span>${priority}</span><h4>${description}</h4></div><strong>${items.length}</strong></header><ul>${items.map((item) => `<li><div class="feature-title"><strong>${escapeHtml(item.feature)}</strong><span>${item.effort}</span></div><p>${escapeHtml(item.user_value)}</p><small>${escapeHtml(item.rationale)}</small></li>`).join('')}</ul></section>`).join('')}</div>${backlog.wont.length ? `<h3>Not in this scope</h3><ul class="checks">${backlog.wont.map((item) => `<li><strong>${escapeHtml(item.feature)}</strong> — ${escapeHtml(item.reason)}</li>`).join('')}</ul>` : ''}`, 340) : '';
535
+ const buildPlan = projection.implementationPlan ? section('', 'Build plan', `<ol class="milestone-list">${projection.implementationPlan.milestones.map((milestone) => `<li><div class="milestone-marker"><span>${String(milestone.order).padStart(2, '0')}</span><small>${escapeHtml(milestone.timebox)}</small></div><article><h4>${escapeHtml(milestone.outcome)}</h4><ul>${milestone.tasks.map((task) => `<li>${escapeHtml(task)}</li>`).join('')}</ul><div class="acceptance"><span>Done when</span><p>${escapeHtml(milestone.acceptance_test)}</p></div></article></li>`).join('')}</ol>`, 380) : '';
536
+ const caveats = projection.caveats.length ? section('', 'Top caveats', `<ul class="checks">${projection.caveats.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`, 420) : '';
537
+ const sources = projection.sources.length ? section('', 'Sources', `<ul class="agree">${projection.sources.map((source) => `<li><p>${source.url ? `<a href="${escapeHtml(source.url)}" rel="noopener noreferrer">${escapeHtml(source.label)}</a>` : escapeHtml(source.label)}</p>${source.citedFor.length ? `<small>Cited for: ${source.citedFor.map(escapeHtml).join('; ')}</small>` : ''}</li>`).join('')}</ul>`, 460) : '';
538
+ const nextStep = section('', 'Next step', `<div class="action-callout"><span>Do this now</span><p>${escapeHtml(projection.nextStep)}</p></div>`, 500);
539
+ const audit = `<details class="fold council-audit"><summary>Council audit — reasoning, evidence, dissent, and run receipt</summary><div class="fold-body">${renderDossierIdeaBody(report)}</div></details>`;
540
+ return `${hero}${warnings}${snapshot}${editorial}${features}${buildPlan}${caveats}${sources}${nextStep}${audit}`;
504
541
  }
505
542
  function renderLegacyIdeaBody(view) {
506
543
  const risks = view.risks ?? [];
@@ -891,19 +928,22 @@ function renderTechnical(view) {
891
928
  export function renderCouncilHtml(view) {
892
929
  const isIdea = view.workflow !== 'code-review';
893
930
  const quick = isIdea && view.mode === 'quick';
931
+ const hasReaderBrief = Boolean(view.decisionReport?.dossier.readerBrief);
894
932
  const kicker = quick ? 'aiki · quick analysis' : isIdea ? 'aiki · idea refinement' : 'aiki · code review';
895
933
  const title = isIdea && view.topic ? cleanTopic(view.topic) : (isIdea ? 'Idea refinement' : 'Code review');
896
934
  const panel = view.columns.map((c) => c.title);
897
- const metaBits = [
935
+ const metaBits = hasReaderBrief ? [] : [
898
936
  panel.length ? `${quick ? 'Analyst' : 'Panel'}: ${panel.join(' · ')}` : '',
899
937
  !quick && view.moderator ? `Chair: ${view.moderator}` : '',
900
938
  view.calls,
901
939
  ].filter(Boolean);
902
- const flags = view.flags.length
940
+ const flags = !hasReaderBrief && view.flags.length
903
941
  ? `<div class="warns">${view.flags.map((f) => `<span class="warn">⚑ ${escapeHtml(f.replaceAll('_', ' '))}</span>`).join('')}</div>`
904
942
  : '';
905
943
  const body = isIdea && view.decisionReport?.dossier
906
- ? renderDossierIdeaBody(view.decisionReport)
944
+ ? view.decisionReport.dossier.readerBrief
945
+ ? renderReaderBriefIdeaBody(view.decisionReport)
946
+ : renderDossierIdeaBody(view.decisionReport)
907
947
  : quick ? renderQuickIdeaBody(view) : isIdea ? renderIdeaBody(view) : renderReviewBody(view);
908
948
  // Embed the report as Markdown for the Copy button. Escape "<" so a "</script>" in content can't break out.
909
949
  const md = JSON.stringify(councilMarkdown(view)).replace(/</g, '\\u003c');
@@ -928,14 +968,14 @@ html{-webkit-text-size-adjust:100%;}
928
968
  body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:16px;line-height:1.6;}
929
969
  main{max-width:940px;margin:0 auto;padding:34px 24px 90px;}
930
970
  a{color:var(--accent);}
931
- h1{font-family:var(--serif);font-weight:600;letter-spacing:-.025em;}
971
+ h1{font-family:var(--sans);font-weight:650;letter-spacing:-.01em;}
932
972
  h2,h3,h4{font-family:var(--sans);font-weight:650;letter-spacing:-.01em;}
933
973
  p{margin:0 0 .6em;}
934
974
 
935
975
  /* masthead */
936
976
  .mast{border-bottom:1px solid var(--ink);padding-bottom:22px;margin-bottom:30px;}
937
977
  .kicker{font-family:var(--mono);font-size:11.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--accent);}
938
- .mast h1{font-size:clamp(32px,5.4vw,50px);line-height:1.05;margin:12px 0 18px;max-width:18ch;}
978
+ .mast h1{font-size:clamp(28px,4.6vw,42px);line-height:1.12;margin:12px 0 16px;}
939
979
  .mmeta{display:flex;flex-wrap:wrap;gap:7px;}
940
980
  .mmeta span{font-family:var(--mono);font-size:11.5px;color:var(--soft);border:1px solid var(--line);background:var(--panel);border-radius:100px;padding:3px 10px;}
941
981
  .warns{margin-top:12px;display:flex;flex-wrap:wrap;gap:8px;}
@@ -15,7 +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
+ import { callCategory, defaultBudgetFor, defaultDeadlineFor, IDEA_MODE_PLANS, isOptionalStage, LEGACY_DEFAULT_BUDGET } from './modes.js';
19
19
  /** Bracket a stage call with the TUI's start/end events (no-op headless). A thrown StageError marks
20
20
  * the row `failed` and re-propagates unchanged — the engine's failure handling is untouched. */
21
21
  export async function runStage(ctx, id, fn) {
@@ -100,7 +100,7 @@ export class RunCtx {
100
100
  this.budget = { limit: opts.budget ?? defaultBudgetFor(opts.workflow, this.mode), used: 0 };
101
101
  this.handles = new Map(opts.handles.map((h) => [h.id, h]));
102
102
  this.signal = opts.signal;
103
- this.deadlineMs = opts.deadlineMs ?? DEFAULT_DEADLINE_MS;
103
+ this.deadlineMs = opts.deadlineMs ?? defaultDeadlineFor(opts.workflow, this.mode);
104
104
  this.now = opts.now ?? Date.now;
105
105
  this.deadlineAt = this.now() + this.deadlineMs;
106
106
  this.replay = opts.replay;
@@ -174,7 +174,7 @@ export class RunCtx {
174
174
  timeoutMs: req.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS,
175
175
  expectJson: req.expectJson,
176
176
  readOnly: true,
177
- research: this.mode === 'research' && stage.startsWith('S4'),
177
+ research: this.workflow === 'idea-refinement' && this.mode !== 'quick' && stage.startsWith('S4'),
178
178
  signal: this.signal, // Ctrl+C kills the in-flight child (T8); undefined headless
179
179
  }, handle.flags);
180
180
  // Only REAL calls count toward the ledger/budget; a replayed call is free and already recorded in