gswd 1.0.0 → 1.1.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/bin/gswd-tools.cjs +228 -0
- package/bin/install.js +8 -0
- package/commands/gswd/imagine.md +7 -1
- package/commands/gswd/start.md +507 -32
- package/dist/lib/audit.d.ts +205 -0
- package/dist/lib/audit.js +805 -0
- package/dist/lib/bootstrap.d.ts +103 -0
- package/dist/lib/bootstrap.js +563 -0
- package/dist/lib/compile.d.ts +239 -0
- package/dist/lib/compile.js +1152 -0
- package/dist/lib/config.d.ts +49 -0
- package/dist/lib/config.js +150 -0
- package/dist/lib/imagine-agents.d.ts +54 -0
- package/dist/lib/imagine-agents.js +185 -0
- package/dist/lib/imagine-gate.d.ts +47 -0
- package/dist/lib/imagine-gate.js +131 -0
- package/dist/lib/imagine-input.d.ts +46 -0
- package/dist/lib/imagine-input.js +233 -0
- package/dist/lib/imagine-synthesis.d.ts +90 -0
- package/dist/lib/imagine-synthesis.js +453 -0
- package/dist/lib/imagine.d.ts +56 -0
- package/dist/lib/imagine.js +413 -0
- package/dist/lib/intake.d.ts +27 -0
- package/dist/lib/intake.js +82 -0
- package/dist/lib/parse.d.ts +59 -0
- package/dist/lib/parse.js +171 -0
- package/dist/lib/render.d.ts +309 -0
- package/dist/lib/render.js +624 -0
- package/dist/lib/specify-agents.d.ts +120 -0
- package/dist/lib/specify-agents.js +269 -0
- package/dist/lib/specify-journeys.d.ts +124 -0
- package/dist/lib/specify-journeys.js +279 -0
- package/dist/lib/specify-nfr.d.ts +45 -0
- package/dist/lib/specify-nfr.js +159 -0
- package/dist/lib/specify-roles.d.ts +46 -0
- package/dist/lib/specify-roles.js +88 -0
- package/dist/lib/specify.d.ts +70 -0
- package/dist/lib/specify.js +676 -0
- package/dist/lib/state.d.ts +140 -0
- package/dist/lib/state.js +340 -0
- package/dist/tests/audit.test.d.ts +4 -0
- package/dist/tests/audit.test.js +1579 -0
- package/dist/tests/bootstrap.test.d.ts +5 -0
- package/dist/tests/bootstrap.test.js +611 -0
- package/dist/tests/compile.test.d.ts +4 -0
- package/dist/tests/compile.test.js +862 -0
- package/dist/tests/config.test.d.ts +4 -0
- package/dist/tests/config.test.js +191 -0
- package/dist/tests/imagine-agents.test.d.ts +6 -0
- package/dist/tests/imagine-agents.test.js +179 -0
- package/dist/tests/imagine-gate.test.d.ts +6 -0
- package/dist/tests/imagine-gate.test.js +264 -0
- package/dist/tests/imagine-input.test.d.ts +6 -0
- package/dist/tests/imagine-input.test.js +283 -0
- package/dist/tests/imagine-synthesis.test.d.ts +7 -0
- package/dist/tests/imagine-synthesis.test.js +380 -0
- package/dist/tests/imagine.test.d.ts +8 -0
- package/dist/tests/imagine.test.js +406 -0
- package/dist/tests/parse.test.d.ts +4 -0
- package/dist/tests/parse.test.js +285 -0
- package/dist/tests/render.test.d.ts +4 -0
- package/dist/tests/render.test.js +236 -0
- package/dist/tests/specify-agents.test.d.ts +4 -0
- package/dist/tests/specify-agents.test.js +352 -0
- package/dist/tests/specify-journeys.test.d.ts +5 -0
- package/dist/tests/specify-journeys.test.js +440 -0
- package/dist/tests/specify-nfr.test.d.ts +4 -0
- package/dist/tests/specify-nfr.test.js +205 -0
- package/dist/tests/specify-roles.test.d.ts +4 -0
- package/dist/tests/specify-roles.test.js +136 -0
- package/dist/tests/specify.test.d.ts +9 -0
- package/dist/tests/specify.test.js +544 -0
- package/dist/tests/state.test.d.ts +4 -0
- package/dist/tests/state.test.js +316 -0
- package/lib/bootstrap.ts +37 -11
- package/lib/compile.ts +426 -4
- package/lib/imagine-agents.ts +53 -7
- package/lib/imagine-synthesis.ts +170 -6
- package/lib/imagine.ts +59 -5
- package/lib/intake.ts +60 -0
- package/lib/parse.ts +2 -1
- package/lib/render.ts +566 -5
- package/lib/specify-agents.ts +25 -3
- package/lib/state.ts +115 -0
- package/package.json +4 -2
- package/templates/gswd/DECISIONS.template.md +3 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GSWD Imagine Synthesis Module — Direction synthesis and auto-mode scoring
|
|
4
|
+
*
|
|
5
|
+
* Combines research from 5 agents into 3 direction options.
|
|
6
|
+
* Provides auto-mode scoring (pain x willingness-to-pay x reachability)
|
|
7
|
+
* for unattended decision-making per GSWD_SPEC Section 8.2 Step 4 and Section 10.
|
|
8
|
+
*
|
|
9
|
+
* Schema: GSWD_SPEC.md Section 8.2 Steps 4-5, Section 10
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AGENT_DISPLAY_NAMES = void 0;
|
|
13
|
+
exports.synthesizeDirections = synthesizeDirections;
|
|
14
|
+
exports.scoreIcpOptions = scoreIcpOptions;
|
|
15
|
+
exports.buildResearchSummary = buildResearchSummary;
|
|
16
|
+
exports.autoSelectDirection = autoSelectDirection;
|
|
17
|
+
/**
|
|
18
|
+
* User-friendly display names for imagine agents.
|
|
19
|
+
*/
|
|
20
|
+
exports.AGENT_DISPLAY_NAMES = {
|
|
21
|
+
'market-researcher': 'Market Landscape',
|
|
22
|
+
'icp-persona': 'Ideal Customer',
|
|
23
|
+
'positioning': 'Positioning and GTM',
|
|
24
|
+
'brainstorm-alternatives': 'Product Directions',
|
|
25
|
+
'devils-advocate': 'Risks and Challenges',
|
|
26
|
+
};
|
|
27
|
+
// ─── Keyword Lists for Scoring ──────────────────────────────────────────────
|
|
28
|
+
const PAIN_KEYWORDS = [
|
|
29
|
+
'pain', 'frustration', 'struggle', 'hate', 'waste', 'broken',
|
|
30
|
+
'manual', 'slow', 'expensive', 'tedious', 'annoying', 'difficult',
|
|
31
|
+
'complex', 'error-prone', 'time-consuming', 'inefficient',
|
|
32
|
+
];
|
|
33
|
+
const WTP_KEYWORDS = [
|
|
34
|
+
'pay', 'budget', 'spend', 'invest', 'subscribe', 'cost',
|
|
35
|
+
'pricing', 'premium', 'revenue', 'monetize', 'purchase',
|
|
36
|
+
'willing to pay', 'price point', 'subscription',
|
|
37
|
+
];
|
|
38
|
+
const REACHABILITY_KEYWORDS = [
|
|
39
|
+
'community', 'forum', 'slack', 'twitter', 'meetup', 'conference',
|
|
40
|
+
'newsletter', 'open source', 'reddit', 'discord', 'linkedin',
|
|
41
|
+
'github', 'youtube', 'blog', 'podcast', 'channel',
|
|
42
|
+
];
|
|
43
|
+
// ─── Internal Helpers ────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Count keyword occurrences in text (case-insensitive).
|
|
46
|
+
* Returns a score clamped to [0, 10].
|
|
47
|
+
*/
|
|
48
|
+
function countKeywords(text, keywords) {
|
|
49
|
+
const lower = text.toLowerCase();
|
|
50
|
+
let count = 0;
|
|
51
|
+
for (const kw of keywords) {
|
|
52
|
+
// Count all occurrences of each keyword
|
|
53
|
+
let idx = 0;
|
|
54
|
+
while (true) {
|
|
55
|
+
const found = lower.indexOf(kw, idx);
|
|
56
|
+
if (found === -1)
|
|
57
|
+
break;
|
|
58
|
+
count++;
|
|
59
|
+
idx = found + kw.length;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Clamp to 0-10 range
|
|
63
|
+
return Math.min(10, count);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Extract content for a specific agent from the results array.
|
|
67
|
+
* Returns empty string if agent not found or failed.
|
|
68
|
+
*/
|
|
69
|
+
function getAgentContent(results, agentName) {
|
|
70
|
+
const result = results.find(r => r.agent === agentName);
|
|
71
|
+
if (!result || result.status === 'failed')
|
|
72
|
+
return '';
|
|
73
|
+
return result.content;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parse direction sections from brainstorm-alternatives output.
|
|
77
|
+
* Looks for ## Direction 1/2/3 headings and extracts sub-fields.
|
|
78
|
+
*/
|
|
79
|
+
function parseDirectionSections(content) {
|
|
80
|
+
const directions = [];
|
|
81
|
+
// Match ## Direction N: ... or ## Direction N — ...
|
|
82
|
+
const regex = /^##\s+(Direction\s+\d+[:\s—–-]*[^\n]*)/gm;
|
|
83
|
+
const matches = [];
|
|
84
|
+
let match;
|
|
85
|
+
while ((match = regex.exec(content)) !== null) {
|
|
86
|
+
matches.push({ label: match[1].trim(), index: match.index });
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < matches.length; i++) {
|
|
89
|
+
const start = matches[i].index;
|
|
90
|
+
const end = i + 1 < matches.length ? matches[i + 1].index : content.length;
|
|
91
|
+
const body = content.slice(start, end).trim();
|
|
92
|
+
directions.push({ label: matches[i].label, body });
|
|
93
|
+
}
|
|
94
|
+
return directions;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Extract a named field from a direction body.
|
|
98
|
+
* Looks for **FieldName:** pattern.
|
|
99
|
+
*/
|
|
100
|
+
function extractField(body, fieldName) {
|
|
101
|
+
const regex = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+?)(?=\\n\\*\\*|$)`, 'is');
|
|
102
|
+
const match = body.match(regex);
|
|
103
|
+
return match ? match[1].trim() : '';
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build evidence-based rationale bullets by cross-referencing agent content.
|
|
107
|
+
* Returns 2-4 bullets traceable to specific agent findings.
|
|
108
|
+
*/
|
|
109
|
+
function buildRationale(directionBody, marketContent, icpContent, positioningContent) {
|
|
110
|
+
const rationale = [];
|
|
111
|
+
// Extract market evidence
|
|
112
|
+
if (marketContent) {
|
|
113
|
+
const marketMatch = marketContent.match(/##\s*Market (?:Overview|Gaps?)\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
114
|
+
if (marketMatch) {
|
|
115
|
+
const firstBullet = marketMatch[1].trim().split('\n')
|
|
116
|
+
.filter(l => l.trim().startsWith('-') || l.trim().startsWith('*'))
|
|
117
|
+
.map(l => l.replace(/^[-*]\s*/, '').trim())
|
|
118
|
+
.find(l => l.length > 10);
|
|
119
|
+
if (firstBullet) {
|
|
120
|
+
rationale.push(`Market evidence: ${firstBullet}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Extract ICP evidence
|
|
125
|
+
if (icpContent) {
|
|
126
|
+
const painMatch = icpContent.match(/##\s*Pain Points?\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
127
|
+
if (painMatch) {
|
|
128
|
+
const firstPain = painMatch[1].trim().split('\n')
|
|
129
|
+
.filter(l => l.trim().startsWith('-') || l.trim().startsWith('*'))
|
|
130
|
+
.map(l => l.replace(/^[-*]\s*/, '').trim())
|
|
131
|
+
.find(l => l.length > 10);
|
|
132
|
+
if (firstPain) {
|
|
133
|
+
rationale.push(`ICP insight: ${firstPain}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Extract positioning evidence
|
|
138
|
+
if (positioningContent) {
|
|
139
|
+
const vpMatch = positioningContent.match(/##\s*Value Proposition\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
140
|
+
if (vpMatch) {
|
|
141
|
+
const firstLine = vpMatch[1].trim().split('\n')[0];
|
|
142
|
+
if (firstLine && firstLine.length > 10) {
|
|
143
|
+
rationale.push(`Competitive advantage: ${firstLine}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Extract direction-specific evidence from the body itself
|
|
148
|
+
const diffField = extractField(directionBody, 'Differentiator');
|
|
149
|
+
if (diffField && diffField.length > 10 && rationale.length < 4) {
|
|
150
|
+
rationale.push(`Direction differentiator: ${diffField}`);
|
|
151
|
+
}
|
|
152
|
+
// Ensure at least one rationale bullet
|
|
153
|
+
if (rationale.length === 0) {
|
|
154
|
+
rationale.push('Rationale unavailable — agent data incomplete');
|
|
155
|
+
}
|
|
156
|
+
return rationale;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build a Direction from parsed section data, enriched with ICP and risk data.
|
|
160
|
+
*/
|
|
161
|
+
function buildDirection(label, body, icpContent, risksContent, marketContent, positioningContent) {
|
|
162
|
+
const icp = extractField(body, 'ICP') || extractIcpSummary(icpContent);
|
|
163
|
+
const problem = extractField(body, 'Problem') || 'See agent research outputs';
|
|
164
|
+
const wedge = extractField(body, 'Wedge') || extractField(body, 'MVP scope') || 'To be refined';
|
|
165
|
+
const differentiator = extractField(body, 'Differentiator') || 'To be refined';
|
|
166
|
+
const riskField = extractField(body, 'Risk');
|
|
167
|
+
const risks = riskField ? [riskField] : extractRiskList(risksContent);
|
|
168
|
+
const rationale = buildRationale(body, marketContent || '', icpContent, positioningContent || '');
|
|
169
|
+
return { label, icp_summary: icp, problem_framing: problem, wedge, differentiator, risks, rationale };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract a short ICP summary from the icp-persona agent output.
|
|
173
|
+
*/
|
|
174
|
+
function extractIcpSummary(content) {
|
|
175
|
+
if (!content)
|
|
176
|
+
return 'ICP data unavailable (agent failed)';
|
|
177
|
+
// Try to find ## ICP Profile section and grab first meaningful line
|
|
178
|
+
const profileMatch = content.match(/##\s*ICP Profile\s*\n+([\s\S]*?)(?=\n##|\n$|$)/);
|
|
179
|
+
if (profileMatch) {
|
|
180
|
+
const lines = profileMatch[1].trim().split('\n').filter(l => l.trim().length > 0);
|
|
181
|
+
return lines.slice(0, 2).join('; ') || content.slice(0, 200);
|
|
182
|
+
}
|
|
183
|
+
return content.slice(0, 200);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract risk items from the devils-advocate agent output.
|
|
187
|
+
*/
|
|
188
|
+
function extractRiskList(content) {
|
|
189
|
+
if (!content)
|
|
190
|
+
return ['Risk data unavailable (agent failed)'];
|
|
191
|
+
const risksMatch = content.match(/##\s*Risks\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
192
|
+
if (risksMatch) {
|
|
193
|
+
const lines = risksMatch[1].trim().split('\n')
|
|
194
|
+
.filter(l => l.trim().startsWith('-') || l.trim().startsWith('*'))
|
|
195
|
+
.map(l => l.replace(/^[-*]\s*/, '').trim())
|
|
196
|
+
.filter(l => l.length > 0);
|
|
197
|
+
return lines.slice(0, 3);
|
|
198
|
+
}
|
|
199
|
+
return ['See devils-advocate output for risk details'];
|
|
200
|
+
}
|
|
201
|
+
// ─── Synthesis ──────────────────────────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* Synthesize 5 agent results into 3 direction options.
|
|
204
|
+
*
|
|
205
|
+
* Handles missing agent data gracefully — failed agents produce degraded
|
|
206
|
+
* output with warnings, not crashes.
|
|
207
|
+
*/
|
|
208
|
+
function synthesizeDirections(agentResults) {
|
|
209
|
+
const warnings = [];
|
|
210
|
+
const rawOutputs = {};
|
|
211
|
+
// Collect raw outputs and track failures
|
|
212
|
+
for (const result of agentResults) {
|
|
213
|
+
if (result.status === 'complete') {
|
|
214
|
+
rawOutputs[result.agent] = result.content;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
warnings.push(`Agent '${result.agent}' failed: ${result.error || 'unknown error'}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Extract content per agent
|
|
221
|
+
const marketContent = getAgentContent(agentResults, 'market-researcher');
|
|
222
|
+
const icpContent = getAgentContent(agentResults, 'icp-persona');
|
|
223
|
+
const positioningContent = getAgentContent(agentResults, 'positioning');
|
|
224
|
+
const brainstormContent = getAgentContent(agentResults, 'brainstorm-alternatives');
|
|
225
|
+
const risksContent = getAgentContent(agentResults, 'devils-advocate');
|
|
226
|
+
let proposed;
|
|
227
|
+
let alternatives;
|
|
228
|
+
if (brainstormContent) {
|
|
229
|
+
// Parse 3 directions from brainstorm agent
|
|
230
|
+
const sections = parseDirectionSections(brainstormContent);
|
|
231
|
+
if (sections.length >= 3) {
|
|
232
|
+
proposed = buildDirection(sections[0].label, sections[0].body, icpContent, risksContent, marketContent, positioningContent);
|
|
233
|
+
alternatives = [
|
|
234
|
+
buildDirection(sections[1].label, sections[1].body, icpContent, risksContent, marketContent, positioningContent),
|
|
235
|
+
buildDirection(sections[2].label, sections[2].body, icpContent, risksContent, marketContent, positioningContent),
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
else if (sections.length >= 1) {
|
|
239
|
+
// Partial parse — use what we have
|
|
240
|
+
proposed = buildDirection(sections[0].label, sections[0].body, icpContent, risksContent, marketContent, positioningContent);
|
|
241
|
+
alternatives = sections.slice(1).map(s => buildDirection(s.label, s.body, icpContent, risksContent, marketContent, positioningContent));
|
|
242
|
+
// Fill missing alternatives with degraded entries
|
|
243
|
+
while (alternatives.length < 2) {
|
|
244
|
+
alternatives.push(buildDegradedDirection(alternatives.length + 2, icpContent, positioningContent, risksContent));
|
|
245
|
+
}
|
|
246
|
+
warnings.push('Brainstorm agent produced fewer than 3 directions; some alternatives are degraded');
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// No parseable directions — build fully degraded
|
|
250
|
+
proposed = buildDegradedDirection(1, icpContent, positioningContent, risksContent);
|
|
251
|
+
alternatives = [
|
|
252
|
+
buildDegradedDirection(2, icpContent, positioningContent, risksContent),
|
|
253
|
+
buildDegradedDirection(3, icpContent, positioningContent, risksContent),
|
|
254
|
+
];
|
|
255
|
+
warnings.push('Could not parse directions from brainstorm agent output');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// Brainstorm agent failed entirely — build from remaining data
|
|
260
|
+
proposed = buildDegradedDirection(1, icpContent, positioningContent, risksContent);
|
|
261
|
+
alternatives = [
|
|
262
|
+
buildDegradedDirection(2, icpContent, positioningContent, risksContent),
|
|
263
|
+
buildDegradedDirection(3, icpContent, positioningContent, risksContent),
|
|
264
|
+
];
|
|
265
|
+
if (!warnings.some(w => w.includes('brainstorm-alternatives'))) {
|
|
266
|
+
warnings.push('Brainstorm-alternatives agent data unavailable; directions are degraded');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Enrich proposed direction with positioning data
|
|
270
|
+
if (positioningContent && proposed.differentiator === 'To be refined') {
|
|
271
|
+
const vpMatch = positioningContent.match(/##\s*Value Proposition\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
272
|
+
if (vpMatch) {
|
|
273
|
+
const firstLine = vpMatch[1].trim().split('\n')[0];
|
|
274
|
+
if (firstLine)
|
|
275
|
+
proposed.differentiator = firstLine;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { proposed, alternatives, agent_warnings: warnings, raw_agent_outputs: rawOutputs };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Build a degraded direction when brainstorm data is unavailable.
|
|
282
|
+
*/
|
|
283
|
+
function buildDegradedDirection(num, icpContent, positioningContent, risksContent) {
|
|
284
|
+
const icp = extractIcpSummary(icpContent);
|
|
285
|
+
const differentiator = positioningContent
|
|
286
|
+
? extractValueProp(positioningContent)
|
|
287
|
+
: 'Requires manual definition';
|
|
288
|
+
const risks = extractRiskList(risksContent);
|
|
289
|
+
return {
|
|
290
|
+
label: `Direction ${num}: Requires manual elaboration`,
|
|
291
|
+
icp_summary: icp,
|
|
292
|
+
problem_framing: 'Derived from available agent data — needs founder input',
|
|
293
|
+
wedge: 'To be defined',
|
|
294
|
+
differentiator,
|
|
295
|
+
risks,
|
|
296
|
+
rationale: ['Rationale unavailable — insufficient agent data'],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Extract value proposition from positioning agent output.
|
|
301
|
+
*/
|
|
302
|
+
function extractValueProp(content) {
|
|
303
|
+
const vpMatch = content.match(/##\s*Value Proposition\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
304
|
+
if (vpMatch) {
|
|
305
|
+
const firstLine = vpMatch[1].trim().split('\n')[0];
|
|
306
|
+
if (firstLine)
|
|
307
|
+
return firstLine;
|
|
308
|
+
}
|
|
309
|
+
return 'See positioning agent output';
|
|
310
|
+
}
|
|
311
|
+
// ─── Scoring ────────────────────────────────────────────────────────────────
|
|
312
|
+
/**
|
|
313
|
+
* Score directions using pain x willingness-to-pay x reachability heuristic.
|
|
314
|
+
*
|
|
315
|
+
* Uses keyword counting as a proxy since these are text-derived signals.
|
|
316
|
+
* Formula: pain * 0.4 + wtp * 0.3 + reachability * 0.3
|
|
317
|
+
*
|
|
318
|
+
* Per GSWD_SPEC: "choose ICP with highest pain x willingness-to-pay x reachability score"
|
|
319
|
+
*/
|
|
320
|
+
function scoreIcpOptions(directions) {
|
|
321
|
+
const scores = directions.map(dir => {
|
|
322
|
+
// Combine all text fields for keyword analysis
|
|
323
|
+
const text = [
|
|
324
|
+
dir.icp_summary,
|
|
325
|
+
dir.problem_framing,
|
|
326
|
+
dir.wedge,
|
|
327
|
+
dir.differentiator,
|
|
328
|
+
...dir.risks,
|
|
329
|
+
].join(' ');
|
|
330
|
+
const painScore = countKeywords(text, PAIN_KEYWORDS);
|
|
331
|
+
const wtpScore = countKeywords(text, WTP_KEYWORDS);
|
|
332
|
+
const reachScore = countKeywords(text, REACHABILITY_KEYWORDS);
|
|
333
|
+
const composite = painScore * 0.4 + wtpScore * 0.3 + reachScore * 0.3;
|
|
334
|
+
// Round to 2 decimal places
|
|
335
|
+
const score = Math.round(composite * 100) / 100;
|
|
336
|
+
return {
|
|
337
|
+
label: dir.label,
|
|
338
|
+
score,
|
|
339
|
+
rationale: `Pain: ${painScore}/10, WTP: ${wtpScore}/10, Reachability: ${reachScore}/10 => weighted ${score}`,
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
// Sort descending by score
|
|
343
|
+
scores.sort((a, b) => b.score - a.score);
|
|
344
|
+
// Find index in original array of highest-scoring direction
|
|
345
|
+
const topLabel = scores[0]?.label;
|
|
346
|
+
const index = directions.findIndex(d => d.label === topLabel);
|
|
347
|
+
return { index: index >= 0 ? index : 0, scores };
|
|
348
|
+
}
|
|
349
|
+
// ─── Research Summary ────────────────────────────────────────────────────────
|
|
350
|
+
/**
|
|
351
|
+
* Extract takeaway bullets from agent content.
|
|
352
|
+
* Looks for bullet items under headings, returns up to maxItems.
|
|
353
|
+
*/
|
|
354
|
+
function extractTakeaways(content, maxItems = 5) {
|
|
355
|
+
if (!content)
|
|
356
|
+
return [];
|
|
357
|
+
const bullets = [];
|
|
358
|
+
const lines = content.split('\n');
|
|
359
|
+
for (const line of lines) {
|
|
360
|
+
const trimmed = line.trim();
|
|
361
|
+
if (/^[-*]\s+/.test(trimmed) && trimmed.length > 10) {
|
|
362
|
+
bullets.push(trimmed.replace(/^[-*]\s+/, ''));
|
|
363
|
+
if (bullets.length >= maxItems)
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return bullets;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Build structured research summary from agent results.
|
|
371
|
+
* Each section: agent display name, 3-5 takeaway bullets, bridging paragraph.
|
|
372
|
+
* Product-contextualized using brief.vision and brief.target_user.
|
|
373
|
+
*/
|
|
374
|
+
function buildResearchSummary(agentResults, brief) {
|
|
375
|
+
const sections = [];
|
|
376
|
+
for (const result of agentResults) {
|
|
377
|
+
const displayName = exports.AGENT_DISPLAY_NAMES[result.agent] || result.agent;
|
|
378
|
+
if (result.status === 'failed' || !result.content) {
|
|
379
|
+
sections.push({
|
|
380
|
+
agentName: result.agent,
|
|
381
|
+
displayName,
|
|
382
|
+
takeaways: ['Agent failed — no findings available'],
|
|
383
|
+
bridge: `These findings could not shape the directions below due to agent failure.`,
|
|
384
|
+
});
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const takeaways = extractTakeaways(result.content);
|
|
388
|
+
// Ensure at least 3 takeaways; pad with content excerpts if needed
|
|
389
|
+
if (takeaways.length < 3) {
|
|
390
|
+
const lines = result.content.split('\n')
|
|
391
|
+
.filter(l => l.trim().length > 20 && !l.trim().startsWith('#'))
|
|
392
|
+
.slice(0, 3 - takeaways.length);
|
|
393
|
+
for (const line of lines) {
|
|
394
|
+
takeaways.push(line.trim().slice(0, 150));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Build product-contextualized bridge paragraph
|
|
398
|
+
const userCtx = brief.target_user || 'target users';
|
|
399
|
+
const visionCtx = brief.vision || 'the product';
|
|
400
|
+
const bridge = `These findings shaped the directions below by revealing how ${visionCtx} can best serve ${userCtx} in this domain.`;
|
|
401
|
+
sections.push({
|
|
402
|
+
agentName: result.agent,
|
|
403
|
+
displayName,
|
|
404
|
+
takeaways: takeaways.slice(0, 5),
|
|
405
|
+
bridge,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return sections;
|
|
409
|
+
}
|
|
410
|
+
// ─── Auto Selection ─────────────────────────────────────────────────────────
|
|
411
|
+
/**
|
|
412
|
+
* Auto-select the best direction and generate decision records.
|
|
413
|
+
*
|
|
414
|
+
* Per GSWD_SPEC Section 8.2 Auto behavior:
|
|
415
|
+
* - Choose ICP with highest pain x WTP x reachability score
|
|
416
|
+
* - Choose wedge with smallest scope that still hits an "aha"
|
|
417
|
+
* - Record decisions as Auto-chosen with rationale
|
|
418
|
+
*/
|
|
419
|
+
function autoSelectDirection(synthesis) {
|
|
420
|
+
const allDirections = [synthesis.proposed, ...synthesis.alternatives];
|
|
421
|
+
const scoreResult = scoreIcpOptions(allDirections);
|
|
422
|
+
const selected = allDirections[scoreResult.index];
|
|
423
|
+
const topScore = scoreResult.scores[0];
|
|
424
|
+
const now = new Date().toISOString();
|
|
425
|
+
const decisions = [
|
|
426
|
+
{
|
|
427
|
+
type: 'direction',
|
|
428
|
+
chosen: selected.label,
|
|
429
|
+
rationale: `Auto-selected as highest-scoring direction. ${topScore.rationale}`,
|
|
430
|
+
score: topScore.score,
|
|
431
|
+
recorded_at: now,
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
type: 'icp',
|
|
435
|
+
chosen: selected.icp_summary,
|
|
436
|
+
rationale: `ICP from ${selected.label} scored highest on pain x WTP x reachability composite`,
|
|
437
|
+
recorded_at: now,
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
type: 'wedge',
|
|
441
|
+
chosen: selected.wedge,
|
|
442
|
+
rationale: `Wedge selected as smallest scope delivering the "aha" moment from ${selected.label}`,
|
|
443
|
+
recorded_at: now,
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
type: 'metric',
|
|
447
|
+
chosen: 'Activation rate, week-1 retention',
|
|
448
|
+
rationale: 'Default metrics aligned with wedge strategy per GSWD_SPEC auto-mode policy',
|
|
449
|
+
recorded_at: now,
|
|
450
|
+
},
|
|
451
|
+
];
|
|
452
|
+
return { selected, decisions };
|
|
453
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSWD Imagine Workflow Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Full pipeline: input -> agents -> synthesis -> gate -> artifacts -> state
|
|
5
|
+
* Implements GSWD_SPEC Section 8.2 end-to-end.
|
|
6
|
+
*
|
|
7
|
+
* Both interactive and auto modes are supported:
|
|
8
|
+
* - Interactive: presents direction checkpoint, awaits selection
|
|
9
|
+
* - Auto: scores directions via pain x WTP x reachability, selects highest
|
|
10
|
+
*
|
|
11
|
+
* Schema: GSWD_SPEC.md Section 8.2
|
|
12
|
+
*/
|
|
13
|
+
import type { IntakeAnswers } from './imagine-input.js';
|
|
14
|
+
import type { SpawnFn } from './imagine-agents.js';
|
|
15
|
+
import type { Direction, AutoDecision, ResearchSummarySection } from './imagine-synthesis.js';
|
|
16
|
+
import type { GateResult } from './imagine-gate.js';
|
|
17
|
+
export interface ImagineOptions {
|
|
18
|
+
ideaFilePath?: string;
|
|
19
|
+
intakeAnswers?: IntakeAnswers;
|
|
20
|
+
autoMode?: boolean;
|
|
21
|
+
skipResearch?: boolean;
|
|
22
|
+
planningDir?: string;
|
|
23
|
+
configPath?: string;
|
|
24
|
+
spawnFn?: SpawnFn;
|
|
25
|
+
selectedDirectionIndex?: number;
|
|
26
|
+
previousAgentOutputs?: Record<string, string>;
|
|
27
|
+
userFeedback?: string;
|
|
28
|
+
visionStatement?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ImagineResult {
|
|
31
|
+
status: 'complete' | 'gate_failed' | 'error';
|
|
32
|
+
artifacts_written: string[];
|
|
33
|
+
gate_result?: GateResult;
|
|
34
|
+
auto_decisions?: AutoDecision[];
|
|
35
|
+
selected_direction?: Direction;
|
|
36
|
+
research_summary?: ResearchSummarySection[];
|
|
37
|
+
vision_statement?: string;
|
|
38
|
+
agent_headlines?: Array<{
|
|
39
|
+
agent: string;
|
|
40
|
+
headline: string;
|
|
41
|
+
status: 'complete' | 'failed';
|
|
42
|
+
}>;
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Run the Imagine workflow end-to-end.
|
|
47
|
+
*
|
|
48
|
+
* Implements GSWD_SPEC Section 8.2 Steps 1-6:
|
|
49
|
+
* 1. Load state and config
|
|
50
|
+
* 2. Collect founder input (file or intake)
|
|
51
|
+
* 3. Spawn parallel agents
|
|
52
|
+
* 4. Synthesize into proposed direction + 2 alternatives
|
|
53
|
+
* 5. Decision gate (must freeze)
|
|
54
|
+
* 6. Write docs and update state
|
|
55
|
+
*/
|
|
56
|
+
export declare function runImagine(options: ImagineOptions): Promise<ImagineResult>;
|