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,8 @@
1
+ /**
2
+ * Tests for GSWD Imagine Workflow Orchestrator
3
+ *
4
+ * Integration-level tests using mock spawnFn and temp directories.
5
+ * Covers: full workflow, gate enforcement, auto mode, error handling.
6
+ * Minimum 10 test cases per Plan 02-05.
7
+ */
8
+ export {};
@@ -0,0 +1,406 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for GSWD Imagine Workflow Orchestrator
4
+ *
5
+ * Integration-level tests using mock spawnFn and temp directories.
6
+ * Covers: full workflow, gate enforcement, auto mode, error handling.
7
+ * Minimum 10 test cases per Plan 02-05.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ const node_test_1 = require("node:test");
44
+ const assert = __importStar(require("node:assert"));
45
+ const fs = __importStar(require("node:fs"));
46
+ const path = __importStar(require("node:path"));
47
+ const os = __importStar(require("node:os"));
48
+ const imagine_js_1 = require("../lib/imagine.js");
49
+ const state_js_1 = require("../lib/state.js");
50
+ const config_js_1 = require("../lib/config.js");
51
+ // ─── Test Fixtures ───────────────────────────────────────────────────────────
52
+ /** Realistic mock agent outputs for a complete workflow */
53
+ const MOCK_AGENT_OUTPUTS = {
54
+ 'market-researcher': `## Market Overview
55
+ The developer tools market is expanding rapidly. Specification tools remain a gap.
56
+
57
+ ## Competitors
58
+ - Notion AI: General-purpose doc editing
59
+ - Linear: Project management
60
+ - Coda AI: Document automation
61
+
62
+ ## Market Gaps
63
+ No tool converts ideas into execution-grade specs automatically.
64
+
65
+ ## Opportunities
66
+ Open source community adoption through GitHub and Discord channels.`,
67
+ 'icp-persona': `## ICP Profile
68
+ Solo SaaS founders who code but skip specifications. Technical enough for CLI tools but struggle with product thinking.
69
+
70
+ ## Pain Points
71
+ - Pain of manual spec writing: tedious, slow, frustrating
72
+ - Waste time building wrong features
73
+ - Struggle to think through edge cases
74
+
75
+ ## Willingness to Pay
76
+ Early-stage founders have limited budget but will pay for tools that save time. Price point $29-49/mo for premium subscription.
77
+
78
+ ## Reachability
79
+ Active on Indie Hackers, Twitter, Hacker News. Attend virtual meetups and subscribe to community newsletters.`,
80
+ 'positioning': `## Value Proposition
81
+ For solo founders who struggle with specifications, GSWD is a CLI tool that turns fuzzy ideas into execution-grade specs.
82
+
83
+ ## Positioning Statement
84
+ Category: Developer productivity
85
+ Differentiation: AI-powered spec generation vs manual templates
86
+
87
+ ## Key Differentiators
88
+ - Automated research agents vs manual brainstorming
89
+ - Deterministic compilation to project plans
90
+
91
+ ## Go-to-Market Angle
92
+ Channel: Indie Hackers, open source GitHub community
93
+ Wedge: Free CLI that generates DECISIONS.md from 3-question intake`,
94
+ 'brainstorm-alternatives': `## Direction 1: CLI Spec Generator
95
+ **ICP:** Solo SaaS founders who code but don't spec
96
+ **Problem:** Founders skip specs, leading to painful rework and wasted build cycles
97
+ **Wedge:** 3-question CLI intake that produces DECISIONS.md in 5 minutes
98
+ **Differentiator:** AI agents do the research a PM would do
99
+ **MVP scope:** Imagine stage only (intake to decisions)
100
+ **Risk:** Founders may not trust AI-generated specs
101
+
102
+ ## Direction 2: Team Alignment Tool
103
+ **ICP:** Small engineering teams (2-5) at early startups who pay for premium tools
104
+ **Problem:** Misalignment between founder vision and dev execution is expensive and frustrating
105
+ **Wedge:** Shared spec generated from async inputs
106
+ **Differentiator:** Bridges business thinking and technical execution
107
+ **MVP scope:** Collaborative editing with conflict detection
108
+ **Risk:** Multi-user features are slow and complex to build
109
+
110
+ ## Direction 3: Idea Validator
111
+ **ICP:** Pre-revenue founders exploring ideas via community forums and Reddit
112
+ **Problem:** Founders invest months building products nobody wants — a tedious manual waste
113
+ **Wedge:** Instant market viability score from one-paragraph idea
114
+ **Differentiator:** Transparent scoring algorithm, not a black box
115
+ **MVP scope:** Input to score to risks to go/no-go
116
+ **Risk:** Scoring without real data feels unreliable`,
117
+ 'devils-advocate': `## Assumptions Challenged
118
+ - Assumption: Founders want CLI tools — Plausible but niche
119
+
120
+ ## Risks
121
+ - **Risk:** AI specs may be too generic
122
+ - **Likelihood:** Medium
123
+ - **Impact:** High
124
+ - **Risk:** Solo founders may not pay — expensive alternatives exist
125
+ - **Likelihood:** High
126
+ - **Impact:** High
127
+ - **Risk:** LLM quality degrades over time
128
+ - **Likelihood:** Low
129
+ - **Impact:** Medium
130
+ - **Risk:** Manual spec habit is hard to break — struggle to change
131
+ - **Likelihood:** Medium
132
+ - **Impact:** Medium
133
+ - **Risk:** Competitors add spec features to existing tools
134
+ - **Likelihood:** Medium
135
+ - **Impact:** High
136
+
137
+ ## Mitigations
138
+ - Validate with 10 user interviews before premium features
139
+ - Open source core to reduce friction via community channels
140
+
141
+ ## Red Flags
142
+ - If no one completes intake in beta, rethink UX`,
143
+ };
144
+ /** Mock spawn function returning realistic agent outputs */
145
+ function createMockSpawnFn() {
146
+ return async (prompt) => {
147
+ for (const [agentName, output] of Object.entries(MOCK_AGENT_OUTPUTS)) {
148
+ if (prompt.includes(`name: ${agentName}`) || prompt.includes(agentName)) {
149
+ return output;
150
+ }
151
+ }
152
+ return '## Output\nGeneric agent output.';
153
+ };
154
+ }
155
+ /** Create a temporary test directory with initialized state + config */
156
+ function createTestEnv() {
157
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-imagine-test-'));
158
+ const planningDir = tmpDir;
159
+ const gswdDir = path.join(planningDir, 'gswd');
160
+ fs.mkdirSync(gswdDir, { recursive: true });
161
+ // Initialize STATE.json
162
+ const state = (0, state_js_1.createDefaultState)('test-project');
163
+ const statePath = path.join(gswdDir, 'STATE.json');
164
+ (0, state_js_1.safeWriteJson)(statePath, state);
165
+ // Initialize config.json
166
+ const configPath = path.join(planningDir, 'config.json');
167
+ (0, config_js_1.mergeGswdConfig)(configPath);
168
+ return {
169
+ planningDir,
170
+ configPath,
171
+ cleanup: () => {
172
+ try {
173
+ fs.rmSync(tmpDir, { recursive: true, force: true });
174
+ }
175
+ catch {
176
+ // ignore cleanup errors
177
+ }
178
+ },
179
+ };
180
+ }
181
+ /** Create a test idea file in the given directory */
182
+ function createIdeaFile(dir) {
183
+ const ideaPath = path.join(dir, 'idea.md');
184
+ const content = `# GSWD — Get Shit Well Done
185
+
186
+ A CLI tool that turns unstructured founder ideas into execution-grade specifications.
187
+
188
+ ## Target Audience
189
+ Built for solo founders building SaaS products who are technical enough to use terminal tools but struggle with product thinking and specification writing.
190
+
191
+ ## Why Now
192
+ LLMs can now understand product requirements deeply. The gap between idea and execution is the biggest problem founders face — there's a growing frustration with manual spec writing.
193
+
194
+ ## Key Themes
195
+ - CLI-first developer experience
196
+ - Automated research and brainstorming
197
+ - Deterministic spec compilation
198
+ - Zero-dependency Node.js tooling
199
+ `;
200
+ fs.writeFileSync(ideaPath, content, 'utf-8');
201
+ return ideaPath;
202
+ }
203
+ // ─── Tests ───────────────────────────────────────────────────────────────────
204
+ (0, node_test_1.describe)('Imagine workflow with mock agents', () => {
205
+ let env;
206
+ (0, node_test_1.beforeEach)(() => {
207
+ env = createTestEnv();
208
+ });
209
+ (0, node_test_1.afterEach)(() => {
210
+ env.cleanup();
211
+ });
212
+ (0, node_test_1.it)('runImagine with ideaFilePath and mock spawnFn produces all 5 artifacts', async () => {
213
+ const ideaPath = createIdeaFile(env.planningDir);
214
+ const result = await (0, imagine_js_1.runImagine)({
215
+ ideaFilePath: ideaPath,
216
+ autoMode: true,
217
+ planningDir: env.planningDir,
218
+ configPath: env.configPath,
219
+ spawnFn: createMockSpawnFn(),
220
+ });
221
+ assert.strictEqual(result.status, 'complete', `Expected complete but got: ${result.error || result.gate_result?.summary}`);
222
+ assert.strictEqual(result.artifacts_written.length, 5, 'Should write 5 artifacts');
223
+ });
224
+ (0, node_test_1.it)('runImagine with intakeAnswers and mock spawnFn produces all 5 artifacts', async () => {
225
+ const result = await (0, imagine_js_1.runImagine)({
226
+ intakeAnswers: {
227
+ vision: 'A CLI tool that turns ideas into execution-grade specifications',
228
+ user: 'Solo SaaS founders who code but skip product specs',
229
+ whyNow: 'LLMs now understand product requirements — growing frustration with manual specs',
230
+ },
231
+ autoMode: true,
232
+ planningDir: env.planningDir,
233
+ configPath: env.configPath,
234
+ spawnFn: createMockSpawnFn(),
235
+ });
236
+ assert.strictEqual(result.status, 'complete', `Expected complete but got: ${result.error || result.gate_result?.summary}`);
237
+ assert.strictEqual(result.artifacts_written.length, 5);
238
+ });
239
+ (0, node_test_1.it)('artifacts exist on disk after successful run', async () => {
240
+ const ideaPath = createIdeaFile(env.planningDir);
241
+ const result = await (0, imagine_js_1.runImagine)({
242
+ ideaFilePath: ideaPath,
243
+ autoMode: true,
244
+ planningDir: env.planningDir,
245
+ configPath: env.configPath,
246
+ spawnFn: createMockSpawnFn(),
247
+ });
248
+ assert.strictEqual(result.status, 'complete');
249
+ const expectedFiles = ['IMAGINE.md', 'ICP.md', 'GTM.md', 'COMPETITION.md', 'DECISIONS.md'];
250
+ for (const file of expectedFiles) {
251
+ const filePath = path.join(env.planningDir, file);
252
+ assert.ok(fs.existsSync(filePath), `${file} should exist on disk`);
253
+ const content = fs.readFileSync(filePath, 'utf-8');
254
+ assert.ok(content.length > 0, `${file} should have content`);
255
+ }
256
+ });
257
+ (0, node_test_1.it)('STATE.json shows imagine: done after successful run', async () => {
258
+ const ideaPath = createIdeaFile(env.planningDir);
259
+ await (0, imagine_js_1.runImagine)({
260
+ ideaFilePath: ideaPath,
261
+ autoMode: true,
262
+ planningDir: env.planningDir,
263
+ configPath: env.configPath,
264
+ spawnFn: createMockSpawnFn(),
265
+ });
266
+ const statePath = path.join(env.planningDir, 'gswd', 'STATE.json');
267
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
268
+ assert.strictEqual(state.stage_status.imagine, 'done');
269
+ });
270
+ });
271
+ (0, node_test_1.describe)('Decision gate enforcement', () => {
272
+ let env;
273
+ (0, node_test_1.beforeEach)(() => {
274
+ env = createTestEnv();
275
+ });
276
+ (0, node_test_1.afterEach)(() => {
277
+ env.cleanup();
278
+ });
279
+ (0, node_test_1.it)('skipResearch with minimal brief still produces gate-passing output', async () => {
280
+ const ideaPath = createIdeaFile(env.planningDir);
281
+ const result = await (0, imagine_js_1.runImagine)({
282
+ ideaFilePath: ideaPath,
283
+ autoMode: true,
284
+ skipResearch: true,
285
+ planningDir: env.planningDir,
286
+ configPath: env.configPath,
287
+ });
288
+ // skipResearch produces minimal agent data — the orchestrator should still
289
+ // build valid DECISIONS.md with enough frozen decisions to pass the gate
290
+ assert.strictEqual(result.status, 'complete', `Expected complete but got: ${result.error || result.gate_result?.summary}`);
291
+ });
292
+ (0, node_test_1.it)('gate result details are included in ImagineResult', async () => {
293
+ const ideaPath = createIdeaFile(env.planningDir);
294
+ const result = await (0, imagine_js_1.runImagine)({
295
+ ideaFilePath: ideaPath,
296
+ autoMode: true,
297
+ planningDir: env.planningDir,
298
+ configPath: env.configPath,
299
+ spawnFn: createMockSpawnFn(),
300
+ });
301
+ assert.ok(result.gate_result, 'Should include gate result');
302
+ assert.ok(typeof result.gate_result.passed === 'boolean', 'Gate result should have passed field');
303
+ assert.ok(result.gate_result.summary, 'Gate result should have summary');
304
+ });
305
+ });
306
+ (0, node_test_1.describe)('Auto mode', () => {
307
+ let env;
308
+ (0, node_test_1.beforeEach)(() => {
309
+ env = createTestEnv();
310
+ });
311
+ (0, node_test_1.afterEach)(() => {
312
+ env.cleanup();
313
+ });
314
+ (0, node_test_1.it)('runImagine with autoMode=true produces AutoDecision[] in result', async () => {
315
+ const ideaPath = createIdeaFile(env.planningDir);
316
+ const result = await (0, imagine_js_1.runImagine)({
317
+ ideaFilePath: ideaPath,
318
+ autoMode: true,
319
+ planningDir: env.planningDir,
320
+ configPath: env.configPath,
321
+ spawnFn: createMockSpawnFn(),
322
+ });
323
+ assert.strictEqual(result.status, 'complete');
324
+ assert.ok(result.auto_decisions, 'Should have auto_decisions');
325
+ assert.ok(result.auto_decisions.length >= 3, 'Should have at least 3 auto decisions');
326
+ const types = result.auto_decisions.map(d => d.type);
327
+ assert.ok(types.includes('direction'), 'Should have direction decision');
328
+ assert.ok(types.includes('icp'), 'Should have ICP decision');
329
+ assert.ok(types.includes('wedge'), 'Should have wedge decision');
330
+ });
331
+ (0, node_test_1.it)('STATE.json auto.decisions is populated after auto run', async () => {
332
+ const ideaPath = createIdeaFile(env.planningDir);
333
+ await (0, imagine_js_1.runImagine)({
334
+ ideaFilePath: ideaPath,
335
+ autoMode: true,
336
+ planningDir: env.planningDir,
337
+ configPath: env.configPath,
338
+ spawnFn: createMockSpawnFn(),
339
+ });
340
+ const statePath = path.join(env.planningDir, 'gswd', 'STATE.json');
341
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
342
+ assert.ok(state.auto.decisions, 'State should have auto.decisions');
343
+ assert.ok(Array.isArray(state.auto.decisions), 'auto.decisions should be an array');
344
+ assert.ok(state.auto.decisions.length >= 3, 'Should have at least 3 decisions');
345
+ });
346
+ (0, node_test_1.it)('DECISIONS.md contains Auto-chosen markers in auto mode', async () => {
347
+ const ideaPath = createIdeaFile(env.planningDir);
348
+ await (0, imagine_js_1.runImagine)({
349
+ ideaFilePath: ideaPath,
350
+ autoMode: true,
351
+ planningDir: env.planningDir,
352
+ configPath: env.configPath,
353
+ spawnFn: createMockSpawnFn(),
354
+ });
355
+ const decisionsPath = path.join(env.planningDir, 'DECISIONS.md');
356
+ const content = fs.readFileSync(decisionsPath, 'utf-8');
357
+ assert.ok(content.includes('Auto-chosen'), 'DECISIONS.md should contain Auto-chosen markers');
358
+ });
359
+ });
360
+ (0, node_test_1.describe)('Error handling', () => {
361
+ let env;
362
+ (0, node_test_1.beforeEach)(() => {
363
+ env = createTestEnv();
364
+ });
365
+ (0, node_test_1.afterEach)(() => {
366
+ env.cleanup();
367
+ });
368
+ (0, node_test_1.it)('no input (no file, no answers) returns error result', async () => {
369
+ const result = await (0, imagine_js_1.runImagine)({
370
+ planningDir: env.planningDir,
371
+ configPath: env.configPath,
372
+ });
373
+ assert.strictEqual(result.status, 'error');
374
+ assert.ok(result.error, 'Should have error message');
375
+ assert.ok(result.error.includes('No input'), 'Error should mention missing input');
376
+ });
377
+ (0, node_test_1.it)('non-existent idea file returns error result', async () => {
378
+ const result = await (0, imagine_js_1.runImagine)({
379
+ ideaFilePath: '/nonexistent/idea.md',
380
+ planningDir: env.planningDir,
381
+ configPath: env.configPath,
382
+ });
383
+ assert.strictEqual(result.status, 'error');
384
+ assert.ok(result.error, 'Should have error message');
385
+ });
386
+ (0, node_test_1.it)('missing STATE.json returns error result', async () => {
387
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-imagine-nostate-'));
388
+ const gswdDir = path.join(tmpDir, 'gswd');
389
+ fs.mkdirSync(gswdDir, { recursive: true });
390
+ const result = await (0, imagine_js_1.runImagine)({
391
+ intakeAnswers: {
392
+ vision: 'A test product for testing purposes',
393
+ user: 'Test users who test things',
394
+ whyNow: 'Testing is important right now',
395
+ },
396
+ planningDir: tmpDir,
397
+ configPath: path.join(tmpDir, 'config.json'),
398
+ });
399
+ assert.strictEqual(result.status, 'error');
400
+ assert.ok(result.error?.includes('STATE.json'), 'Error should mention STATE.json');
401
+ try {
402
+ fs.rmSync(tmpDir, { recursive: true, force: true });
403
+ }
404
+ catch { /* ignore */ }
405
+ });
406
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Parse module tests — ID extraction, normalization, heading validation
3
+ */
4
+ export {};