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
package/lib/imagine-synthesis.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { AgentResult } from './imagine-agents.js';
|
|
12
|
+
import type { StarterBrief } from './imagine-input.js';
|
|
12
13
|
|
|
13
14
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
14
15
|
|
|
@@ -19,8 +20,31 @@ export interface Direction {
|
|
|
19
20
|
wedge: string; // MVP boundary / entry point
|
|
20
21
|
differentiator: string; // What makes this unique
|
|
21
22
|
risks: string[]; // Top 2-3 risks for this direction
|
|
23
|
+
rationale?: string[]; // Evidence-based rationale bullets (optional for backward compat)
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Research summary section for post-agent display.
|
|
28
|
+
* Produced by buildResearchSummary(), consumed by renderResearchSummary().
|
|
29
|
+
*/
|
|
30
|
+
export interface ResearchSummarySection {
|
|
31
|
+
agentName: string; // Internal agent name (e.g., 'market-researcher')
|
|
32
|
+
displayName: string; // User-friendly name (e.g., 'Market Landscape')
|
|
33
|
+
takeaways: string[]; // 3-5 key bullet takeaways
|
|
34
|
+
bridge: string; // Bridging paragraph: "These findings shaped..."
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* User-friendly display names for imagine agents.
|
|
39
|
+
*/
|
|
40
|
+
export const AGENT_DISPLAY_NAMES: Record<string, string> = {
|
|
41
|
+
'market-researcher': 'Market Landscape',
|
|
42
|
+
'icp-persona': 'Ideal Customer',
|
|
43
|
+
'positioning': 'Positioning and GTM',
|
|
44
|
+
'brainstorm-alternatives': 'Product Directions',
|
|
45
|
+
'devils-advocate': 'Risks and Challenges',
|
|
46
|
+
};
|
|
47
|
+
|
|
24
48
|
export interface AutoDecision {
|
|
25
49
|
type: string; // 'icp' | 'wedge' | 'direction' | 'metric'
|
|
26
50
|
chosen: string; // What was selected
|
|
@@ -135,6 +159,71 @@ function extractField(body: string, fieldName: string): string {
|
|
|
135
159
|
return match ? match[1].trim() : '';
|
|
136
160
|
}
|
|
137
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Build evidence-based rationale bullets by cross-referencing agent content.
|
|
164
|
+
* Returns 2-4 bullets traceable to specific agent findings.
|
|
165
|
+
*/
|
|
166
|
+
function buildRationale(
|
|
167
|
+
directionBody: string,
|
|
168
|
+
marketContent: string,
|
|
169
|
+
icpContent: string,
|
|
170
|
+
positioningContent: string,
|
|
171
|
+
): string[] {
|
|
172
|
+
const rationale: string[] = [];
|
|
173
|
+
|
|
174
|
+
// Extract market evidence
|
|
175
|
+
if (marketContent) {
|
|
176
|
+
const marketMatch = marketContent.match(/##\s*Market (?:Overview|Gaps?)\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
177
|
+
if (marketMatch) {
|
|
178
|
+
const firstBullet = marketMatch[1].trim().split('\n')
|
|
179
|
+
.filter(l => l.trim().startsWith('-') || l.trim().startsWith('*'))
|
|
180
|
+
.map(l => l.replace(/^[-*]\s*/, '').trim())
|
|
181
|
+
.find(l => l.length > 10);
|
|
182
|
+
if (firstBullet) {
|
|
183
|
+
rationale.push(`Market evidence: ${firstBullet}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Extract ICP evidence
|
|
189
|
+
if (icpContent) {
|
|
190
|
+
const painMatch = icpContent.match(/##\s*Pain Points?\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
191
|
+
if (painMatch) {
|
|
192
|
+
const firstPain = painMatch[1].trim().split('\n')
|
|
193
|
+
.filter(l => l.trim().startsWith('-') || l.trim().startsWith('*'))
|
|
194
|
+
.map(l => l.replace(/^[-*]\s*/, '').trim())
|
|
195
|
+
.find(l => l.length > 10);
|
|
196
|
+
if (firstPain) {
|
|
197
|
+
rationale.push(`ICP insight: ${firstPain}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Extract positioning evidence
|
|
203
|
+
if (positioningContent) {
|
|
204
|
+
const vpMatch = positioningContent.match(/##\s*Value Proposition\s*\n+([\s\S]*?)(?=\n##|$)/);
|
|
205
|
+
if (vpMatch) {
|
|
206
|
+
const firstLine = vpMatch[1].trim().split('\n')[0];
|
|
207
|
+
if (firstLine && firstLine.length > 10) {
|
|
208
|
+
rationale.push(`Competitive advantage: ${firstLine}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Extract direction-specific evidence from the body itself
|
|
214
|
+
const diffField = extractField(directionBody, 'Differentiator');
|
|
215
|
+
if (diffField && diffField.length > 10 && rationale.length < 4) {
|
|
216
|
+
rationale.push(`Direction differentiator: ${diffField}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Ensure at least one rationale bullet
|
|
220
|
+
if (rationale.length === 0) {
|
|
221
|
+
rationale.push('Rationale unavailable — agent data incomplete');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return rationale;
|
|
225
|
+
}
|
|
226
|
+
|
|
138
227
|
/**
|
|
139
228
|
* Build a Direction from parsed section data, enriched with ICP and risk data.
|
|
140
229
|
*/
|
|
@@ -143,6 +232,8 @@ function buildDirection(
|
|
|
143
232
|
body: string,
|
|
144
233
|
icpContent: string,
|
|
145
234
|
risksContent: string,
|
|
235
|
+
marketContent?: string,
|
|
236
|
+
positioningContent?: string,
|
|
146
237
|
): Direction {
|
|
147
238
|
const icp = extractField(body, 'ICP') || extractIcpSummary(icpContent);
|
|
148
239
|
const problem = extractField(body, 'Problem') || 'See agent research outputs';
|
|
@@ -150,8 +241,9 @@ function buildDirection(
|
|
|
150
241
|
const differentiator = extractField(body, 'Differentiator') || 'To be refined';
|
|
151
242
|
const riskField = extractField(body, 'Risk');
|
|
152
243
|
const risks = riskField ? [riskField] : extractRiskList(risksContent);
|
|
244
|
+
const rationale = buildRationale(body, marketContent || '', icpContent, positioningContent || '');
|
|
153
245
|
|
|
154
|
-
return { label, icp_summary: icp, problem_framing: problem, wedge, differentiator, risks };
|
|
246
|
+
return { label, icp_summary: icp, problem_framing: problem, wedge, differentiator, risks, rationale };
|
|
155
247
|
}
|
|
156
248
|
|
|
157
249
|
/**
|
|
@@ -220,15 +312,15 @@ export function synthesizeDirections(agentResults: AgentResult[]): SynthesisResu
|
|
|
220
312
|
const sections = parseDirectionSections(brainstormContent);
|
|
221
313
|
|
|
222
314
|
if (sections.length >= 3) {
|
|
223
|
-
proposed = buildDirection(sections[0].label, sections[0].body, icpContent, risksContent);
|
|
315
|
+
proposed = buildDirection(sections[0].label, sections[0].body, icpContent, risksContent, marketContent, positioningContent);
|
|
224
316
|
alternatives = [
|
|
225
|
-
buildDirection(sections[1].label, sections[1].body, icpContent, risksContent),
|
|
226
|
-
buildDirection(sections[2].label, sections[2].body, icpContent, risksContent),
|
|
317
|
+
buildDirection(sections[1].label, sections[1].body, icpContent, risksContent, marketContent, positioningContent),
|
|
318
|
+
buildDirection(sections[2].label, sections[2].body, icpContent, risksContent, marketContent, positioningContent),
|
|
227
319
|
];
|
|
228
320
|
} else if (sections.length >= 1) {
|
|
229
321
|
// Partial parse — use what we have
|
|
230
|
-
proposed = buildDirection(sections[0].label, sections[0].body, icpContent, risksContent);
|
|
231
|
-
alternatives = sections.slice(1).map(s => buildDirection(s.label, s.body, icpContent, risksContent));
|
|
322
|
+
proposed = buildDirection(sections[0].label, sections[0].body, icpContent, risksContent, marketContent, positioningContent);
|
|
323
|
+
alternatives = sections.slice(1).map(s => buildDirection(s.label, s.body, icpContent, risksContent, marketContent, positioningContent));
|
|
232
324
|
// Fill missing alternatives with degraded entries
|
|
233
325
|
while (alternatives.length < 2) {
|
|
234
326
|
alternatives.push(buildDegradedDirection(alternatives.length + 2, icpContent, positioningContent, risksContent));
|
|
@@ -289,6 +381,7 @@ function buildDegradedDirection(
|
|
|
289
381
|
wedge: 'To be defined',
|
|
290
382
|
differentiator,
|
|
291
383
|
risks,
|
|
384
|
+
rationale: ['Rationale unavailable — insufficient agent data'],
|
|
292
385
|
};
|
|
293
386
|
}
|
|
294
387
|
|
|
@@ -350,6 +443,77 @@ export function scoreIcpOptions(directions: Direction[]): ScoreResult {
|
|
|
350
443
|
return { index: index >= 0 ? index : 0, scores };
|
|
351
444
|
}
|
|
352
445
|
|
|
446
|
+
// ─── Research Summary ────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Extract takeaway bullets from agent content.
|
|
450
|
+
* Looks for bullet items under headings, returns up to maxItems.
|
|
451
|
+
*/
|
|
452
|
+
function extractTakeaways(content: string, maxItems: number = 5): string[] {
|
|
453
|
+
if (!content) return [];
|
|
454
|
+
const bullets: string[] = [];
|
|
455
|
+
const lines = content.split('\n');
|
|
456
|
+
for (const line of lines) {
|
|
457
|
+
const trimmed = line.trim();
|
|
458
|
+
if (/^[-*]\s+/.test(trimmed) && trimmed.length > 10) {
|
|
459
|
+
bullets.push(trimmed.replace(/^[-*]\s+/, ''));
|
|
460
|
+
if (bullets.length >= maxItems) break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return bullets;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Build structured research summary from agent results.
|
|
468
|
+
* Each section: agent display name, 3-5 takeaway bullets, bridging paragraph.
|
|
469
|
+
* Product-contextualized using brief.vision and brief.target_user.
|
|
470
|
+
*/
|
|
471
|
+
export function buildResearchSummary(
|
|
472
|
+
agentResults: AgentResult[],
|
|
473
|
+
brief: StarterBrief,
|
|
474
|
+
): ResearchSummarySection[] {
|
|
475
|
+
const sections: ResearchSummarySection[] = [];
|
|
476
|
+
|
|
477
|
+
for (const result of agentResults) {
|
|
478
|
+
const displayName = AGENT_DISPLAY_NAMES[result.agent] || result.agent;
|
|
479
|
+
|
|
480
|
+
if (result.status === 'failed' || !result.content) {
|
|
481
|
+
sections.push({
|
|
482
|
+
agentName: result.agent,
|
|
483
|
+
displayName,
|
|
484
|
+
takeaways: ['Agent failed — no findings available'],
|
|
485
|
+
bridge: `These findings could not shape the directions below due to agent failure.`,
|
|
486
|
+
});
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const takeaways = extractTakeaways(result.content);
|
|
491
|
+
// Ensure at least 3 takeaways; pad with content excerpts if needed
|
|
492
|
+
if (takeaways.length < 3) {
|
|
493
|
+
const lines = result.content.split('\n')
|
|
494
|
+
.filter(l => l.trim().length > 20 && !l.trim().startsWith('#'))
|
|
495
|
+
.slice(0, 3 - takeaways.length);
|
|
496
|
+
for (const line of lines) {
|
|
497
|
+
takeaways.push(line.trim().slice(0, 150));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Build product-contextualized bridge paragraph
|
|
502
|
+
const userCtx = brief.target_user || 'target users';
|
|
503
|
+
const visionCtx = brief.vision || 'the product';
|
|
504
|
+
const bridge = `These findings shaped the directions below by revealing how ${visionCtx} can best serve ${userCtx} in this domain.`;
|
|
505
|
+
|
|
506
|
+
sections.push({
|
|
507
|
+
agentName: result.agent,
|
|
508
|
+
displayName,
|
|
509
|
+
takeaways: takeaways.slice(0, 5),
|
|
510
|
+
bridge,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return sections;
|
|
515
|
+
}
|
|
516
|
+
|
|
353
517
|
// ─── Auto Selection ─────────────────────────────────────────────────────────
|
|
354
518
|
|
|
355
519
|
/**
|
package/lib/imagine.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { parseIdeaFile, buildFromIntake, validateBrief } from './imagine-input.j
|
|
|
20
20
|
import type { StarterBrief, IntakeAnswers } from './imagine-input.js';
|
|
21
21
|
import { IMAGINE_AGENTS, orchestrateAgents } from './imagine-agents.js';
|
|
22
22
|
import type { AgentResult, SpawnFn } from './imagine-agents.js';
|
|
23
|
-
import { synthesizeDirections, autoSelectDirection, scoreIcpOptions } from './imagine-synthesis.js';
|
|
24
|
-
import type { Direction, AutoDecision, SynthesisResult } from './imagine-synthesis.js';
|
|
23
|
+
import { synthesizeDirections, autoSelectDirection, scoreIcpOptions, buildResearchSummary } from './imagine-synthesis.js';
|
|
24
|
+
import type { Direction, AutoDecision, SynthesisResult, ResearchSummarySection } from './imagine-synthesis.js';
|
|
25
25
|
import { validateDecisionGate } from './imagine-gate.js';
|
|
26
26
|
import type { GateResult } from './imagine-gate.js';
|
|
27
27
|
|
|
@@ -36,6 +36,9 @@ export interface ImagineOptions {
|
|
|
36
36
|
configPath?: string; // Override config.json path (for testing)
|
|
37
37
|
spawnFn?: SpawnFn; // Task() wrapper (injectable for testing)
|
|
38
38
|
selectedDirectionIndex?: number; // Manual direction selection (0-based)
|
|
39
|
+
previousAgentOutputs?: Record<string, string>; // For re-run augmentation (IMAGINE-03)
|
|
40
|
+
userFeedback?: string; // For re-run augmentation (IMAGINE-03)
|
|
41
|
+
visionStatement?: string; // Pre-approved vision (from Accept/Edit flow)
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export interface ImagineResult {
|
|
@@ -44,6 +47,9 @@ export interface ImagineResult {
|
|
|
44
47
|
gate_result?: GateResult;
|
|
45
48
|
auto_decisions?: AutoDecision[];
|
|
46
49
|
selected_direction?: Direction;
|
|
50
|
+
research_summary?: ResearchSummarySection[]; // Structured per-agent summary (IMAGINE-01)
|
|
51
|
+
vision_statement?: string; // Frozen vision statement (IMAGINE-05)
|
|
52
|
+
agent_headlines?: Array<{ agent: string; headline: string; status: 'complete' | 'failed' }>; // Per-agent completion headlines (COMM-03)
|
|
47
53
|
error?: string;
|
|
48
54
|
}
|
|
49
55
|
|
|
@@ -56,6 +62,7 @@ function buildImagineContent(
|
|
|
56
62
|
brief: StarterBrief,
|
|
57
63
|
selected: Direction,
|
|
58
64
|
synthesis: SynthesisResult,
|
|
65
|
+
visionStatement: string,
|
|
59
66
|
autoDecisions?: AutoDecision[],
|
|
60
67
|
): string {
|
|
61
68
|
const alternatives = synthesis.alternatives
|
|
@@ -69,7 +76,7 @@ function buildImagineContent(
|
|
|
69
76
|
return `# Imagine
|
|
70
77
|
|
|
71
78
|
## Vision
|
|
72
|
-
${
|
|
79
|
+
${visionStatement}
|
|
73
80
|
|
|
74
81
|
## Target User
|
|
75
82
|
${brief.target_user}
|
|
@@ -116,6 +123,7 @@ function buildDecisionsContent(
|
|
|
116
123
|
selected: Direction,
|
|
117
124
|
synthesis: SynthesisResult,
|
|
118
125
|
brief: StarterBrief,
|
|
126
|
+
visionStatement: string,
|
|
119
127
|
autoDecisions?: AutoDecision[],
|
|
120
128
|
): string {
|
|
121
129
|
// Build frozen decisions (need >= 8)
|
|
@@ -156,6 +164,9 @@ function buildDecisionsContent(
|
|
|
156
164
|
|
|
157
165
|
return `# Decisions
|
|
158
166
|
|
|
167
|
+
## Vision
|
|
168
|
+
${visionStatement}
|
|
169
|
+
|
|
159
170
|
## Frozen Decisions
|
|
160
171
|
${frozenDecisions.join('\n')}
|
|
161
172
|
|
|
@@ -223,6 +234,23 @@ function buildAgentArtifact(agentName: string, rawOutputs: Record<string, string
|
|
|
223
234
|
return content || fallback;
|
|
224
235
|
}
|
|
225
236
|
|
|
237
|
+
// ─── Vision Synthesis ───────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Synthesize an aspirational vision statement from brief and selected direction.
|
|
241
|
+
* Vision = future state the product enables, NOT product description.
|
|
242
|
+
* Example: "A world where solo founders never build the wrong thing because
|
|
243
|
+
* their specification process is as rigorous as their code."
|
|
244
|
+
*
|
|
245
|
+
* This is a template-based synthesis; in manual mode, the LLM in start.md
|
|
246
|
+
* will generate a richer vision and pass it via options.visionStatement.
|
|
247
|
+
*/
|
|
248
|
+
function synthesizeVisionStatement(brief: StarterBrief, selected: Direction): string {
|
|
249
|
+
const userContext = brief.target_user || 'users';
|
|
250
|
+
const problemContext = selected.problem_framing || brief.vision;
|
|
251
|
+
return `A world where ${userContext} achieve ${problemContext} effortlessly — because the right tools make clarity the default, not the exception.`;
|
|
252
|
+
}
|
|
253
|
+
|
|
226
254
|
// ─── Main Workflow ──────────────────────────────────────────────────────────
|
|
227
255
|
|
|
228
256
|
/**
|
|
@@ -282,6 +310,7 @@ export async function runImagine(options: ImagineOptions): Promise<ImagineResult
|
|
|
282
310
|
|
|
283
311
|
// ── Step 3: Spawn agents ──────────────────────────────────────────
|
|
284
312
|
let agentResults: AgentResult[];
|
|
313
|
+
const agentHeadlines: Array<{ agent: string; headline: string; status: 'complete' | 'failed' }> = [];
|
|
285
314
|
|
|
286
315
|
if (options.skipResearch) {
|
|
287
316
|
// Create minimal results from brief content
|
|
@@ -312,9 +341,15 @@ export async function runImagine(options: ImagineOptions): Promise<ImagineResult
|
|
|
312
341
|
config.max_parallel_agents,
|
|
313
342
|
brief,
|
|
314
343
|
spawnFn,
|
|
344
|
+
options.previousAgentOutputs,
|
|
345
|
+
options.userFeedback,
|
|
346
|
+
(result) => { agentHeadlines.push(result); },
|
|
315
347
|
);
|
|
316
348
|
}
|
|
317
349
|
|
|
350
|
+
// Build research summary (IMAGINE-01)
|
|
351
|
+
const researchSummary = buildResearchSummary(agentResults, brief);
|
|
352
|
+
|
|
318
353
|
// Mid-stage checkpoint: agents complete (RESM-02)
|
|
319
354
|
writeCheckpoint(statePath, 'gswd/imagine', 'agents-complete');
|
|
320
355
|
|
|
@@ -341,9 +376,19 @@ export async function runImagine(options: ImagineOptions): Promise<ImagineResult
|
|
|
341
376
|
// Mid-stage checkpoint: direction selected (RESM-02)
|
|
342
377
|
writeCheckpoint(statePath, 'gswd/imagine', 'direction-selected');
|
|
343
378
|
|
|
379
|
+
// ── Step 5.5: Synthesize vision statement (IMAGINE-05) ───────────
|
|
380
|
+
let visionStatement: string;
|
|
381
|
+
if (options.visionStatement) {
|
|
382
|
+
// User already approved/edited a vision statement (manual mode)
|
|
383
|
+
visionStatement = options.visionStatement;
|
|
384
|
+
} else {
|
|
385
|
+
// Auto-generate from brief + selected direction
|
|
386
|
+
visionStatement = synthesizeVisionStatement(brief, selected);
|
|
387
|
+
}
|
|
388
|
+
|
|
344
389
|
// ── Step 6: Build artifacts ──────────────────────────────────────
|
|
345
|
-
const imagineContent = buildImagineContent(brief, selected, synthesis, autoDecisions);
|
|
346
|
-
const decisionsContent = buildDecisionsContent(selected, synthesis, brief, autoDecisions);
|
|
390
|
+
const imagineContent = buildImagineContent(brief, selected, synthesis, visionStatement, autoDecisions);
|
|
391
|
+
const decisionsContent = buildDecisionsContent(selected, synthesis, brief, visionStatement, autoDecisions);
|
|
347
392
|
|
|
348
393
|
const icpContent = buildAgentArtifact(
|
|
349
394
|
'icp-persona',
|
|
@@ -378,6 +423,9 @@ export async function runImagine(options: ImagineOptions): Promise<ImagineResult
|
|
|
378
423
|
}
|
|
379
424
|
|
|
380
425
|
// ── Step 8: Write artifacts ──────────────────────────────────────
|
|
426
|
+
const directionsContent = synthesis.raw_agent_outputs['brainstorm-alternatives'] || '';
|
|
427
|
+
const risksRawContent = synthesis.raw_agent_outputs['devils-advocate'] || '';
|
|
428
|
+
|
|
381
429
|
const artifacts: { name: string; content: string }[] = [
|
|
382
430
|
{ name: 'IMAGINE.md', content: imagineContent },
|
|
383
431
|
{ name: 'ICP.md', content: icpContent },
|
|
@@ -386,6 +434,9 @@ export async function runImagine(options: ImagineOptions): Promise<ImagineResult
|
|
|
386
434
|
{ name: 'DECISIONS.md', content: decisionsContent },
|
|
387
435
|
];
|
|
388
436
|
|
|
437
|
+
if (directionsContent) artifacts.push({ name: 'DIRECTIONS.md', content: directionsContent });
|
|
438
|
+
if (risksRawContent) artifacts.push({ name: 'RISKS.md', content: risksRawContent });
|
|
439
|
+
|
|
389
440
|
const artifactsWritten: string[] = [];
|
|
390
441
|
|
|
391
442
|
for (const artifact of artifacts) {
|
|
@@ -419,6 +470,9 @@ export async function runImagine(options: ImagineOptions): Promise<ImagineResult
|
|
|
419
470
|
gate_result: gateResult,
|
|
420
471
|
auto_decisions: autoDecisions,
|
|
421
472
|
selected_direction: selected,
|
|
473
|
+
research_summary: researchSummary,
|
|
474
|
+
vision_statement: visionStatement,
|
|
475
|
+
agent_headlines: agentHeadlines.length > 0 ? agentHeadlines : undefined,
|
|
422
476
|
};
|
|
423
477
|
} catch (err: unknown) {
|
|
424
478
|
const message = err instanceof Error ? err.message : String(err);
|
package/lib/intake.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSWD Intake Module — INTAKE.json persistence for idempotent resume
|
|
3
|
+
*
|
|
4
|
+
* Writes/reads the user's confirmed product description to .planning/gswd/INTAKE.json.
|
|
5
|
+
* Uses atomic write pattern from state.ts (safeWriteJson).
|
|
6
|
+
*
|
|
7
|
+
* INTAKE.json is separate from intake.md:
|
|
8
|
+
* - intake.md: text file passed to bootstrap CLI (imagine agents)
|
|
9
|
+
* - INTAKE.json: machine-readable state for resume
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import { safeWriteJson } from './state.js';
|
|
15
|
+
|
|
16
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export interface IntakeData {
|
|
19
|
+
version: 1;
|
|
20
|
+
recorded_at: string;
|
|
21
|
+
product_description: string;
|
|
22
|
+
source: 'interactive' | 'file';
|
|
23
|
+
idea_file?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── CRUD ────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Write INTAKE.json atomically to the given GSWD directory.
|
|
30
|
+
* Persistence timing: called once after user confirms their product description
|
|
31
|
+
* (the "Did I get that right?" moment in start.md).
|
|
32
|
+
*/
|
|
33
|
+
export function writeIntake(
|
|
34
|
+
gswdDir: string,
|
|
35
|
+
data: Omit<IntakeData, 'version' | 'recorded_at'>
|
|
36
|
+
): IntakeData {
|
|
37
|
+
const intake: IntakeData = {
|
|
38
|
+
version: 1,
|
|
39
|
+
recorded_at: new Date().toISOString(),
|
|
40
|
+
...data,
|
|
41
|
+
};
|
|
42
|
+
const intakePath = path.join(gswdDir, 'INTAKE.json');
|
|
43
|
+
safeWriteJson(intakePath, intake);
|
|
44
|
+
return intake;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read INTAKE.json. Returns null if missing or corrupt.
|
|
49
|
+
*/
|
|
50
|
+
export function readIntake(gswdDir: string): IntakeData | null {
|
|
51
|
+
const intakePath = path.join(gswdDir, 'INTAKE.json');
|
|
52
|
+
try {
|
|
53
|
+
const content = fs.readFileSync(intakePath, 'utf-8');
|
|
54
|
+
const parsed = JSON.parse(content);
|
|
55
|
+
if (typeof parsed.product_description !== 'string') return null;
|
|
56
|
+
return parsed as IntakeData;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/lib/parse.ts
CHANGED
|
@@ -20,6 +20,7 @@ export const REQUIRED_HEADINGS: Record<string, string[]> = {
|
|
|
20
20
|
'INTEGRATIONS.md': ['## Integrations'],
|
|
21
21
|
'ARCHITECTURE.md': ['## Architecture', '### Components', '### Data Model'],
|
|
22
22
|
'DECISIONS.md': [
|
|
23
|
+
'## Vision',
|
|
23
24
|
'## Frozen Decisions',
|
|
24
25
|
'## Success Metrics',
|
|
25
26
|
'## Out of Scope',
|
|
@@ -27,7 +28,7 @@ export const REQUIRED_HEADINGS: Record<string, string[]> = {
|
|
|
27
28
|
'## Open Questions',
|
|
28
29
|
],
|
|
29
30
|
'AUDIT.md': ['## Coverage Matrix', '## Check Results'],
|
|
30
|
-
'PROJECT.md': ['## What This Is', '## Target User', '## Problem Statement', '## Wedge / MVP Boundary', '## Success Metrics', '## Out of Scope'],
|
|
31
|
+
'PROJECT.md': ['## What This Is', '## Target User', '## Problem Statement', '## Wedge / MVP Boundary', '## Success Metrics', '## Out of Scope', '## Context'],
|
|
31
32
|
'REQUIREMENTS.md': ['## Functional Requirements', '## Non-Functional Requirements', '## Traceability'],
|
|
32
33
|
'ROADMAP.md': ['## Overview', '## Phases'],
|
|
33
34
|
'STATE.md': ['## Frozen Decisions', '## Approvals', '## Open Questions', '## Risks'],
|