aiki-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -7
- package/README.md +109 -30
- package/dist/bench/arms.js +10 -9
- package/dist/bench/harness.js +13 -10
- package/dist/bench/idea-lane-rotation.js +237 -0
- package/dist/bench/idea-v3-bench.js +506 -0
- package/dist/bench/idea-v3-rating.js +582 -0
- package/dist/bench/results.js +10 -3
- package/dist/bench/scoring/decision-insights.js +112 -0
- package/dist/bench/scoring/seeded-bugs.js +4 -0
- package/dist/cli/bench.js +180 -3
- package/dist/cli/doctor.js +56 -24
- package/dist/cli/index.js +32 -7
- package/dist/cli/resolve.js +7 -6
- package/dist/cli/resume.js +18 -0
- package/dist/cli/run.js +63 -8
- package/dist/cli/version.js +8 -0
- package/dist/council/view.js +378 -109
- package/dist/orchestration/calculations.js +97 -0
- package/dist/orchestration/context.js +37 -9
- package/dist/orchestration/decision-dossier.js +262 -0
- package/dist/orchestration/decision-graph.js +256 -0
- package/dist/orchestration/engine.js +5 -2
- package/dist/orchestration/evidence-pack.js +46 -0
- package/dist/orchestration/idea-lanes.js +20 -0
- package/dist/orchestration/jsonStage.js +72 -0
- package/dist/orchestration/legacy-idea-adapter.js +102 -0
- package/dist/orchestration/modes.js +33 -0
- package/dist/orchestration/preflight.js +183 -0
- package/dist/orchestration/quick-analysis.js +81 -0
- package/dist/orchestration/stages/cr-ladder.js +80 -0
- package/dist/orchestration/stages/s10-render.js +562 -150
- package/dist/orchestration/stages/s4-analyze.js +12 -7
- package/dist/orchestration/stages/s5-drift.js +9 -9
- package/dist/orchestration/stages/s6-positions.js +10 -0
- package/dist/orchestration/stages/s7-decision-graph.js +76 -0
- package/dist/orchestration/stages/s8-verify.js +153 -46
- package/dist/orchestration/stages/s8b-rebuttal.js +208 -0
- package/dist/orchestration/stages/s9-judge.js +329 -108
- package/dist/orchestration/stages/s9b-plan.js +85 -75
- package/dist/providers/codex.js +2 -1
- package/dist/providers/spawn.js +5 -0
- package/dist/schemas/index.js +572 -13
- package/dist/skills/idea-refinement/analyst.md +18 -14
- package/dist/skills/idea-refinement/economics-delivery.md +7 -0
- package/dist/skills/idea-refinement/market-adoption.md +7 -0
- package/dist/storage/runs.js +11 -4
- package/dist/tui/app.js +37 -13
- package/dist/tui/format.js +4 -5
- package/dist/tui/smart-entry.js +17 -1
- package/dist/tui/timeline.js +2 -4
- package/dist/workflows/code-review.js +4 -2
- package/dist/workflows/idea-refinement.js +110 -46
- package/package.json +12 -4
- package/dist/orchestration/stages/s0-grill.js +0 -79
- package/dist/orchestration/stages/s1-intent.js +0 -25
- package/dist/orchestration/stages/s2-misread.js +0 -76
- package/dist/orchestration/stages/s3-prompts.js +0 -55
- package/dist/orchestration/stages/s6-claims.js +0 -56
- package/dist/orchestration/stages/s7-disagreement.js +0 -134
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
// S9b — idea validation plan. This is the one report-v3 model call after the judge: it turns the
|
|
2
2
|
// adjudicated risks, blind spots, and open questions into anchored validation actions. Rendering stays
|
|
3
|
-
// deterministic; if the planner fails or produces unanchored actions, we write
|
|
3
|
+
// deterministic; if the planner fails or produces unanchored actions, we write flagged unavailability.
|
|
4
4
|
import { ActionPlan } from '../../schemas/index.js';
|
|
5
|
+
import { z } from 'zod';
|
|
5
6
|
import { BudgetExceeded, isFatal } from '../context.js';
|
|
6
7
|
import { jsonCall } from '../jsonStage.js';
|
|
7
8
|
import { loadSkill } from '../skills.js';
|
|
8
9
|
import { mergeOpenQuestions } from './s10-render.js';
|
|
10
|
+
const PlannerOutput = z.object({
|
|
11
|
+
actions: z.array(z.object({
|
|
12
|
+
order: z.number().int().min(1).optional(),
|
|
13
|
+
action: z.string().min(1),
|
|
14
|
+
why: z.string().min(1),
|
|
15
|
+
validates: z.string().min(1),
|
|
16
|
+
effort: z.string().min(1).optional(),
|
|
17
|
+
kill_signal: z.string().min(1),
|
|
18
|
+
}).strict()).min(1).max(7),
|
|
19
|
+
sequencing_note: z.string().min(1),
|
|
20
|
+
}).strict();
|
|
9
21
|
const S9B_PROMPT = `ROLE: Validation planner. You write the next actions for a decision-maker after an
|
|
10
22
|
idea council has already debated and judged the idea. Do not write a build roadmap. Write only validation
|
|
11
23
|
actions that test unsettled risks, blind spots, or open questions. Cheapest decisive test first.{{SKILL}}
|
|
@@ -13,11 +25,13 @@ actions that test unsettled risks, blind spots, or open questions. Cheapest deci
|
|
|
13
25
|
Output ONLY JSON matching the ActionPlan schema:
|
|
14
26
|
- actions: 1-7 ordered actions, each imperative and concrete.
|
|
15
27
|
- validates MUST anchor to one of:
|
|
16
|
-
- a
|
|
28
|
+
- a graph claim id from upheld_risks, e.g. "G3"
|
|
17
29
|
- a blind spot label as "blind:<label>"
|
|
18
30
|
- an open-question prefix as "Q:<question prefix>"
|
|
19
31
|
- why ties the action to the risk, blind spot, or question.
|
|
20
32
|
- kill_signal is the result that should stop or reshape the idea.
|
|
33
|
+
- Preserve the chair's numeric distinctions: operating break-even is not capital payback, and a target cap
|
|
34
|
+
is not a known cost. Do not introduce or reinterpret a number that is absent from decision_snapshot.
|
|
21
35
|
- sequencing_note explains why this order is cheapest and decisive.
|
|
22
36
|
|
|
23
37
|
CONTEXT: {{CONTEXT_JSON}}`;
|
|
@@ -31,29 +45,37 @@ function sevRank(s) {
|
|
|
31
45
|
function norm(s) {
|
|
32
46
|
return s.trim().toLowerCase();
|
|
33
47
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
function blindAnchor(b) {
|
|
38
|
-
return `blind:${b}`;
|
|
39
|
-
}
|
|
40
|
-
function upheldRisks(map, judgeReport) {
|
|
41
|
-
const claimById = new Map([...map.consensus, ...map.unique].map((c) => [c.id, c.statement]));
|
|
48
|
+
function unresolvedRisks(graph, judgeReport) {
|
|
49
|
+
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
42
50
|
const rulingById = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
43
|
-
|
|
44
|
-
.map((
|
|
45
|
-
const adj = rulingById.get(
|
|
51
|
+
const risks = graph.claims
|
|
52
|
+
.map((claim) => {
|
|
53
|
+
const adj = rulingById.get(claim.id);
|
|
46
54
|
if (adj?.ruling !== 'UPHOLD')
|
|
47
55
|
return null;
|
|
48
56
|
return {
|
|
49
|
-
id:
|
|
50
|
-
assumption:
|
|
51
|
-
severity:
|
|
57
|
+
id: claim.id,
|
|
58
|
+
assumption: claim.proposition,
|
|
59
|
+
severity: claim.sensitivity === 'DECISIVE' ? 'HIGH' : claim.sensitivity === 'MATERIAL' ? 'MED' : 'LOW',
|
|
52
60
|
reasoning: adj.reasoning,
|
|
53
61
|
};
|
|
54
62
|
})
|
|
55
|
-
.filter((
|
|
56
|
-
|
|
63
|
+
.filter((risk) => risk !== null);
|
|
64
|
+
const seen = new Set(risks.map((risk) => risk.id));
|
|
65
|
+
for (const hole of graph.holes.evidence) {
|
|
66
|
+
if (seen.has(hole.claim_id))
|
|
67
|
+
continue;
|
|
68
|
+
const claim = claimById.get(hole.claim_id);
|
|
69
|
+
if (!claim)
|
|
70
|
+
continue;
|
|
71
|
+
risks.push({
|
|
72
|
+
id: claim.id,
|
|
73
|
+
assumption: claim.proposition,
|
|
74
|
+
severity: claim.sensitivity === 'DECISIVE' ? 'HIGH' : claim.sensitivity === 'MATERIAL' ? 'MED' : 'LOW',
|
|
75
|
+
reasoning: hole.reason,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return risks.sort((a, b) => sevRank(a.severity) - sevRank(b.severity));
|
|
57
79
|
}
|
|
58
80
|
export function buildActionPlannerPrompt(input, skill) {
|
|
59
81
|
return S9B_PROMPT
|
|
@@ -62,7 +84,7 @@ export function buildActionPlannerPrompt(input, skill) {
|
|
|
62
84
|
}
|
|
63
85
|
export function validAnchor(anchor, anchors) {
|
|
64
86
|
const a = anchor.trim();
|
|
65
|
-
if (anchors.
|
|
87
|
+
if (anchors.claimIds.includes(a))
|
|
66
88
|
return true;
|
|
67
89
|
if (a.toLowerCase().startsWith('blind:')) {
|
|
68
90
|
const label = norm(a.slice('blind:'.length));
|
|
@@ -89,81 +111,69 @@ export function anchoredActionPlan(plan, anchors) {
|
|
|
89
111
|
return null;
|
|
90
112
|
return { actions, sequencing_note: plan.sequencing_note };
|
|
91
113
|
}
|
|
92
|
-
export function
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for (const b of map.blind_spots) {
|
|
106
|
-
actions.push({
|
|
107
|
-
order: actions.length + 1,
|
|
108
|
-
action: `Answer the ${b} gap with one concrete evidence source.`,
|
|
109
|
-
why: 'No analyst examined this dimension enough to rely on it.',
|
|
110
|
-
validates: blindAnchor(b),
|
|
111
|
-
effort: 'S',
|
|
112
|
-
kill_signal: 'The answer exposes a hard blocker or makes the target user/use case incoherent.',
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
for (const q of openQuestions) {
|
|
116
|
-
actions.push({
|
|
117
|
-
order: actions.length + 1,
|
|
118
|
-
action: `Resolve this question: ${clip(q, 140)}`,
|
|
119
|
-
why: 'The analysts identified it as an answer that could change the verdict.',
|
|
120
|
-
validates: questionAnchor(q),
|
|
121
|
-
effort: 'S',
|
|
122
|
-
kill_signal: 'The answer contradicts the value proposition or removes the target user.',
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
if (actions.length === 0) {
|
|
126
|
-
const q = `What evidence would change the verdict for ${clip(contract.task, 80)}?`;
|
|
127
|
-
actions.push({
|
|
128
|
-
order: 1,
|
|
129
|
-
action: 'Run one cheap test of the core user need before investing more.',
|
|
130
|
-
why: 'The council produced no unsettled anchored item, so validate the core demand directly.',
|
|
131
|
-
validates: questionAnchor(q),
|
|
132
|
-
effort: 'S',
|
|
133
|
-
kill_signal: 'Target users do not recognize the problem or would not switch from existing alternatives.',
|
|
134
|
-
});
|
|
135
|
-
}
|
|
114
|
+
export function normalizeEffort(raw) {
|
|
115
|
+
const value = raw?.trim().toLowerCase();
|
|
116
|
+
if (value === 's' || value === 'm' || value === 'l')
|
|
117
|
+
return value.toUpperCase();
|
|
118
|
+
const match = value?.match(/(\d+(?:\.\d+)?)(?:\s*-\s*(\d+(?:\.\d+)?))?\s*(minute|hour|day|week|month|year)s?/);
|
|
119
|
+
if (!match)
|
|
120
|
+
return 'M';
|
|
121
|
+
const amount = Number(match[2] ?? match[1]);
|
|
122
|
+
const unit = match[3];
|
|
123
|
+
const days = amount * (unit === 'minute' ? 1 / 1440 : unit === 'hour' ? 1 / 24 : unit === 'day' ? 1 : unit === 'week' ? 7 : unit === 'month' ? 30 : 365);
|
|
124
|
+
return days <= 2 ? 'S' : days <= 14 ? 'M' : 'L';
|
|
125
|
+
}
|
|
126
|
+
export function normalizePlannerOutput(plan) {
|
|
136
127
|
return ActionPlan.parse({
|
|
137
|
-
actions: actions.
|
|
138
|
-
|
|
128
|
+
actions: plan.actions.map((action, i) => ({
|
|
129
|
+
...action,
|
|
130
|
+
order: i + 1,
|
|
131
|
+
effort: normalizeEffort(action.effort),
|
|
132
|
+
})),
|
|
133
|
+
sequencing_note: plan.sequencing_note,
|
|
139
134
|
});
|
|
140
135
|
}
|
|
141
|
-
|
|
136
|
+
function unavailablePlan(reason, contract, risks, blindSpots, openQuestions) {
|
|
137
|
+
const unresolved = openQuestions.length ? openQuestions : [
|
|
138
|
+
...risks.map((risk) => `What evidence would resolve whether ${risk.assumption}?`),
|
|
139
|
+
...blindSpots.map((spot) => `What evidence resolves the ${spot} gap?`),
|
|
140
|
+
];
|
|
141
|
+
return {
|
|
142
|
+
kind: 'PlannerUnavailable',
|
|
143
|
+
reason,
|
|
144
|
+
unresolved_questions: (unresolved.length ? unresolved : [`What evidence would change the verdict for ${clip(contract.task, 100)}?`]).slice(0, 10),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export async function s9bPlan(ctx, contract, seats, graph, judgeReport) {
|
|
142
148
|
const openQuestions = mergeOpenQuestions(seats);
|
|
143
|
-
const risks =
|
|
149
|
+
const risks = unresolvedRisks(graph, judgeReport);
|
|
150
|
+
const blindSpots = graph.holes.coverage.map((hole) => hole.label);
|
|
144
151
|
const anchors = {
|
|
145
|
-
|
|
146
|
-
blindSpots
|
|
152
|
+
claimIds: risks.map((r) => r.id),
|
|
153
|
+
blindSpots,
|
|
147
154
|
openQuestions,
|
|
148
155
|
};
|
|
149
156
|
const fallback = async (flag) => {
|
|
150
157
|
ctx.addFlag(flag);
|
|
151
|
-
const plan =
|
|
158
|
+
const plan = unavailablePlan(flag === 'plan_skipped' ? 'budget_exhausted' : 'planner_failed', contract, risks, blindSpots, openQuestions);
|
|
152
159
|
await ctx.writer.writeJson('action-plan', plan);
|
|
153
160
|
return plan;
|
|
154
161
|
};
|
|
155
|
-
if (ctx.budget.limit - ctx.budget.used <
|
|
162
|
+
if (ctx.budget.limit - ctx.budget.used < 1)
|
|
156
163
|
return fallback('plan_skipped');
|
|
157
164
|
const prompt = buildActionPlannerPrompt({
|
|
158
165
|
task: contract.task,
|
|
159
166
|
recommendation: judgeReport.recommendation,
|
|
160
167
|
conditions: judgeReport.conditions,
|
|
168
|
+
decision_snapshot: judgeReport.decision_snapshot,
|
|
161
169
|
upheld_risks: risks,
|
|
162
|
-
blind_spots:
|
|
170
|
+
blind_spots: blindSpots,
|
|
163
171
|
open_questions: openQuestions,
|
|
164
172
|
}, loadSkill('idea-refinement', 'planner'));
|
|
165
173
|
try {
|
|
166
|
-
const first = await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-plan', prompt,
|
|
174
|
+
const first = normalizePlannerOutput(await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-plan', prompt, PlannerOutput, {
|
|
175
|
+
repair: ctx.budget.limit - ctx.budget.used >= 2,
|
|
176
|
+
}));
|
|
167
177
|
const anchored = anchoredActionPlan(first, anchors);
|
|
168
178
|
if (anchored) {
|
|
169
179
|
await ctx.writer.writeJson('action-plan', anchored);
|
|
@@ -172,11 +182,11 @@ export async function s9bPlan(ctx, contract, seats, map, judgeReport) {
|
|
|
172
182
|
if (ctx.budget.limit - ctx.budget.used < 1)
|
|
173
183
|
return fallback('plan_fallback');
|
|
174
184
|
const repair = `${prompt}\n\n---\nYour previous plan had no actions with valid anchors.\n` +
|
|
175
|
-
`Valid
|
|
185
|
+
`Valid graph claim ids: ${anchors.claimIds.join(', ') || '(none)'}\n` +
|
|
176
186
|
`Valid blind spots: ${anchors.blindSpots.join(' | ') || '(none)'}\n` +
|
|
177
187
|
`Valid open questions: ${anchors.openQuestions.join(' | ') || '(none)'}\n` +
|
|
178
188
|
`Output ONLY corrected JSON with every action anchored to one of those values.`;
|
|
179
|
-
const repaired = await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-anchor-repair', repair,
|
|
189
|
+
const repaired = normalizePlannerOutput(await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-anchor-repair', repair, PlannerOutput, { repair: false }));
|
|
180
190
|
const repairedAnchored = anchoredActionPlan(repaired, anchors);
|
|
181
191
|
if (repairedAnchored) {
|
|
182
192
|
await ctx.writer.writeJson('action-plan', repairedAnchored);
|
package/dist/providers/codex.js
CHANGED
|
@@ -17,7 +17,8 @@ import { runAdapter } from './adapter-core.js';
|
|
|
17
17
|
const codexSpec = {
|
|
18
18
|
id: 'codex',
|
|
19
19
|
buildArgs(req, flags) {
|
|
20
|
-
|
|
20
|
+
// `--search` is a verified root option: it must precede `exec` (Codex 0.144.1).
|
|
21
|
+
const args = req.research ? ['--search', 'exec', '--skip-git-repo-check'] : ['exec', '--skip-git-repo-check'];
|
|
21
22
|
if (req.readOnly !== false && flags.readOnlyFlag === 'sandbox')
|
|
22
23
|
args.push('-s', 'read-only');
|
|
23
24
|
if (flags.model)
|
package/dist/providers/spawn.js
CHANGED
|
@@ -81,6 +81,7 @@ export const spawnCapture = (bin, args, { cwd, timeoutMs, env, signal }) => {
|
|
|
81
81
|
let notFound = false;
|
|
82
82
|
let settled = false;
|
|
83
83
|
const child = spawn(bin, args, { cwd, env, detached: true, stdio: ['ignore', fd, 'pipe'] });
|
|
84
|
+
child.unref(); // a child that survives the group-kill must never keep our process alive post-resolve
|
|
84
85
|
// SIGKILL the whole detached process group. Shared by the timeout and the Ctrl+C abort (T8).
|
|
85
86
|
const killGroup = () => {
|
|
86
87
|
try {
|
|
@@ -99,6 +100,10 @@ export const spawnCapture = (bin, args, { cwd, timeoutMs, env, signal }) => {
|
|
|
99
100
|
const timer = setTimeout(() => {
|
|
100
101
|
timedOut = true;
|
|
101
102
|
killGroup();
|
|
103
|
+
// Bound the call at timeoutMs even if the group-kill missed a detached child holding our pipe/fd
|
|
104
|
+
// (observed with the claude CLI). Waiting on 'close' alone lets one hung call overrun the wall-clock
|
|
105
|
+
// deadline by an unbounded amount; force-resolve instead. A late 'close' is ignored (`settled`).
|
|
106
|
+
finish(null, 'SIGKILL');
|
|
102
107
|
}, timeoutMs);
|
|
103
108
|
// Ctrl+C: kill the in-flight child immediately so no orphaned metered call survives (§472, T8).
|
|
104
109
|
const onAbort = () => killGroup();
|