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.
Files changed (86) hide show
  1. package/bin/gswd-tools.cjs +228 -0
  2. package/bin/install.js +8 -0
  3. package/commands/gswd/imagine.md +7 -1
  4. package/commands/gswd/start.md +507 -32
  5. package/dist/lib/audit.d.ts +205 -0
  6. package/dist/lib/audit.js +805 -0
  7. package/dist/lib/bootstrap.d.ts +103 -0
  8. package/dist/lib/bootstrap.js +563 -0
  9. package/dist/lib/compile.d.ts +239 -0
  10. package/dist/lib/compile.js +1152 -0
  11. package/dist/lib/config.d.ts +49 -0
  12. package/dist/lib/config.js +150 -0
  13. package/dist/lib/imagine-agents.d.ts +54 -0
  14. package/dist/lib/imagine-agents.js +185 -0
  15. package/dist/lib/imagine-gate.d.ts +47 -0
  16. package/dist/lib/imagine-gate.js +131 -0
  17. package/dist/lib/imagine-input.d.ts +46 -0
  18. package/dist/lib/imagine-input.js +233 -0
  19. package/dist/lib/imagine-synthesis.d.ts +90 -0
  20. package/dist/lib/imagine-synthesis.js +453 -0
  21. package/dist/lib/imagine.d.ts +56 -0
  22. package/dist/lib/imagine.js +413 -0
  23. package/dist/lib/intake.d.ts +27 -0
  24. package/dist/lib/intake.js +82 -0
  25. package/dist/lib/parse.d.ts +59 -0
  26. package/dist/lib/parse.js +171 -0
  27. package/dist/lib/render.d.ts +309 -0
  28. package/dist/lib/render.js +624 -0
  29. package/dist/lib/specify-agents.d.ts +120 -0
  30. package/dist/lib/specify-agents.js +269 -0
  31. package/dist/lib/specify-journeys.d.ts +124 -0
  32. package/dist/lib/specify-journeys.js +279 -0
  33. package/dist/lib/specify-nfr.d.ts +45 -0
  34. package/dist/lib/specify-nfr.js +159 -0
  35. package/dist/lib/specify-roles.d.ts +46 -0
  36. package/dist/lib/specify-roles.js +88 -0
  37. package/dist/lib/specify.d.ts +70 -0
  38. package/dist/lib/specify.js +676 -0
  39. package/dist/lib/state.d.ts +140 -0
  40. package/dist/lib/state.js +340 -0
  41. package/dist/tests/audit.test.d.ts +4 -0
  42. package/dist/tests/audit.test.js +1579 -0
  43. package/dist/tests/bootstrap.test.d.ts +5 -0
  44. package/dist/tests/bootstrap.test.js +611 -0
  45. package/dist/tests/compile.test.d.ts +4 -0
  46. package/dist/tests/compile.test.js +862 -0
  47. package/dist/tests/config.test.d.ts +4 -0
  48. package/dist/tests/config.test.js +191 -0
  49. package/dist/tests/imagine-agents.test.d.ts +6 -0
  50. package/dist/tests/imagine-agents.test.js +179 -0
  51. package/dist/tests/imagine-gate.test.d.ts +6 -0
  52. package/dist/tests/imagine-gate.test.js +264 -0
  53. package/dist/tests/imagine-input.test.d.ts +6 -0
  54. package/dist/tests/imagine-input.test.js +283 -0
  55. package/dist/tests/imagine-synthesis.test.d.ts +7 -0
  56. package/dist/tests/imagine-synthesis.test.js +380 -0
  57. package/dist/tests/imagine.test.d.ts +8 -0
  58. package/dist/tests/imagine.test.js +406 -0
  59. package/dist/tests/parse.test.d.ts +4 -0
  60. package/dist/tests/parse.test.js +285 -0
  61. package/dist/tests/render.test.d.ts +4 -0
  62. package/dist/tests/render.test.js +236 -0
  63. package/dist/tests/specify-agents.test.d.ts +4 -0
  64. package/dist/tests/specify-agents.test.js +352 -0
  65. package/dist/tests/specify-journeys.test.d.ts +5 -0
  66. package/dist/tests/specify-journeys.test.js +440 -0
  67. package/dist/tests/specify-nfr.test.d.ts +4 -0
  68. package/dist/tests/specify-nfr.test.js +205 -0
  69. package/dist/tests/specify-roles.test.d.ts +4 -0
  70. package/dist/tests/specify-roles.test.js +136 -0
  71. package/dist/tests/specify.test.d.ts +9 -0
  72. package/dist/tests/specify.test.js +544 -0
  73. package/dist/tests/state.test.d.ts +4 -0
  74. package/dist/tests/state.test.js +316 -0
  75. package/lib/bootstrap.ts +37 -11
  76. package/lib/compile.ts +426 -4
  77. package/lib/imagine-agents.ts +53 -7
  78. package/lib/imagine-synthesis.ts +170 -6
  79. package/lib/imagine.ts +59 -5
  80. package/lib/intake.ts +60 -0
  81. package/lib/parse.ts +2 -1
  82. package/lib/render.ts +566 -5
  83. package/lib/specify-agents.ts +25 -3
  84. package/lib/state.ts +115 -0
  85. package/package.json +4 -2
  86. 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>;