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.
- package/CHANGELOG.md +12 -5
- package/README.md +9 -11
- package/dist/bench/idea-v3-rating.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/run.js +1 -1
- package/dist/council/view.js +52 -12
- package/dist/orchestration/context.js +3 -3
- package/dist/orchestration/decision-dossier.js +417 -88
- package/dist/orchestration/decision-graph.js +31 -0
- package/dist/orchestration/legacy-idea-adapter.js +3 -0
- package/dist/orchestration/modes.js +15 -9
- package/dist/orchestration/preflight.js +17 -3
- package/dist/orchestration/quick-analysis.js +21 -4
- package/dist/orchestration/stages/s10-render.js +167 -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 +192 -42
- package/dist/schemas/index.js +67 -3
- 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/workflows/idea-refinement.js +42 -14
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
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
3
|
// deterministic; if the planner fails or produces unanchored actions, we write flagged unavailability.
|
|
4
|
-
import { ActionPlan, FeatureBacklog, ImplementationPlan } from '../../schemas/index.js';
|
|
4
|
+
import { ActionPlan, FeatureBacklog, ImplementationPlan, ReaderBrief, readerBriefIssues } from '../../schemas/index.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { BudgetExceeded, isFatal } from '../context.js';
|
|
7
7
|
import { jsonCall } from '../jsonStage.js';
|
|
8
8
|
import { loadSkill } from '../skills.js';
|
|
9
9
|
import { mergeOpenQuestions } from './s10-render.js';
|
|
10
|
+
import { interpretClaimOutcome } from '../decision-graph.js';
|
|
10
11
|
const PlannerOutput = z.object({
|
|
11
12
|
actions: z.array(z.object({
|
|
12
13
|
order: z.number().int().min(1).optional(),
|
|
@@ -15,25 +16,46 @@ const PlannerOutput = z.object({
|
|
|
15
16
|
validates: z.string().min(1),
|
|
16
17
|
effort: z.string().min(1).optional(),
|
|
17
18
|
kill_signal: z.string().min(1),
|
|
18
|
-
}).strict()).
|
|
19
|
+
}).strict()).max(7),
|
|
19
20
|
sequencing_note: z.string().min(1),
|
|
20
21
|
feature_backlog: FeatureBacklog.optional(),
|
|
21
22
|
implementation_plan: ImplementationPlan.optional(),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
reader_brief: ReaderBrief.optional(),
|
|
24
|
+
}).strict().superRefine((plan, ctx) => {
|
|
25
|
+
if (plan.actions.length === 0 && !plan.reader_brief) {
|
|
26
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['actions'], message: 'an empty planner answer requires reader_brief' });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const S9B_PROMPT = `ROLE: User answer editor and action planner. The council has already done the analysis.
|
|
30
|
+
Turn its strongest supported findings and both scouts' proposals into a useful answer to the original user.{{SKILL}}
|
|
26
31
|
|
|
27
32
|
Output ONLY JSON matching the ActionPlan schema:
|
|
28
|
-
-
|
|
33
|
+
- reader_brief is REQUIRED. It contains:
|
|
34
|
+
- headline: a concrete answer, not a report label.
|
|
35
|
+
- bottom_line: the direct recommendation and why, with no process talk.
|
|
36
|
+
- sections: 2-6 useful sections {heading,summary,bullets}; synthesize the requested deliverables and explain
|
|
37
|
+
why standout ideas matter. Prefer concrete product language over due-diligence language. Explain the verdict,
|
|
38
|
+
selection logic, and trade-offs; do not restate the feature backlog or milestone list.
|
|
39
|
+
- next_step: one action the user should take next.
|
|
40
|
+
- caveats: at most 3 honest limitations that materially affect the answer.
|
|
41
|
+
- source_ids: only ids present in CONTEXT.sources; use [] when no source supports the reader answer.
|
|
42
|
+
- reader_brief must never mention graph/claim ids, verification enums, evidence coverage, structural scoring,
|
|
43
|
+
provider-call mechanics, or the fact that an answer editor assembled it.
|
|
44
|
+
- CONTEXT.chair is decision reasoning, not factual proof. Treat its recommendation, rationale, conditions, and
|
|
45
|
+
claim outcomes as judgment. Do not call a proposition supported, verified, proven, or factual unless the same
|
|
46
|
+
proposition appears in supported_findings; otherwise frame it as a recommendation, risk, or hypothesis.
|
|
47
|
+
- actions: 1-4 ordered validation actions, each imperative and concrete.
|
|
29
48
|
- validates MUST anchor to one of:
|
|
30
49
|
- a graph claim id from upheld_risks, e.g. "G3"
|
|
31
50
|
- a blind spot label as "blind:<label>"
|
|
32
51
|
- an open-question prefix as "Q:<question prefix>"
|
|
33
52
|
- why ties the action to the risk, blind spot, or question.
|
|
34
53
|
- kill_signal is the result that should stop or reshape the idea.
|
|
35
|
-
- Preserve
|
|
36
|
-
is not a known cost. Do not introduce or reinterpret a number that is absent from decision_snapshot.
|
|
54
|
+
- Preserve CONTEXT.chair's numeric distinctions: operating break-even is not capital payback, and a target cap
|
|
55
|
+
is not a known cost. Do not introduce or reinterpret a number that is absent from CONTEXT.chair.decision_snapshot.
|
|
56
|
+
- CONTEXT.as_of_date is the evidence snapshot date, not a deadline. Compare it with any sourced deadline.
|
|
57
|
+
- Treat stated deadlines and available time as hard boundaries. When CONTEXT has no numeric deadline or capacity,
|
|
58
|
+
do not invent a day-count calendar; use ordered phases with explicit acceptance tests.
|
|
37
59
|
- sequencing_note explains why this order is cheapest and decisive.
|
|
38
60
|
- Read requested_outputs in CONTEXT. When it includes FEATURE_BACKLOG, include feature_backlog with
|
|
39
61
|
must/should/later items {feature,user_value,rationale,effort:S|M|L} and wont items {feature,reason}.
|
|
@@ -41,6 +63,8 @@ Output ONLY JSON matching the ActionPlan schema:
|
|
|
41
63
|
- When requested_outputs includes IMPLEMENTATION_PLAN, include implementation_plan.milestones with
|
|
42
64
|
{order,timebox,outcome,tasks,acceptance_test}. This is a concrete build sequence, not validation prose.
|
|
43
65
|
- Do not omit a requested output and do not invent an unrequested product surface.
|
|
66
|
+
- Use deliverable_proposals from BOTH seats as options, then select and improve the strongest coherent set.
|
|
67
|
+
- Treat supported_findings and sources as factual boundaries. Proposals are product judgment, not proof.
|
|
44
68
|
|
|
45
69
|
CONTEXT: {{CONTEXT_JSON}}`;
|
|
46
70
|
function clip(s, n) {
|
|
@@ -53,19 +77,21 @@ function sevRank(s) {
|
|
|
53
77
|
function norm(s) {
|
|
54
78
|
return s.trim().toLowerCase();
|
|
55
79
|
}
|
|
56
|
-
function unresolvedRisks(graph, judgeReport) {
|
|
80
|
+
export function unresolvedRisks(graph, judgeReport) {
|
|
57
81
|
const claimById = new Map(graph.claims.map((claim) => [claim.id, claim]));
|
|
58
82
|
const rulingById = new Map(judgeReport.adjudications.map((a) => [a.id, a]));
|
|
59
83
|
const risks = graph.claims
|
|
60
84
|
.map((claim) => {
|
|
61
85
|
const adj = rulingById.get(claim.id);
|
|
62
|
-
|
|
86
|
+
const outcome = interpretClaimOutcome(graph, claim, adj);
|
|
87
|
+
if ((!adj && outcome.decisionEffect !== 'FAILED')
|
|
88
|
+
|| (outcome.propositionTruth === 'HOLDS' && outcome.decisionEffect === 'HELD'))
|
|
63
89
|
return null;
|
|
64
90
|
return {
|
|
65
91
|
id: claim.id,
|
|
66
92
|
assumption: claim.proposition,
|
|
67
93
|
severity: claim.sensitivity === 'DECISIVE' ? 'HIGH' : claim.sensitivity === 'MATERIAL' ? 'MED' : 'LOW',
|
|
68
|
-
reasoning: adj.
|
|
94
|
+
reasoning: adj?.reasoning ?? 'This supported concern works against the decision.',
|
|
69
95
|
};
|
|
70
96
|
})
|
|
71
97
|
.filter((risk) => risk !== null);
|
|
@@ -85,6 +111,96 @@ function unresolvedRisks(graph, judgeReport) {
|
|
|
85
111
|
}
|
|
86
112
|
return risks.sort((a, b) => sevRank(a.severity) - sevRank(b.severity));
|
|
87
113
|
}
|
|
114
|
+
const ANSWER_MATERIAL_FLAGS = new Set([
|
|
115
|
+
'synthesis_suspect', 'low_diversity', 'weak_seat', 'deliverable_gap', 'headless_intent',
|
|
116
|
+
'verification_skipped', 'research_ungrounded', 'source_fallback_search',
|
|
117
|
+
]);
|
|
118
|
+
function safePublicUrl(raw) {
|
|
119
|
+
if (!raw)
|
|
120
|
+
return undefined;
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(raw);
|
|
123
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : undefined;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Canonical, decision-relevant input to the existing S9b answer-editor call. */
|
|
130
|
+
export function buildAnswerContext(input) {
|
|
131
|
+
const { contract, graph, judgeReport, seats } = input;
|
|
132
|
+
const evidenceDates = graph.evidence
|
|
133
|
+
.filter((evidence) => evidence.source_kind === 'PRIMARY' || evidence.source_kind === 'SECONDARY')
|
|
134
|
+
.map((evidence) => evidence.accessed_at?.match(/^\d{4}-\d{2}-\d{2}/)?.[0])
|
|
135
|
+
.filter((date) => Boolean(date))
|
|
136
|
+
.sort();
|
|
137
|
+
const asOfDate = evidenceDates.at(-1);
|
|
138
|
+
const requestedOutputs = contract.requested_outputs ?? ['DECISION'];
|
|
139
|
+
const adjudicationById = new Map(judgeReport.adjudications.map((item) => [item.id, item]));
|
|
140
|
+
const outcomes = graph.claims.map((claim) => {
|
|
141
|
+
const adjudication = adjudicationById.get(claim.id);
|
|
142
|
+
const outcome = interpretClaimOutcome(graph, claim, adjudication);
|
|
143
|
+
return {
|
|
144
|
+
id: claim.id,
|
|
145
|
+
proposition: claim.proposition,
|
|
146
|
+
proposition_truth: outcome.propositionTruth,
|
|
147
|
+
decision_effect: outcome.decisionEffect,
|
|
148
|
+
...(adjudication ? { reasoning: adjudication.reasoning } : {}),
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
const outcomeById = new Map(outcomes.map((outcome) => [outcome.id, outcome]));
|
|
152
|
+
const deliverableProposals = seats.flatMap((seat) => {
|
|
153
|
+
const seatId = seat.sample ?? seat.provider;
|
|
154
|
+
return (seat.output.deliverable_proposals ?? []).map((proposal) => ({
|
|
155
|
+
...proposal,
|
|
156
|
+
provider: seat.provider,
|
|
157
|
+
seat_id: seatId,
|
|
158
|
+
evidence_ids: [...new Set(proposal.evidence_ids.flatMap((localId) => graph.evidence
|
|
159
|
+
.filter((evidence) => evidence.provider === seat.provider
|
|
160
|
+
&& evidence.source_id === seatId
|
|
161
|
+
&& evidence.id === `${seatId}/${localId}`)
|
|
162
|
+
.map((evidence) => evidence.id)))],
|
|
163
|
+
}));
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
original_request: input.originalRequest,
|
|
167
|
+
...(asOfDate ? { as_of_date: asOfDate } : {}),
|
|
168
|
+
task: contract.task,
|
|
169
|
+
constraints: contract.constraints,
|
|
170
|
+
success_criteria: contract.success_criteria,
|
|
171
|
+
requested_outputs: requestedOutputs,
|
|
172
|
+
chair: {
|
|
173
|
+
epistemic_status: 'DECISION_REASONING_NOT_FACT',
|
|
174
|
+
recommendation: judgeReport.recommendation,
|
|
175
|
+
decision_reasoning: judgeReport.verdict,
|
|
176
|
+
rationale: judgeReport.key_points ?? [],
|
|
177
|
+
decision_conditions: judgeReport.conditions ?? [],
|
|
178
|
+
decision_snapshot: judgeReport.decision_snapshot,
|
|
179
|
+
claim_outcomes: outcomes,
|
|
180
|
+
},
|
|
181
|
+
upheld_risks: unresolvedRisks(graph, judgeReport),
|
|
182
|
+
blind_spots: graph.holes.coverage.map((hole) => hole.label),
|
|
183
|
+
open_questions: mergeOpenQuestions(seats),
|
|
184
|
+
deliverable_proposals: deliverableProposals,
|
|
185
|
+
supported_findings: graph.claims
|
|
186
|
+
.filter((claim) => claim.nature === 'FACTUAL'
|
|
187
|
+
&& claim.evidence_state === 'SUPPORTED'
|
|
188
|
+
&& outcomeById.get(claim.id)?.proposition_truth === 'HOLDS')
|
|
189
|
+
.map((claim) => ({ id: claim.id, finding: claim.proposition })),
|
|
190
|
+
material_flags: [...input.flags].filter((flag) => ANSWER_MATERIAL_FLAGS.has(flag)),
|
|
191
|
+
sources: graph.evidence.map((evidence) => {
|
|
192
|
+
const url = safePublicUrl(evidence.url) ?? safePublicUrl(evidence.locator);
|
|
193
|
+
return {
|
|
194
|
+
id: evidence.id,
|
|
195
|
+
kind: evidence.source_kind,
|
|
196
|
+
...(evidence.title ? { title: evidence.title } : {}),
|
|
197
|
+
...(url ? { url } : {}),
|
|
198
|
+
...(evidence.accessed_at ? { accessed_at: evidence.accessed_at } : {}),
|
|
199
|
+
supports: evidence.claim_supported,
|
|
200
|
+
};
|
|
201
|
+
}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
88
204
|
export function buildActionPlannerPrompt(input, skill) {
|
|
89
205
|
return S9B_PROMPT
|
|
90
206
|
.replace('{{SKILL}}', skill ? `\n\n${skill}` : '')
|
|
@@ -110,22 +226,41 @@ export function validAnchor(anchor, anchors) {
|
|
|
110
226
|
}
|
|
111
227
|
return false;
|
|
112
228
|
}
|
|
113
|
-
|
|
229
|
+
function normalizeSourceIds(ids, allowed) {
|
|
230
|
+
const normalized = ids.flatMap((id) => {
|
|
231
|
+
if (allowed.includes(id))
|
|
232
|
+
return [id];
|
|
233
|
+
const matches = allowed.filter((known) => known.endsWith(`/${id}`));
|
|
234
|
+
return matches.length === 1 ? matches : [];
|
|
235
|
+
});
|
|
236
|
+
return [...new Set(normalized)];
|
|
237
|
+
}
|
|
238
|
+
export function anchoredActionPlan(plan, anchors, requestedOutputs = [], requireReaderBrief = false) {
|
|
239
|
+
if (requestedOutputs.includes('FEATURE_BACKLOG') && !plan.feature_backlog)
|
|
240
|
+
return null;
|
|
241
|
+
if (requestedOutputs.includes('IMPLEMENTATION_PLAN') && !plan.implementation_plan)
|
|
242
|
+
return null;
|
|
243
|
+
if (requireReaderBrief && !plan.reader_brief)
|
|
244
|
+
return null;
|
|
245
|
+
const readerBrief = plan.reader_brief ? {
|
|
246
|
+
...plan.reader_brief,
|
|
247
|
+
source_ids: normalizeSourceIds(plan.reader_brief.source_ids, anchors.sourceIds),
|
|
248
|
+
} : undefined;
|
|
249
|
+
if (readerBrief && readerBriefIssues(readerBrief, anchors.knownReaderIds ?? anchors.claimIds).length)
|
|
250
|
+
return null;
|
|
114
251
|
const actions = plan.actions
|
|
115
252
|
.filter((a) => validAnchor(a.validates, anchors))
|
|
116
253
|
.sort((a, b) => a.order - b.order)
|
|
254
|
+
.slice(0, requireReaderBrief ? 4 : 7)
|
|
117
255
|
.map((a, i) => ({ ...a, order: i + 1 }));
|
|
118
|
-
if (actions.length === 0)
|
|
119
|
-
return null;
|
|
120
|
-
if (requestedOutputs.includes('FEATURE_BACKLOG') && !plan.feature_backlog)
|
|
121
|
-
return null;
|
|
122
|
-
if (requestedOutputs.includes('IMPLEMENTATION_PLAN') && !plan.implementation_plan)
|
|
256
|
+
if (actions.length === 0 && !requireReaderBrief)
|
|
123
257
|
return null;
|
|
124
258
|
return {
|
|
125
259
|
actions,
|
|
126
260
|
sequencing_note: plan.sequencing_note,
|
|
127
261
|
...(requestedOutputs.includes('FEATURE_BACKLOG') && plan.feature_backlog ? { feature_backlog: plan.feature_backlog } : {}),
|
|
128
262
|
...(requestedOutputs.includes('IMPLEMENTATION_PLAN') && plan.implementation_plan ? { implementation_plan: plan.implementation_plan } : {}),
|
|
263
|
+
...(readerBrief ? { reader_brief: readerBrief } : {}),
|
|
129
264
|
};
|
|
130
265
|
}
|
|
131
266
|
export function normalizeEffort(raw) {
|
|
@@ -140,7 +275,19 @@ export function normalizeEffort(raw) {
|
|
|
140
275
|
const days = amount * (unit === 'minute' ? 1 / 1440 : unit === 'hour' ? 1 / 24 : unit === 'day' ? 1 : unit === 'week' ? 7 : unit === 'month' ? 30 : 365);
|
|
141
276
|
return days <= 2 ? 'S' : days <= 14 ? 'M' : 'L';
|
|
142
277
|
}
|
|
143
|
-
|
|
278
|
+
const CALENDAR_TIMEBOX = /\b(?:days?|weeks?|months?|years?)\b|\b\d{4}-\d{2}-\d{2}\b|\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\b/i;
|
|
279
|
+
const EXPLICIT_SCHEDULE = /\b(?:\d{4}-\d{2}-\d{2}|\d+(?:\.\d+)?\s*[-–]?\s*(?:hours?|days?|weeks?|months?|years?)|(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\s+\d{1,2}(?:st|nd|rd|th)?(?:,\s*\d{4})?)\b/i;
|
|
280
|
+
function hasExplicitSchedule(context) {
|
|
281
|
+
return EXPLICIT_SCHEDULE.test(JSON.stringify({
|
|
282
|
+
original_request: context.original_request,
|
|
283
|
+
constraints: context.constraints,
|
|
284
|
+
success_criteria: context.success_criteria,
|
|
285
|
+
chair: context.chair,
|
|
286
|
+
supported_findings: context.supported_findings,
|
|
287
|
+
sources: context.sources.map((source) => source.supports),
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
export function normalizePlannerOutput(plan, calendarAllowed = true) {
|
|
144
291
|
return ActionPlan.parse({
|
|
145
292
|
actions: plan.actions.map((action, i) => ({
|
|
146
293
|
...action,
|
|
@@ -149,7 +296,13 @@ export function normalizePlannerOutput(plan) {
|
|
|
149
296
|
})),
|
|
150
297
|
sequencing_note: plan.sequencing_note,
|
|
151
298
|
...(plan.feature_backlog ? { feature_backlog: plan.feature_backlog } : {}),
|
|
152
|
-
...(plan.implementation_plan ? { implementation_plan:
|
|
299
|
+
...(plan.implementation_plan ? { implementation_plan: {
|
|
300
|
+
milestones: plan.implementation_plan.milestones.map((milestone, index) => ({
|
|
301
|
+
...milestone,
|
|
302
|
+
timebox: !calendarAllowed && CALENDAR_TIMEBOX.test(milestone.timebox) ? `Phase ${index + 1}` : milestone.timebox,
|
|
303
|
+
})),
|
|
304
|
+
} } : {}),
|
|
305
|
+
...(plan.reader_brief ? { reader_brief: plan.reader_brief } : {}),
|
|
153
306
|
});
|
|
154
307
|
}
|
|
155
308
|
function unavailablePlan(reason, contract, risks, blindSpots, openQuestions) {
|
|
@@ -163,16 +316,20 @@ function unavailablePlan(reason, contract, risks, blindSpots, openQuestions) {
|
|
|
163
316
|
unresolved_questions: (unresolved.length ? unresolved : [`What evidence would change the verdict for ${clip(contract.task, 100)}?`]).slice(0, 10),
|
|
164
317
|
};
|
|
165
318
|
}
|
|
166
|
-
export async function s9bPlan(ctx, contract, seats, graph, judgeReport) {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
const risks =
|
|
171
|
-
const blindSpots =
|
|
319
|
+
export async function s9bPlan(ctx, contract, seats, graph, judgeReport, originalRequest = contract.task) {
|
|
320
|
+
const answerContext = buildAnswerContext({ originalRequest, contract, seats, graph, judgeReport, flags: ctx.flags });
|
|
321
|
+
const openQuestions = answerContext.open_questions;
|
|
322
|
+
const requestedOutputs = answerContext.requested_outputs;
|
|
323
|
+
const risks = answerContext.upheld_risks;
|
|
324
|
+
const blindSpots = answerContext.blind_spots;
|
|
325
|
+
const sourceIds = graph.evidence.map((evidence) => evidence.id);
|
|
326
|
+
const requireReaderBrief = 'success_bar' in contract;
|
|
172
327
|
const anchors = {
|
|
173
328
|
claimIds: risks.map((r) => r.id),
|
|
329
|
+
knownReaderIds: graph.claims.map((claim) => claim.id),
|
|
174
330
|
blindSpots,
|
|
175
331
|
openQuestions,
|
|
332
|
+
sourceIds,
|
|
176
333
|
};
|
|
177
334
|
const fallback = async (flag) => {
|
|
178
335
|
ctx.addFlag(flag);
|
|
@@ -182,35 +339,28 @@ export async function s9bPlan(ctx, contract, seats, graph, judgeReport) {
|
|
|
182
339
|
};
|
|
183
340
|
if (ctx.budget.limit - ctx.budget.used < 1)
|
|
184
341
|
return fallback('plan_skipped');
|
|
185
|
-
const prompt = buildActionPlannerPrompt(
|
|
186
|
-
|
|
187
|
-
recommendation: judgeReport.recommendation,
|
|
188
|
-
conditions: judgeReport.conditions,
|
|
189
|
-
decision_snapshot: judgeReport.decision_snapshot,
|
|
190
|
-
upheld_risks: risks,
|
|
191
|
-
blind_spots: blindSpots,
|
|
192
|
-
open_questions: openQuestions,
|
|
193
|
-
requested_outputs: requestedOutputs,
|
|
194
|
-
}, loadSkill('idea-refinement', 'planner'));
|
|
342
|
+
const prompt = buildActionPlannerPrompt(answerContext, loadSkill('idea-refinement', 'planner'));
|
|
343
|
+
const calendarAllowed = hasExplicitSchedule(answerContext);
|
|
195
344
|
try {
|
|
196
345
|
const first = normalizePlannerOutput(await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-plan', prompt, PlannerOutput, {
|
|
197
346
|
repair: ctx.budget.limit - ctx.budget.used >= 2,
|
|
198
|
-
}));
|
|
199
|
-
const anchored = anchoredActionPlan(first, anchors, requestedOutputs);
|
|
347
|
+
}), calendarAllowed);
|
|
348
|
+
const anchored = anchoredActionPlan(first, anchors, requestedOutputs, requireReaderBrief);
|
|
200
349
|
if (anchored) {
|
|
201
350
|
await ctx.writer.writeJson('action-plan', anchored);
|
|
202
351
|
return anchored;
|
|
203
352
|
}
|
|
204
353
|
if (ctx.budget.limit - ctx.budget.used < 1)
|
|
205
354
|
return fallback('plan_fallback');
|
|
206
|
-
const repair = `${prompt}\n\n---\nYour previous
|
|
355
|
+
const repair = `${prompt}\n\n---\nYour previous response had invalid anchors, omitted a requested deliverable or reader_brief, used internal audit language, or cited an unknown source id.\n` +
|
|
207
356
|
`Valid graph claim ids: ${anchors.claimIds.join(', ') || '(none)'}\n` +
|
|
208
357
|
`Valid blind spots: ${anchors.blindSpots.join(' | ') || '(none)'}\n` +
|
|
209
358
|
`Valid open questions: ${anchors.openQuestions.join(' | ') || '(none)'}\n` +
|
|
359
|
+
`Valid source ids: ${anchors.sourceIds.join(', ') || '(none)'}\n` +
|
|
210
360
|
`Required outputs: ${requestedOutputs.join(', ')}\n` +
|
|
211
|
-
`Output ONLY corrected JSON with every action anchored and every requested deliverable present.`;
|
|
212
|
-
const repaired = normalizePlannerOutput(await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-anchor-repair', repair, PlannerOutput, { repair: false }));
|
|
213
|
-
const repairedAnchored = anchoredActionPlan(repaired, anchors, requestedOutputs);
|
|
361
|
+
`Output ONLY corrected JSON with every action anchored, reader_brief present, and every requested deliverable present.`;
|
|
362
|
+
const repaired = normalizePlannerOutput(await jsonCall(ctx, ctx.handle(ctx.roles.judge), 'S9b-anchor-repair', repair, PlannerOutput, { repair: false }), calendarAllowed);
|
|
363
|
+
const repairedAnchored = anchoredActionPlan(repaired, anchors, requestedOutputs, requireReaderBrief);
|
|
214
364
|
if (repairedAnchored) {
|
|
215
365
|
await ctx.writer.writeJson('action-plan', repairedAnchored);
|
|
216
366
|
return repairedAnchored;
|
package/dist/schemas/index.js
CHANGED
|
@@ -186,6 +186,7 @@ export const PreflightReading = z.object({
|
|
|
186
186
|
missing_evidence: z.array(z.string().min(1)).max(8),
|
|
187
187
|
domain_dimensions: z.array(DomainDimension).min(3).max(5),
|
|
188
188
|
questions: z.array(RunBriefQuestion).max(4),
|
|
189
|
+
requested_outputs: z.array(RequestedOutputSchema).max(4).default([]),
|
|
189
190
|
}).strict().superRefine((reading, ctx) => {
|
|
190
191
|
checkQuestionIds(reading.questions, ctx);
|
|
191
192
|
checkDomainDimensionIds(reading.domain_dimensions, ctx);
|
|
@@ -236,6 +237,7 @@ export const ClaimPosition = z
|
|
|
236
237
|
dimension_id: z.string().min(1),
|
|
237
238
|
stance: z.enum(['SUPPORT', 'OPPOSE', 'MIXED', 'UNKNOWN']),
|
|
238
239
|
basis: z.enum(['EVIDENCE', 'INFERENCE', 'ASSUMPTION']),
|
|
240
|
+
nature: z.enum(['FACTUAL', 'JUDGMENT']).default('JUDGMENT'),
|
|
239
241
|
load_bearing: z.boolean(),
|
|
240
242
|
if_false: z.enum(['STOP', 'PIVOT', 'CONDITION', 'MINOR']),
|
|
241
243
|
reasoning: z.string().min(1),
|
|
@@ -316,6 +318,14 @@ export const DecisionQuestion = z
|
|
|
316
318
|
claim_ids: z.array(z.string().min(1)),
|
|
317
319
|
})
|
|
318
320
|
.strict();
|
|
321
|
+
export const DeliverableProposal = z.object({
|
|
322
|
+
output: z.enum(['FEATURE_BACKLOG', 'IMPLEMENTATION_PLAN']),
|
|
323
|
+
title: z.string().min(1),
|
|
324
|
+
detail: z.string().min(1),
|
|
325
|
+
user_value: z.string().min(1),
|
|
326
|
+
why_distinctive: z.string().min(1),
|
|
327
|
+
evidence_ids: z.array(z.string().min(1)).max(6),
|
|
328
|
+
}).strict();
|
|
319
329
|
const IdeaRoleOutputBase = z
|
|
320
330
|
.object({
|
|
321
331
|
workflow: z.literal('idea-refinement'),
|
|
@@ -326,6 +336,7 @@ const IdeaRoleOutputBase = z
|
|
|
326
336
|
calculations: z.array(CalculationLedger).max(8).default([]),
|
|
327
337
|
coverage: z.array(CoverageEntry).max(18), // 13 core + up to 5 preflight domain dimensions
|
|
328
338
|
decision_questions: z.array(DecisionQuestion).max(8),
|
|
339
|
+
deliverable_proposals: z.array(DeliverableProposal).max(8).default([]),
|
|
329
340
|
})
|
|
330
341
|
.strict();
|
|
331
342
|
function checkSubmissionRefs(submission, ctx) {
|
|
@@ -395,6 +406,12 @@ function checkSubmissionRefs(submission, ctx) {
|
|
|
395
406
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['decision_questions', index, 'claim_ids'], message: `unknown position id: ${id}` });
|
|
396
407
|
}
|
|
397
408
|
}
|
|
409
|
+
for (const [index, proposal] of submission.deliverable_proposals.entries()) {
|
|
410
|
+
for (const id of proposal.evidence_ids) {
|
|
411
|
+
if (!evidenceIds.has(id))
|
|
412
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['deliverable_proposals', index, 'evidence_ids'], message: `unknown evidence id: ${id}` });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
398
415
|
}
|
|
399
416
|
export const IdeaRoleOutput = IdeaRoleOutputBase.superRefine(checkSubmissionRefs);
|
|
400
417
|
/** Defect categories a finding (and a seeded bug, T11) can carry. BENCHMARK.md's "defect class" match
|
|
@@ -527,6 +544,17 @@ export function salvageIdeaRoleOutputModel(input) {
|
|
|
527
544
|
&& calc.inputs.every((input) => input.evidence_ids.every((id) => keptEvidence.has(id))))
|
|
528
545
|
.slice(0, 8)
|
|
529
546
|
: output.calculations;
|
|
547
|
+
const deliverableProposals = parseOrDrop(output.deliverable_proposals, (item) => {
|
|
548
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
549
|
+
return DeliverableProposal.safeParse(item);
|
|
550
|
+
const proposal = item;
|
|
551
|
+
return DeliverableProposal.strip().safeParse({
|
|
552
|
+
...proposal,
|
|
553
|
+
evidence_ids: Array.isArray(proposal.evidence_ids)
|
|
554
|
+
? proposal.evidence_ids.filter((id) => keptEvidence.has(id))
|
|
555
|
+
: proposal.evidence_ids,
|
|
556
|
+
});
|
|
557
|
+
});
|
|
530
558
|
return {
|
|
531
559
|
...output,
|
|
532
560
|
evidence,
|
|
@@ -534,6 +562,7 @@ export function salvageIdeaRoleOutputModel(input) {
|
|
|
534
562
|
calculations,
|
|
535
563
|
coverage: parseOrDrop(scrubIds(output.coverage, 'position_ids'), (item) => CoverageEntryBase.strip().superRefine(checkCoverageEntry).safeParse(item)),
|
|
536
564
|
decision_questions: parseOrDrop(scrubIds(output.decision_questions, 'claim_ids'), (item) => DecisionQuestion.strip().safeParse(item)),
|
|
565
|
+
deliverable_proposals: deliverableProposals,
|
|
537
566
|
};
|
|
538
567
|
}
|
|
539
568
|
/** The model-facing S4 shape. Exact known enum aliases are canonicalized, then the strict schema validates
|
|
@@ -588,6 +617,7 @@ export const DecisionClaim = z.object({
|
|
|
588
617
|
position_ids: z.array(z.string().min(1)).min(1),
|
|
589
618
|
state: z.enum(['CONSENSUS', 'SHARED_CONCERN', 'DISAGREEMENT', 'UNIQUE', 'UNCERTAIN']),
|
|
590
619
|
evidence_state: z.enum(['SUPPORTED', 'CONFLICTED', 'UNVERIFIED']),
|
|
620
|
+
nature: z.enum(['FACTUAL', 'JUDGMENT']).default('JUDGMENT'),
|
|
591
621
|
load_bearing: z.boolean(),
|
|
592
622
|
if_false: z.enum(['STOP', 'PIVOT', 'CONDITION', 'MINOR']),
|
|
593
623
|
sensitivity: z.enum(['DECISIVE', 'MATERIAL', 'LOW']),
|
|
@@ -899,7 +929,32 @@ export const ImplementationPlan = z.object({
|
|
|
899
929
|
acceptance_test: z.string().min(1),
|
|
900
930
|
}).strict()).min(1).max(7),
|
|
901
931
|
}).strict();
|
|
902
|
-
|
|
932
|
+
const ReaderBriefBase = z.object({
|
|
933
|
+
headline: z.string().min(1).max(160),
|
|
934
|
+
bottom_line: z.string().min(1).max(1200),
|
|
935
|
+
sections: z.array(z.object({
|
|
936
|
+
heading: z.string().min(1).max(120),
|
|
937
|
+
summary: z.string().min(1).max(1000),
|
|
938
|
+
bullets: z.array(z.string().min(1).max(500)).max(6),
|
|
939
|
+
}).strict()).min(2).max(6),
|
|
940
|
+
next_step: z.string().min(1).max(600),
|
|
941
|
+
caveats: z.array(z.string().min(1).max(500)).max(3),
|
|
942
|
+
source_ids: z.array(z.string().min(1)).max(8),
|
|
943
|
+
}).strict();
|
|
944
|
+
const READER_AUDIT_ENUM = /\b(?:UNVERIFIED|UNVERIFIABLE|PARTIAL|CONFLICTED)\b/;
|
|
945
|
+
const READER_AUDIT_LANGUAGE = /\b(?:structural score|evidence coverage|verification coverage|claim ids?|graph ids?|provider calls?|model calls?|answer editor|reader_brief)\b/i;
|
|
946
|
+
export const ReaderBrief = ReaderBriefBase.superRefine((brief, ctx) => {
|
|
947
|
+
const text = JSON.stringify(brief);
|
|
948
|
+
if (READER_AUDIT_ENUM.test(text) || READER_AUDIT_LANGUAGE.test(text)) {
|
|
949
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'reader_brief contains internal audit language' });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
/** Contextual reader-language check: only reject IDs that actually exist in this run. */
|
|
953
|
+
export function readerBriefIssues(brief, knownIds) {
|
|
954
|
+
const text = JSON.stringify(brief);
|
|
955
|
+
return [...knownIds].filter((id) => new RegExp(`\\b${id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(text));
|
|
956
|
+
}
|
|
957
|
+
const ActionPlanBase = z
|
|
903
958
|
.object({
|
|
904
959
|
actions: z
|
|
905
960
|
.array(z
|
|
@@ -912,13 +967,19 @@ export const ActionPlan = z
|
|
|
912
967
|
kill_signal: z.string().min(1),
|
|
913
968
|
})
|
|
914
969
|
.strict())
|
|
915
|
-
.min(1)
|
|
916
970
|
.max(7),
|
|
917
971
|
sequencing_note: z.string().min(1),
|
|
918
972
|
feature_backlog: FeatureBacklog.optional(),
|
|
919
973
|
implementation_plan: ImplementationPlan.optional(),
|
|
974
|
+
// Optional only so pre-v5 run artifacts remain replayable. New decision contracts require it in S9b.
|
|
975
|
+
reader_brief: ReaderBrief.optional(),
|
|
920
976
|
})
|
|
921
977
|
.strict();
|
|
978
|
+
export const ActionPlan = ActionPlanBase.superRefine((plan, ctx) => {
|
|
979
|
+
if (plan.actions.length === 0 && !plan.reader_brief) {
|
|
980
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['actions'], message: 'an empty action plan requires reader_brief' });
|
|
981
|
+
}
|
|
982
|
+
});
|
|
922
983
|
export const PlannerUnavailable = z.object({
|
|
923
984
|
kind: z.literal('PlannerUnavailable'),
|
|
924
985
|
reason: z.enum(['budget_exhausted', 'planner_failed']),
|
|
@@ -934,7 +995,7 @@ export const QuickDecisionModel = z.object({
|
|
|
934
995
|
key_points: z.array(z.string().min(1)).min(2).max(8),
|
|
935
996
|
dissent: z.array(z.string().min(1)).min(1).max(4),
|
|
936
997
|
confidence_notes: z.string().min(1),
|
|
937
|
-
action_plan:
|
|
998
|
+
action_plan: ActionPlanBase.extend({ reader_brief: ReaderBrief }),
|
|
938
999
|
}).strict().superRefine((decision, ctx) => {
|
|
939
1000
|
const hasConditions = decision.conditions.length > 0;
|
|
940
1001
|
if (decision.recommendation === 'PROCEED_WITH_CONDITIONS' && !hasConditions) {
|
|
@@ -987,11 +1048,14 @@ export const RunMeta = z.object({
|
|
|
987
1048
|
flags: z.array(z.enum([
|
|
988
1049
|
'synthesis_suspect',
|
|
989
1050
|
'low_diversity',
|
|
1051
|
+
'weak_seat',
|
|
990
1052
|
'plan_skipped',
|
|
991
1053
|
'plan_fallback',
|
|
992
1054
|
'headless_intent',
|
|
993
1055
|
'verification_skipped',
|
|
994
1056
|
'research_ungrounded',
|
|
1057
|
+
'source_fallback_search',
|
|
1058
|
+
'deliverable_gap',
|
|
995
1059
|
'single_model',
|
|
996
1060
|
])).optional(),
|
|
997
1061
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Analyst playbook — pressure-test the idea
|
|
1
|
+
# Analyst playbook — strengthen and pressure-test the idea
|
|
2
2
|
|
|
3
|
-
You are an
|
|
4
|
-
where it breaks.
|
|
3
|
+
You are an independent analyst on a decision panel. Build the strongest useful version for your lane,
|
|
4
|
+
then expose where it breaks. Creation and criticism are both required.
|
|
5
5
|
|
|
6
6
|
## Strongest version first
|
|
7
7
|
- Before attacking, state the best honest version of the idea: who it is for and why it would win.
|
|
8
|
-
-
|
|
8
|
+
- Test THAT version, not a weak strawman. If the steelman is thin, that itself is a finding.
|
|
9
9
|
|
|
10
10
|
## Positions — make the stance explicit
|
|
11
11
|
- State each decision-critical proposition and take an explicit SUPPORT, OPPOSE, MIXED, or UNKNOWN
|
|
@@ -45,5 +45,5 @@ ownership; emit one explicit coverage entry for every owned dimension.
|
|
|
45
45
|
- Phrase each so that a yes/no or a single number would actually flip your assessment.
|
|
46
46
|
|
|
47
47
|
## Do not
|
|
48
|
-
- No
|
|
48
|
+
- No unsupported hype or generic feature wishlists. Explain the mechanism and user value.
|
|
49
49
|
- No unanchored position or evidence. No proposition you cannot tie to the decision.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Chair playbook — rule on the real disagreement
|
|
2
|
+
|
|
3
|
+
Add to your base instructions; the schema and caps are unchanged and always win.
|
|
4
|
+
|
|
5
|
+
## Synthesis discipline
|
|
6
|
+
- Count convergence only when seats reached the same conclusion independently with different evidence.
|
|
7
|
+
Two seats repeating the same hedge is not agreement.
|
|
8
|
+
- Never smooth over a clash. State both readings, compare their evidence quality, and rule.
|
|
9
|
+
- A strong dissent beats a weak majority. Side with the better-evidenced minority when warranted and
|
|
10
|
+
explain why in `key_points`.
|
|
11
|
+
- Never answer "it depends". Choose the required verdict enum; put contingencies into concrete,
|
|
12
|
+
checkable conditions that name their claim.
|
|
13
|
+
- Lead `key_points` with the evidence or result that would change the verdict.
|
|
14
|
+
- Report a blind spot only when verification or rebuttal actually exposed it.
|
|
15
|
+
|
|
16
|
+
## First action
|
|
17
|
+
- Anchor the first action to the single cheapest check that could kill or confirm the decision.
|
|
18
|
+
- Pick one thing first, not a list disguised as one action.
|
|
@@ -3,5 +3,16 @@
|
|
|
3
3
|
Own unit economics and capital needs, technical and operational feasibility, supply constraints,
|
|
4
4
|
legal and compliance exposure, scalability, failure modes, and the cheapest decisive kill criteria.
|
|
5
5
|
|
|
6
|
+
Act as the skeptic/executor for requested deliverables. When FEATURE_BACKLOG is requested, produce 2–4
|
|
7
|
+
`deliverable_proposals` that preserve the standout value while remaining credible under the stated deadline.
|
|
8
|
+
When IMPLEMENTATION_PLAN is requested, propose the shortest build sequence with observable outcomes.
|
|
9
|
+
|
|
6
10
|
Steelman the idea first. Cross into another lane only for a load-bearing issue. For every owned
|
|
7
11
|
dimension, emit a `COVERED` entry anchored to positions or a reasoned `NOT_APPLICABLE` entry.
|
|
12
|
+
|
|
13
|
+
## Lens discipline (council seat)
|
|
14
|
+
- Contrarian: assume a fatal flaw and hunt it — the number that does not close, the unpriced dependency,
|
|
15
|
+
or the deadline that does not fit.
|
|
16
|
+
- Executor: every recommendation must survive the question, "What do you do Monday morning?"
|
|
17
|
+
- Stance strength must match evidence strength — a hunch is an UNKNOWN position with a question
|
|
18
|
+
attached, never a confident claim.
|
|
@@ -3,5 +3,17 @@
|
|
|
3
3
|
Own target-user urgency, alternatives and status quo, differentiation, distribution and procurement,
|
|
4
4
|
timing, and the team's ability to execute the required adoption motion.
|
|
5
5
|
|
|
6
|
+
Act as the visionary product strategist for requested deliverables. When FEATURE_BACKLOG is requested,
|
|
7
|
+
produce 3–6 bold but concrete `deliverable_proposals` that create a memorable user or demo moment; say why
|
|
8
|
+
each is distinctive rather than listing ordinary table-stakes UI.
|
|
9
|
+
|
|
6
10
|
Steelman the idea first. Cross into another lane only for a load-bearing issue. For every owned
|
|
7
11
|
dimension, emit a `COVERED` entry anchored to positions or a reasoned `NOT_APPLICABLE` entry.
|
|
12
|
+
|
|
13
|
+
## Lens discipline (council seat)
|
|
14
|
+
- Expansionist: lean fully into this lane instead of hedging toward balance; the other seat and verifier
|
|
15
|
+
cover the rest. Name the undervalued upside with a number and evidence ID, or mark it UNKNOWN.
|
|
16
|
+
- Outsider: read the pitch with zero context. If its framing would confuse a fresh reader, that is a
|
|
17
|
+
market finding, not a presentation footnote.
|
|
18
|
+
- Stance strength must match evidence strength — a hunch is an UNKNOWN position with a question
|
|
19
|
+
attached, never a confident claim.
|