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
@@ -1,5 +1,23 @@
1
1
  // S6 — preserve validated analyst submissions for graph compilation. No lexical merging: positions
2
2
  // keep their original text and are grouped only by stable references in S7.
3
+ /** A surviving scout seat with little output or evidence is weak. ponytail: thresholds are blunt on
4
+ * purpose; raise them only with benchmark evidence. */
5
+ export function detectWeakSeat(positions, mode) {
6
+ if (mode === 'quick')
7
+ return [];
8
+ const seats = new Map();
9
+ for (const position of positions) {
10
+ const provider = position.id.split('/')[0].replace(/-coverage-fill$/, '');
11
+ const seat = seats.get(provider) ?? { positions: 0, evidenced: 0 };
12
+ seat.positions += 1;
13
+ if (position.evidence_ids?.length)
14
+ seat.evidenced += 1;
15
+ seats.set(provider, seat);
16
+ }
17
+ if (seats.size < 2)
18
+ return [];
19
+ return [...seats].filter(([, seat]) => seat.positions < 3 || seat.evidenced / seat.positions < 0.5).map(([provider]) => provider);
20
+ }
3
21
  export function collectPositions(seats) {
4
22
  return seats.map((seat) => ({ provider: seat.provider, source_id: seat.sample ?? seat.provider, submission: seat.output }));
5
23
  }
@@ -4,6 +4,7 @@ import { compileDecisionGraph, coverageHoleQueue, positionId } from '../decision
4
4
  import { IdeaRoleOutputModel } from '../../schemas/index.js';
5
5
  import { isFatal } from '../context.js';
6
6
  import { jsonCall } from '../jsonStage.js';
7
+ import { detectWeakSeat } from './s6-positions.js';
7
8
  import { overlapCoefficient, tokenize } from '../cluster.js';
8
9
  const GROUP_SIMILARITY = 0.8;
9
10
  export function buildCoverageFillPrompt(task, holes) {
@@ -71,6 +72,8 @@ export async function s7DecisionGraph(ctx, submissions, rubric, task = '') {
71
72
  const completed = await fillCoverage(ctx, submissions, rubric, task);
72
73
  const groups = deterministicClaimGroups(completed);
73
74
  const graph = compileDecisionGraph(completed, rubric, groups);
75
+ if (detectWeakSeat(graph.positions, ctx.mode ?? 'council').length)
76
+ ctx.addFlag('weak_seat');
74
77
  await ctx.writer.writeJson('decision-graph', graph);
75
78
  return graph;
76
79
  }
@@ -4,6 +4,7 @@ import { selectEscalations } from '../decision-graph.js';
4
4
  import { ClaimVerificationSet } from '../../schemas/index.js';
5
5
  import { isFatal, StageError } from '../context.js';
6
6
  import { jsonCall } from '../jsonStage.js';
7
+ import { loadSkill } from '../skills.js';
7
8
  const S8_PROMPT = `ROLE: Independent verifier. Check each anonymous decision claim below against the
8
9
  provided evidence cards and deterministic calculation checks. Output ONLY JSON:
9
10
  {"verifications": [{"claim_id": "<G-id>", "status": "VERIFIED|PARTIAL|CONTRADICTED|UNVERIFIABLE",
@@ -12,8 +13,21 @@ provided evidence cards and deterministic calculation checks. Output ONLY JSON:
12
13
  VERIFIED = the proposition is supported. CONTRADICTED = it is refuted. PARTIAL = evidence is mixed or
13
14
  incomplete. UNVERIFIABLE = the supplied sources cannot decide. Cite only supplied evidence IDs. Model
14
15
  knowledge cannot verify a current, numeric, legal, medical, financial, market, or regulatory fact.
15
- Judge each item independently; no verdict quota. JSON only.
16
+ Judge each item independently; no verdict quota. JSON only.{{SKILL}}
16
17
  ITEMS: {{ITEMS_JSON}}`;
18
+ export function selectVerificationTargets(claims, max) {
19
+ return claims
20
+ .map((claim, index) => ({ claim, index }))
21
+ .sort((left, right) => Number(right.claim.nature === 'FACTUAL') - Number(left.claim.nature === 'FACTUAL') || left.index - right.index)
22
+ .slice(0, max)
23
+ .map(({ claim }) => claim);
24
+ }
25
+ export function selectVerificationEscalations(graph, max = 8) {
26
+ const candidates = selectEscalations(graph, { max: graph.claims.length });
27
+ const byId = new Map(candidates.map((candidate) => [candidate.claim_id, candidate]));
28
+ const claims = candidates.map((candidate) => graph.claims.find((claim) => claim.id === candidate.claim_id));
29
+ return selectVerificationTargets(claims, max).map((claim) => byId.get(claim.id));
30
+ }
17
31
  function evidenceIdsForClaim(graph, claimId) {
18
32
  const positionById = new Map(graph.positions.map((position) => [position.id, position]));
19
33
  const claim = graph.claims.find((item) => item.id === claimId);
@@ -25,12 +39,12 @@ function evidenceIdsForClaim(graph, claimId) {
25
39
  .flatMap((calculation) => calculation.inputs.flatMap((input) => input.evidence_ids));
26
40
  return new Set([...positionEvidence, ...calculationEvidence]);
27
41
  }
28
- function verifierInput(graph) {
42
+ function verifierInput(graph, skill = '') {
29
43
  const positionById = new Map(graph.positions.map((position) => [position.id, position]));
30
44
  const evidenceRefs = new Map(graph.evidence.map((evidence, index) => [`E${index + 1}`, evidence.id]));
31
45
  const aliasByEvidence = new Map([...evidenceRefs].map(([alias, id]) => [id, alias]));
32
46
  const evidenceById = new Map(graph.evidence.map((evidence) => [evidence.id, evidence]));
33
- const items = selectEscalations(graph, { max: 8 }).map((escalation) => {
47
+ const items = selectVerificationEscalations(graph).map((escalation) => {
34
48
  const claim = graph.claims.find((item) => item.id === escalation.claim_id);
35
49
  const linkedEvidenceIds = evidenceIdsForClaim(graph, claim.id);
36
50
  return {
@@ -64,13 +78,16 @@ function verifierInput(graph) {
64
78
  evidence_hole: graph.holes.evidence.find((hole) => hole.claim_id === claim.id)?.reason,
65
79
  };
66
80
  });
67
- return { prompt: S8_PROMPT.replace('{{ITEMS_JSON}}', JSON.stringify(items, null, 2)), evidenceRefs };
81
+ const prompt = S8_PROMPT
82
+ .replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
83
+ .replace('{{ITEMS_JSON}}', JSON.stringify(items, null, 2));
84
+ return { prompt, evidenceRefs };
68
85
  }
69
- export function buildVerifierPrompt(graph) {
70
- return verifierInput(graph).prompt;
86
+ export function buildVerifierPrompt(graph, skill = '') {
87
+ return verifierInput(graph, skill).prompt;
71
88
  }
72
89
  /** Validate every S8 claim/evidence reference before the chair can see it. */
73
- export function claimVerificationRefIssues(graph, verifications, expectedIds = selectEscalations(graph, { max: 8 }).map((item) => item.claim_id)) {
90
+ export function claimVerificationRefIssues(graph, verifications, expectedIds = selectVerificationEscalations(graph).map((item) => item.claim_id)) {
74
91
  const expected = new Set(expectedIds);
75
92
  const evidence = new Map(graph.evidence.map((item) => [item.id, item]));
76
93
  const seen = new Set();
@@ -111,7 +128,7 @@ export function claimVerificationRefIssues(graph, verifications, expectedIds = s
111
128
  return issues;
112
129
  }
113
130
  export async function s8Verify(ctx, graph) {
114
- const escalations = selectEscalations(graph, { max: 8 });
131
+ const escalations = selectVerificationEscalations(graph);
115
132
  if (escalations.length === 0) {
116
133
  const empty = { verifications: [] };
117
134
  await ctx.writer.writeJson('verifications', empty);
@@ -138,7 +155,7 @@ export async function s8Verify(ctx, graph) {
138
155
  return skipped;
139
156
  }
140
157
  try {
141
- const input = verifierInput(graph);
158
+ const input = verifierInput(graph, loadSkill('idea-refinement', 'verifier'));
142
159
  // Optional work gets no model repair: one logical optional call must remain one call.
143
160
  const result = await jsonCall(ctx, ctx.handle(ctx.roles.verifier), 'S8', input.prompt, ClaimVerificationSet, { repair: false });
144
161
  const translated = {
@@ -5,6 +5,7 @@ import { selectRebuttalEscalations, } from '../decision-graph.js';
5
5
  import { isFatal, StageError } from '../context.js';
6
6
  import { jsonCall } from '../jsonStage.js';
7
7
  import { IDEA_MODE_PLANS } from '../modes.js';
8
+ import { loadSkill } from '../skills.js';
8
9
  /** Internal protocol caps only. R6 owns exposing modes and changing the surrounding topology. */
9
10
  export const REBUTTAL_LIMITS = {
10
11
  quick: { maxNodes: 0, optionalCalls: 0 },
@@ -22,7 +23,7 @@ Output ONLY JSON:
22
23
  CONCEDE = your position changes. COUNTER = retain it using a cited supplied card or a precise logical
23
24
  rebuttal. NARROW = state the smaller proposition actually supported. UNRESOLVED = evidence cannot decide.
24
25
  Return exactly one event per assigned claim. Cite only evidence IDs shown for that claim. Do not invent
25
- sources, URLs, claim IDs, or evidence IDs. JSON only.
26
+ sources, URLs, claim IDs, or evidence IDs. JSON only.{{SKILL}}
26
27
  NODES: {{NODES_JSON}}`;
27
28
  function claimAuthors(graph, claimId) {
28
29
  const positionById = new Map(graph.positions.map((position) => [position.id, position]));
@@ -70,7 +71,7 @@ export function planRebuttalCalls(graph, escalations, scouts, judge) {
70
71
  return claim_ids?.length ? [{ provider, claim_ids }] : [];
71
72
  });
72
73
  }
73
- function rebuttalInput(graph, verifications, escalationById, assignment) {
74
+ function rebuttalInput(graph, verifications, escalationById, assignment, skill = '') {
74
75
  const claimIds = new Set(assignment.claim_ids);
75
76
  const positionRefs = new Map();
76
77
  const evidenceRefs = new Map();
@@ -130,11 +131,16 @@ function rebuttalInput(graph, verifications, escalationById, assignment) {
130
131
  };
131
132
  });
132
133
  return {
133
- prompt: REBUTTAL_PROMPT.replace('{{NODES_JSON}}', JSON.stringify(nodes, null, 2)),
134
+ prompt: buildRebuttalPrompt(nodes, skill),
134
135
  evidenceRefs,
135
136
  allowedEvidence,
136
137
  };
137
138
  }
139
+ export function buildRebuttalPrompt(nodes, skill = '') {
140
+ return REBUTTAL_PROMPT
141
+ .replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
142
+ .replace('{{NODES_JSON}}', JSON.stringify(nodes, null, 2));
143
+ }
138
144
  function translateEvents(graph, assignment, input, output, startIndex) {
139
145
  const expected = new Set(assignment.claim_ids);
140
146
  const seen = new Set();
@@ -189,8 +195,9 @@ export async function s8bRebuttal(ctx, graph, verifications, mode = ctx.mode) {
189
195
  const assignments = planned.slice(0, optionalCalls);
190
196
  const escalationById = new Map(escalations.map((item) => [item.claim_id, item]));
191
197
  const events = [];
198
+ const skill = loadSkill('idea-refinement', 'rebuttal');
192
199
  for (const assignment of assignments) {
193
- const input = rebuttalInput(graph, verifications, escalationById, assignment);
200
+ const input = rebuttalInput(graph, verifications, escalationById, assignment, skill);
194
201
  try {
195
202
  const output = await jsonCall(ctx, ctx.handle(assignment.provider), `S8b-${assignment.provider}`, input.prompt, RebuttalResponseSet, { repair: false });
196
203
  events.push(...translateEvents(graph, assignment, input, output, events.length));
@@ -1,10 +1,11 @@
1
1
  // S9 — chair adjudication over graph-selected escalations. Consensus and shared concerns are
2
2
  // read-only context; anonymous position text and the verifier record cross the boundary unchanged.
3
- import { selectEscalations } from '../decision-graph.js';
4
3
  import { IdeaChairReportModel } from '../../schemas/index.js';
5
4
  import { isFatal, StageError } from '../context.js';
6
5
  import { jsonCall } from '../jsonStage.js';
7
- import { claimVerificationRefIssues } from './s8-verify.js';
6
+ import { claimShortLabel } from '../decision-dossier.js';
7
+ import { loadSkill } from '../skills.js';
8
+ import { claimVerificationRefIssues, selectVerificationEscalations } from './s8-verify.js';
8
9
  const EMPTY_REBUTTALS = {
9
10
  round: 1,
10
11
  selected_claim_ids: [],
@@ -39,7 +40,7 @@ Output ONLY JSON matching the judge schema:
39
40
  - key_points: 4-8 standalone decision-relevant bullets.
40
41
  - dissent: a JSON array of strings (an array even when there is only one) — the strongest arguments
41
42
  against your verdict.
42
- - confidence_notes: explain calibrated confidence.
43
+ - confidence_notes: explain calibrated confidence.{{SKILL}}
43
44
  ESCALATED CLAIMS + VERIFICATION: {{ESCALATIONS_JSON}}
44
45
  APPEND-ONLY REBUTTAL EVENTS: {{REBUTTALS_JSON}}
45
46
  SETTLED/UNRESOLVED CONTEXT: {{CONTEXT_JSON}}`;
@@ -173,19 +174,40 @@ export function adjudicableClaimIds(graph, ids, judgeProvider) {
173
174
  return [...ids].filter((id) => !claimById.get(id)?.position_ids
174
175
  .some((positionId) => positionById.get(positionId)?.provider === judgeProvider));
175
176
  }
177
+ /** One condition per distinct claim behind an evidence hole, claim-labeled instead of a generic
178
+ * placeholder reason. Deduped by claim_id and capped at 4 so the fallback conditions list doesn't
179
+ * drown in near-identical entries. */
180
+ export function evidenceHoleConditions(graph) {
181
+ const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
182
+ const seen = new Set();
183
+ const out = [];
184
+ for (const hole of graph.holes.evidence) {
185
+ if (seen.has(hole.claim_id))
186
+ continue;
187
+ seen.add(hole.claim_id);
188
+ const claim = claimById.get(hole.claim_id);
189
+ out.push(`Obtain independent evidence for: "${claimShortLabel(claim?.proposition ?? 'a load-bearing claim')}".`);
190
+ if (out.length === 4)
191
+ break;
192
+ }
193
+ return out;
194
+ }
176
195
  function fallbackConditions(graph, adjudications) {
177
196
  const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
178
197
  const upheld = adjudications
179
198
  .filter((item) => item.ruling === 'UPHOLD')
180
199
  .map((item) => `Proceed only if you can resolve: ${claimById.get(item.id)?.proposition ?? item.id}.`);
181
200
  const holes = graph.holes.coverage.map((hole) => `Proceed only after examining the ${hole.label} gap.`);
182
- const evidenceHoles = graph.holes.evidence.map((hole) => `Proceed only after resolving evidence gap ${hole.claim_id}: ${hole.reason}.`);
201
+ const evidenceHoles = evidenceHoleConditions(graph);
183
202
  return [...upheld, ...holes, ...evidenceHoles, 'Proceed only after one cheap test confirms the core user need.'].slice(0, 6);
184
203
  }
185
- export function buildJudgePrompt(contract, graph, verifications, rubric, rebuttals = EMPTY_REBUTTALS, judgeProvider) {
186
- return judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider).prompt;
204
+ export function buildJudgePrompt(contract, graph, verifications, rubric, rebuttals = EMPTY_REBUTTALS, judgeProvider, skill = '') {
205
+ return judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider, skill).prompt;
206
+ }
207
+ export function buildChairRepairPrompt(basePrompt, corrections) {
208
+ return `${basePrompt}\n\n---\nCorrect these problems:\n${corrections}Output ONLY corrected JSON.`;
187
209
  }
188
- function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider) {
210
+ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProvider, skill = '') {
189
211
  const positionById = new Map(graph.positions.map((position) => [position.id, position]));
190
212
  const verificationById = new Map(verifications.verifications.map((item) => [item.claim_id, item]));
191
213
  const citedEvidence = [...new Set([
@@ -194,7 +216,7 @@ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProv
194
216
  ])];
195
217
  const evidenceRefs = new Map(citedEvidence.map((id, index) => [`E${index + 1}`, id]));
196
218
  const aliasByEvidence = new Map([...evidenceRefs].map(([alias, id]) => [id, alias]));
197
- const selectedIds = selectEscalations(graph, { max: 8 }).map((item) => item.claim_id);
219
+ const selectedIds = selectVerificationEscalations(graph).map((item) => item.claim_id);
198
220
  const eligibleIds = judgeProvider ? adjudicableClaimIds(graph, selectedIds, judgeProvider) : selectedIds;
199
221
  const escalationIds = new Set(eligibleIds);
200
222
  const escalations = graph.claims.filter((claim) => escalationIds.has(claim.id)).map((claim) => {
@@ -234,6 +256,7 @@ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProv
234
256
  ...(event.narrowed_proposition ? { narrowed_proposition: event.narrowed_proposition } : {}),
235
257
  }));
236
258
  const prompt = S9_PROMPT
259
+ .replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
237
260
  .replace('{{RUBRIC_JSON}}', JSON.stringify(rubric.map((item) => item.label)))
238
261
  .replace('{{ESCALATIONS_JSON}}', JSON.stringify(escalations, null, 2))
239
262
  .replace('{{REBUTTALS_JSON}}', JSON.stringify(rebuttalContext, null, 2))
@@ -242,13 +265,13 @@ function judgeInput(contract, graph, verifications, rubric, rebuttals, judgeProv
242
265
  return { prompt, evidenceRefs };
243
266
  }
244
267
  export async function s9Judge(ctx, contract, graph, verifications, rubric, rebuttals = EMPTY_REBUTTALS) {
245
- const selectedIds = selectEscalations(graph, { max: 8 }).map((item) => item.claim_id);
268
+ const selectedIds = selectVerificationEscalations(graph).map((item) => item.claim_id);
246
269
  const verificationIssues = claimVerificationRefIssues(graph, verifications, selectedIds);
247
270
  if (verificationIssues.length)
248
271
  throw new StageError('S9', 'BAD_OUTPUT', `invalid verification references: ${verificationIssues.join('; ')}`);
249
272
  const ids = adjudicableClaimIds(graph, selectedIds, ctx.roles.judge);
250
273
  const selfAuthored = new Set(selectedIds.filter((id) => !ids.includes(id)));
251
- const input = judgeInput(contract, graph, verifications, rubric, rebuttals, ctx.roles.judge);
274
+ const input = judgeInput(contract, graph, verifications, rubric, rebuttals, ctx.roles.judge, loadSkill('idea-refinement', 'chair'));
252
275
  const basePrompt = input.prompt;
253
276
  const judge = ctx.handle(ctx.roles.judge);
254
277
  const positionById = new Map(graph.positions.map((position) => [position.id, position]));
@@ -291,13 +314,13 @@ export async function s9Judge(ctx, contract, graph, verifications, rubric, rebut
291
314
  ];
292
315
  if ((violations.length || evidenceViolations.length || report.dissent.length === 0 || recIssues.length || chairIssues.length)
293
316
  && ctx.budget.limit - ctx.budget.used > 1) {
294
- const repair = `${basePrompt}\n\n---\nCorrect these problems:\n`
317
+ const corrections = ''
295
318
  + (violations.length ? `- adjudications may reference only [${ids.join(', ')}], not ${violations.join(', ')}\n` : '')
296
319
  + (evidenceViolations.length ? `- evidence reference errors: ${evidenceViolations.join('; ')}\n` : '')
297
320
  + (report.dissent.length === 0 ? '- dissent must contain at least one item\n' : '')
298
321
  + (recIssues.length ? `- ${recIssues.join('; ')}\n` : '')
299
- + (chairIssues.length ? `- chair contract errors: ${chairIssues.join('; ')}\n` : '')
300
- + 'Output ONLY corrected JSON.';
322
+ + (chairIssues.length ? `- chair contract errors: ${chairIssues.join('; ')}\n` : '');
323
+ const repair = buildChairRepairPrompt(basePrompt, corrections);
301
324
  try {
302
325
  report = translateChairReport(await jsonCall(ctx, judge, 'S9-repair', repair, IdeaChairReportModel, {
303
326
  repair: ctx.budget.limit - ctx.budget.used > 2,