aiki-cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +16 -8
- package/dist/bench/idea-v3-rating.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/run.js +10 -7
- package/dist/council/view.js +146 -46
- package/dist/orchestration/context.js +3 -3
- package/dist/orchestration/decision-dossier.js +467 -80
- package/dist/orchestration/decision-graph.js +31 -0
- package/dist/orchestration/legacy-idea-adapter.js +3 -0
- package/dist/orchestration/modes.js +16 -4
- package/dist/orchestration/preflight.js +43 -9
- package/dist/orchestration/quick-analysis.js +35 -6
- package/dist/orchestration/stages/s10-render.js +179 -79
- package/dist/orchestration/stages/s4-analyze.js +3 -1
- package/dist/orchestration/stages/s6-positions.js +18 -0
- package/dist/orchestration/stages/s7-decision-graph.js +3 -0
- package/dist/orchestration/stages/s8-verify.js +26 -9
- package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
- package/dist/orchestration/stages/s9-judge.js +36 -13
- package/dist/orchestration/stages/s9b-plan.js +208 -35
- package/dist/orchestration/url-sources.js +200 -0
- package/dist/schemas/index.js +134 -6
- package/dist/skills/idea-refinement/analyst.md +5 -5
- package/dist/skills/idea-refinement/chair.md +18 -0
- package/dist/skills/idea-refinement/economics-delivery.md +11 -0
- package/dist/skills/idea-refinement/market-adoption.md +12 -0
- package/dist/skills/idea-refinement/planner.md +25 -19
- package/dist/skills/idea-refinement/rebuttal.md +15 -0
- package/dist/skills/idea-refinement/verifier.md +17 -0
- package/dist/storage/runs.js +2 -1
- package/dist/tui/app.js +19 -4
- package/dist/workflows/idea-refinement.js +51 -15
- package/package.json +1 -1
package/dist/tui/app.js
CHANGED
|
@@ -23,7 +23,7 @@ import { computeDiff, computeWorkingTreeDiff, detectRepoStatus } from '../orches
|
|
|
23
23
|
import { loadCouncilView } from '../council/view.js';
|
|
24
24
|
import { openCouncilHtml } from '../council/open.js';
|
|
25
25
|
import { readJsonArtifact } from '../storage/runs-read.js';
|
|
26
|
-
import { defaultBudgetFor } from '../orchestration/modes.js';
|
|
26
|
+
import { defaultBudgetFor, defaultDeadlineFor, inferIdeaMode } from '../orchestration/modes.js';
|
|
27
27
|
import { GLYPH, displayNames, elapsedLabel, initTimeline, markEnd, markStart, progressBar, runningPhrase, totalElapsed } from './timeline.js';
|
|
28
28
|
import { formatCompletion, formatError } from './format.js';
|
|
29
29
|
import { COMMANDS, PRODUCT_LINE, filterCommands, parseCommand, routeInput, scopeRedirect, suggestCommand } from './smart-entry.js';
|
|
@@ -260,7 +260,20 @@ export function App(props) {
|
|
|
260
260
|
setPhase('clarify');
|
|
261
261
|
}),
|
|
262
262
|
};
|
|
263
|
-
const ctx = new RunCtx({
|
|
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
|
+
});
|
|
264
277
|
ctxRef.current = ctx;
|
|
265
278
|
setWorkflow(wf);
|
|
266
279
|
setRows(initTimeline(stages, rs, available));
|
|
@@ -286,7 +299,9 @@ export function App(props) {
|
|
|
286
299
|
setPhase('finished');
|
|
287
300
|
});
|
|
288
301
|
};
|
|
289
|
-
const startIdea = (text) =>
|
|
302
|
+
const startIdea = (text) => {
|
|
303
|
+
startRun('idea-refinement', text, null, runIdeaRefinement, IDEA_STAGES, undefined, inferIdeaMode(text));
|
|
304
|
+
};
|
|
290
305
|
const startCodeReview = async (action) => {
|
|
291
306
|
if (!repo || !repo.defaultBranch) {
|
|
292
307
|
setRouterMessage('code review needs a git repo with a detectable default branch');
|
|
@@ -447,7 +462,7 @@ export function App(props) {
|
|
|
447
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));
|
|
448
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 ? (
|
|
449
464
|
/* V10 confirm gate — nothing spends model calls until Enter. */
|
|
450
|
-
_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 up to ", budgetOverride ?? defaultBudgetFor('idea-refinement',
|
|
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 && (
|
|
451
466
|
/* V10 live command palette — filtered as you type; ↑↓ move, Tab completes, Enter runs. */
|
|
452
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) => {
|
|
453
468
|
const color = r.status === 'done' ? 'green' : r.status === 'failed' ? 'red' : r.status === 'running' ? 'yellow' : 'gray';
|
|
@@ -15,6 +15,7 @@ import { loadSkill } from '../orchestration/skills.js';
|
|
|
15
15
|
import { buildLanePrompts } from '../orchestration/idea-lanes.js';
|
|
16
16
|
import { preflight, renderDecisionInput } from '../orchestration/preflight.js';
|
|
17
17
|
import { buildQuickPrompt, quickActionPlan, quickJudgeReport, s4QuickAnalyze } from '../orchestration/quick-analysis.js';
|
|
18
|
+
import { snapshotUrlSources } from '../orchestration/url-sources.js';
|
|
18
19
|
/** Idea-vetting core rubric: 13 mandatory coverage items. S0 adds 3-5 domain dimensions per run.
|
|
19
20
|
* Inlined here (like the S4 template) while the skill/`rubric.json` loader (§11)
|
|
20
21
|
* is deferred; it moves to skills/idea-refinement/rubric.json when that loader lands. */
|
|
@@ -41,7 +42,7 @@ export function buildIdeaRubric(domainDimensions = []) {
|
|
|
41
42
|
}
|
|
42
43
|
/** Idea S4 analyst template. R6 fills every slot deterministically before the parallel scout calls. */
|
|
43
44
|
export const IDEA_S4_ANALYST_TEMPLATE = `ROLE: Independent analyst on a decision panel. You work ALONE; you will not see
|
|
44
|
-
other analysts' output.
|
|
45
|
+
other analysts' output. Improve the strongest version and pressure-test it honestly within your assigned lane.
|
|
45
46
|
|
|
46
47
|
TASK CONTRACT: {{INTENT_CONTRACT_JSON}}
|
|
47
48
|
INPUT DOCUMENT: read the file at {{INPUT_PATH}}{{SKILL}}
|
|
@@ -53,7 +54,10 @@ Produce ONLY JSON matching {{S4_SCHEMA_REF}} with:
|
|
|
53
54
|
- task_echo: restate the task in ≤2 sentences (drift check).
|
|
54
55
|
- strongest_version: the best honest version of this idea in ≤150 words.
|
|
55
56
|
- positions: explicit claim positions with local_id, proposition, rubric dimension_id, stance
|
|
56
|
-
SUPPORT|OPPOSE|MIXED|UNKNOWN, basis EVIDENCE|INFERENCE|ASSUMPTION,
|
|
57
|
+
SUPPORT|OPPOSE|MIXED|UNKNOWN, basis EVIDENCE|INFERENCE|ASSUMPTION, nature FACTUAL when the position
|
|
58
|
+
asserts something independently checkable about the world (rules, deadlines, prices, observed CLI
|
|
59
|
+
behavior, published numbers) or JUDGMENT for a design choice, recommendation, prioritization, or
|
|
60
|
+
prediction (when unsure use JUDGMENT), load_bearing, if_false
|
|
57
61
|
STOP|PIVOT|CONDITION|MINOR, concise reasoning, evidence_ids, and depends_on position ids.
|
|
58
62
|
- evidence: evidence cards {id, claim_supported, source_kind USER|PRIMARY|SECONDARY|MODEL_KNOWLEDGE,
|
|
59
63
|
support SUPPORTS|CONTRADICTS|CONTEXT_ONLY (exact token, no extra words), freshness CURRENT|DATED|UNKNOWN,
|
|
@@ -65,8 +69,11 @@ Produce ONLY JSON matching {{S4_SCHEMA_REF}} with:
|
|
|
65
69
|
- coverage: one entry per rubric dimension {dimension_id, status COVERED|NOT_APPLICABLE,
|
|
66
70
|
position_ids ([] when none), rationale (required for NOT_APPLICABLE)}.
|
|
67
71
|
- decision_questions: questions {question, claim_ids} whose answers could change the verdict.
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
- deliverable_proposals: when requested_outputs includes FEATURE_BACKLOG or IMPLEMENTATION_PLAN, propose
|
|
73
|
+
concrete user-facing work as {output,title,detail,user_value,why_distinctive,evidence_ids}; otherwise [].
|
|
74
|
+
Proposals are recommendations, not factual claims. Use evidence_ids only when evidence directly supports them.
|
|
75
|
+
Caps: at most 12 positions, 20 evidence cards, 8 calculations, 8 decision_questions, 8 deliverable_proposals.
|
|
76
|
+
Rules: no unsupported hype, no summaries of your own output, no markdown, JSON only.`;
|
|
70
77
|
/**
|
|
71
78
|
* Resolve the {{SKILL}} slot before deterministic structural fill. An empty skill collapses the slot
|
|
72
79
|
* to the pre-skill baseline.
|
|
@@ -76,11 +83,11 @@ export function buildAnalystTemplate(skill) {
|
|
|
76
83
|
}
|
|
77
84
|
/** R6 deterministic S4 prompt fill. No model-authored prompt generation remains. */
|
|
78
85
|
export function buildAnalystPrompt(contract, inputPath, evidencePack, mode, skill) {
|
|
79
|
-
const modeRules = mode
|
|
80
|
-
? `\n\nMODE:
|
|
86
|
+
const modeRules = mode !== 'quick'
|
|
87
|
+
? `\n\nMODE: full council. Use provider-native read-only source investigation when available. Every current
|
|
81
88
|
fact must have an independently checkable locator and access date. If investigation is unavailable,
|
|
82
89
|
leave the claim unverified; never invent a source.`
|
|
83
|
-
: `\n\nMODE:
|
|
90
|
+
: `\n\nMODE: quick. Analyze independently and keep unsupported claims visibly unverified.`;
|
|
84
91
|
const prompt = buildAnalystTemplate(skill)
|
|
85
92
|
.replace('{{INTENT_CONTRACT_JSON}}', JSON.stringify(contract))
|
|
86
93
|
.replace('{{INPUT_PATH}}', inputPath)
|
|
@@ -106,14 +113,45 @@ export const IDEA_STAGES = [
|
|
|
106
113
|
{ id: 'S9b', label: 'Validation plan', role: 'judge' },
|
|
107
114
|
{ id: 'S10', label: 'Report', role: null },
|
|
108
115
|
];
|
|
116
|
+
/** Record guarantees observed at the surviving typed S4 boundary; never infer them from prompts. */
|
|
117
|
+
export function recordIdeaOutcomeFlags(ctx, contract, seats) {
|
|
118
|
+
if (ctx.mode === 'quick')
|
|
119
|
+
return;
|
|
120
|
+
const requested = (contract.requested_outputs ?? [])
|
|
121
|
+
.filter((output) => output === 'FEATURE_BACKLOG' || output === 'IMPLEMENTATION_PLAN');
|
|
122
|
+
if (requested.some((output) => seats.some((seat) => !seat.output.deliverable_proposals.some((proposal) => proposal.output === output)))) {
|
|
123
|
+
ctx.addFlag('deliverable_gap');
|
|
124
|
+
}
|
|
125
|
+
const grounded = seats.some((seat) => {
|
|
126
|
+
const cited = new Set(seat.output.positions.flatMap((position) => position.evidence_ids));
|
|
127
|
+
return seat.output.evidence.some((card) => cited.has(card.id)
|
|
128
|
+
&& (card.source_kind === 'PRIMARY' || card.source_kind === 'SECONDARY')
|
|
129
|
+
&& card.freshness === 'CURRENT'
|
|
130
|
+
&& Boolean(card.accessed_at && (card.url || card.locator))
|
|
131
|
+
&& card.support !== 'CONTEXT_ONLY');
|
|
132
|
+
});
|
|
133
|
+
if (!grounded)
|
|
134
|
+
ctx.addFlag('research_ungrounded');
|
|
135
|
+
}
|
|
109
136
|
/** Runs the full idea-refinement pipeline S0–S10. Throws on any fatal condition; the engine's
|
|
110
137
|
* `executeRun` wrapper turns that into a graceful failure + meta. Each stage is wrapped in
|
|
111
138
|
* `runStage` so the TUI timeline (T8) gets start/end events; headless, that's a no-op. */
|
|
112
139
|
export async function runIdeaRefinement(ctx, input) {
|
|
113
140
|
if (ctx.evidencePack)
|
|
114
141
|
await ctx.writer.writeInput('evidence-pack.json', JSON.stringify(ctx.evidencePack, null, 2));
|
|
115
|
-
const
|
|
116
|
-
|
|
142
|
+
const urlSources = await snapshotUrlSources(input);
|
|
143
|
+
await ctx.writer.writeJson('url-sources', urlSources);
|
|
144
|
+
const unreadableSources = urlSources.sources.filter((source) => source.status !== 'FETCHED');
|
|
145
|
+
const needsSourceFallback = ctx.mode !== 'quick' && unreadableSources.length > 0;
|
|
146
|
+
const hasReadableResearch = Boolean(ctx.evidencePack?.files.length)
|
|
147
|
+
|| urlSources.sources.some((source) => source.status === 'FETCHED');
|
|
148
|
+
const canSearch = ctx.roles.s4.includes('codex');
|
|
149
|
+
if (needsSourceFallback && !canSearch && !hasReadableResearch) {
|
|
150
|
+
const details = unreadableSources.map((source) => `${source.url} (${source.status}: ${source.error})`).join('; ');
|
|
151
|
+
throw new StageError('S0', 'SOURCE_UNREADABLE', `source investigation stopped before model calls because no source was readable and Codex search is unavailable${details ? `: ${details}` : ''}. Paste the relevant text, provide a public export, or enable Codex, then rerun.`);
|
|
152
|
+
}
|
|
153
|
+
const { contract, brief } = await runStage(ctx, 'S0', () => preflight(ctx, input, IDEA_RUBRIC.map((item) => item.label), urlSources));
|
|
154
|
+
const grilledInput = renderDecisionInput(input, brief, urlSources);
|
|
117
155
|
// Persist the input as a file so S4's "read the file at {{INPUT_PATH}}" resolves (not a stage).
|
|
118
156
|
await ctx.writer.writeInput('idea.md', input);
|
|
119
157
|
await ctx.writer.writeInput('idea-brief.md', grilledInput);
|
|
@@ -144,26 +182,24 @@ export async function runIdeaRefinement(ctx, input) {
|
|
|
144
182
|
return report;
|
|
145
183
|
});
|
|
146
184
|
const actionPlan = await runStage(ctx, 'S9b', async () => {
|
|
147
|
-
const plan = quickActionPlan(ctx, quick.seat.provider, quick.decision, graph);
|
|
185
|
+
const plan = quickActionPlan(ctx, quick.seat.provider, quick.decision, graph, contract);
|
|
148
186
|
await ctx.writer.writeJson('action-plan', plan);
|
|
149
187
|
return plan;
|
|
150
188
|
});
|
|
151
189
|
await runStage(ctx, 'S10', () => s10Render(ctx, { contract, seats: kept, graph, verifications, rebuttals, judgeReport, actionPlan, rubric, original: input }));
|
|
152
190
|
return;
|
|
153
191
|
}
|
|
154
|
-
if (ctx.mode === 'research' && !ctx.evidencePack && !ctx.available().includes('codex')) {
|
|
155
|
-
ctx.addFlag('research_ungrounded');
|
|
156
|
-
}
|
|
157
192
|
const analystPrompt = buildAnalystPrompt(contract, inputPath, ctx.evidencePack, ctx.mode, analystSkill);
|
|
158
193
|
await ctx.writer.writePrompt('analyst.md', analystPrompt);
|
|
159
194
|
const lanePrompts = buildLanePrompts(analystPrompt, rubric);
|
|
160
|
-
const seats = await runStage(ctx, 'S4', () => s4Analyze(ctx, lanePrompts));
|
|
195
|
+
const seats = await runStage(ctx, 'S4', () => s4Analyze(ctx, lanePrompts, needsSourceFallback));
|
|
161
196
|
const { kept } = await runStage(ctx, 'S5', () => s5Drift(ctx, contract, seats));
|
|
197
|
+
recordIdeaOutcomeFlags(ctx, contract, kept);
|
|
162
198
|
const positions = await runStage(ctx, 'S6', () => s6Positions(ctx, kept));
|
|
163
199
|
const graph = await runStage(ctx, 'S7', () => s7DecisionGraph(ctx, positions, rubric, contract.task));
|
|
164
200
|
const verifications = await runStage(ctx, 'S8', () => s8Verify(ctx, graph));
|
|
165
201
|
const rebuttals = await runStage(ctx, 'S8b', () => s8bRebuttal(ctx, graph, verifications, ctx.mode));
|
|
166
202
|
const judgeReport = await runStage(ctx, 'S9', () => s9Judge(ctx, contract, graph, verifications, rubric, rebuttals));
|
|
167
|
-
const actionPlan = await runStage(ctx, 'S9b', () => s9bPlan(ctx, contract, kept, graph, judgeReport));
|
|
203
|
+
const actionPlan = await runStage(ctx, 'S9b', () => s9bPlan(ctx, contract, kept, graph, judgeReport, input));
|
|
168
204
|
await runStage(ctx, 'S10', () => s10Render(ctx, { contract, seats: kept, graph, verifications, rebuttals, judgeReport, actionPlan, rubric, original: input }));
|
|
169
205
|
}
|
package/package.json
CHANGED