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.
Files changed (60) hide show
  1. package/CHANGELOG.md +99 -7
  2. package/README.md +109 -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 +32 -7
  14. package/dist/cli/resolve.js +7 -6
  15. package/dist/cli/resume.js +18 -0
  16. package/dist/cli/run.js +63 -8
  17. package/dist/cli/version.js +8 -0
  18. package/dist/council/view.js +378 -109
  19. package/dist/orchestration/calculations.js +97 -0
  20. package/dist/orchestration/context.js +37 -9
  21. package/dist/orchestration/decision-dossier.js +262 -0
  22. package/dist/orchestration/decision-graph.js +256 -0
  23. package/dist/orchestration/engine.js +5 -2
  24. package/dist/orchestration/evidence-pack.js +46 -0
  25. package/dist/orchestration/idea-lanes.js +20 -0
  26. package/dist/orchestration/jsonStage.js +72 -0
  27. package/dist/orchestration/legacy-idea-adapter.js +102 -0
  28. package/dist/orchestration/modes.js +33 -0
  29. package/dist/orchestration/preflight.js +183 -0
  30. package/dist/orchestration/quick-analysis.js +81 -0
  31. package/dist/orchestration/stages/cr-ladder.js +80 -0
  32. package/dist/orchestration/stages/s10-render.js +562 -150
  33. package/dist/orchestration/stages/s4-analyze.js +12 -7
  34. package/dist/orchestration/stages/s5-drift.js +9 -9
  35. package/dist/orchestration/stages/s6-positions.js +10 -0
  36. package/dist/orchestration/stages/s7-decision-graph.js +76 -0
  37. package/dist/orchestration/stages/s8-verify.js +153 -46
  38. package/dist/orchestration/stages/s8b-rebuttal.js +208 -0
  39. package/dist/orchestration/stages/s9-judge.js +329 -108
  40. package/dist/orchestration/stages/s9b-plan.js +85 -75
  41. package/dist/providers/codex.js +2 -1
  42. package/dist/providers/spawn.js +5 -0
  43. package/dist/schemas/index.js +572 -13
  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 +11 -4
  48. package/dist/tui/app.js +37 -13
  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 +110 -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
@@ -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 a flagged fallback plan.
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 dispute id from upheld_risks, e.g. "D3"
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 questionAnchor(q) {
35
- return `Q:${clip(q, 90)}`;
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
- return map.contradictions
44
- .map((d) => {
45
- const adj = rulingById.get(d.id);
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: d.id,
50
- assumption: d.claim_ids.map((id) => claimById.get(id) ?? id).join(' / '),
51
- severity: [...d.attacks].sort((a, b) => sevRank(a.severity) - sevRank(b.severity))[0]?.severity ?? 'MED',
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((r) => r !== null)
56
- .sort((a, b) => sevRank(a.severity) - sevRank(b.severity));
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.disputeIds.includes(a))
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 fallbackActionPlan(contract, map, judgeReport, openQuestions) {
93
- const risks = upheldRisks(map, judgeReport);
94
- const actions = [];
95
- for (const r of risks) {
96
- actions.push({
97
- order: actions.length + 1,
98
- action: `Test whether "${clip(r.assumption, 120)}" is true with the smallest realistic sample.`,
99
- why: `The chair upheld this as a load-bearing ${r.severity} risk.`,
100
- validates: r.id,
101
- effort: 'S',
102
- kill_signal: 'The assumption fails in the sample or only works outside the intended use case.',
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.slice(0, 7).map((a, i) => ({ ...a, order: i + 1 })),
138
- sequencing_note: 'Start with upheld risks, then blind spots, then open questions; stop when a kill signal fires.',
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
- export async function s9bPlan(ctx, contract, seats, map, judgeReport) {
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 = upheldRisks(map, judgeReport);
149
+ const risks = unresolvedRisks(graph, judgeReport);
150
+ const blindSpots = graph.holes.coverage.map((hole) => hole.label);
144
151
  const anchors = {
145
- disputeIds: risks.map((r) => r.id),
146
- blindSpots: map.blind_spots,
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 = fallbackActionPlan(contract, map, judgeReport, openQuestions);
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 < 2)
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: map.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, ActionPlan);
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 dispute ids: ${anchors.disputeIds.join(', ') || '(none)'}\n` +
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, ActionPlan);
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);
@@ -17,7 +17,8 @@ import { runAdapter } from './adapter-core.js';
17
17
  const codexSpec = {
18
18
  id: 'codex',
19
19
  buildArgs(req, flags) {
20
- const args = ['exec', '--skip-git-repo-check'];
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)
@@ -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();