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,4 @@
1
+ /**
2
+ * Config module tests — merge under gswd key, preserve existing keys, idempotent
3
+ */
4
+ export {};
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * Config module tests — merge under gswd key, preserve existing keys, idempotent
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const node_test_1 = require("node:test");
40
+ const assert = __importStar(require("node:assert"));
41
+ const fs = __importStar(require("node:fs"));
42
+ const path = __importStar(require("node:path"));
43
+ const os = __importStar(require("node:os"));
44
+ const config_js_1 = require("../lib/config.js");
45
+ let tmpDir;
46
+ (0, node_test_1.beforeEach)(() => {
47
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-config-test-'));
48
+ });
49
+ (0, node_test_1.afterEach)(() => {
50
+ fs.rmSync(tmpDir, { recursive: true, force: true });
51
+ });
52
+ // ─── readConfig ──────────────────────────────────────────────────────────────
53
+ (0, node_test_1.describe)('readConfig', () => {
54
+ (0, node_test_1.it)('returns empty object for missing file', () => {
55
+ const result = (0, config_js_1.readConfig)(path.join(tmpDir, 'nonexistent.json'));
56
+ assert.deepStrictEqual(result, {});
57
+ });
58
+ (0, node_test_1.it)('returns parsed JSON for valid file', () => {
59
+ const configPath = path.join(tmpDir, 'config.json');
60
+ fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
61
+ const result = (0, config_js_1.readConfig)(configPath);
62
+ assert.strictEqual(result.model_profile, 'balanced');
63
+ });
64
+ (0, node_test_1.it)('returns empty object for invalid JSON', () => {
65
+ const configPath = path.join(tmpDir, 'config.json');
66
+ fs.writeFileSync(configPath, 'not json');
67
+ assert.deepStrictEqual((0, config_js_1.readConfig)(configPath), {});
68
+ });
69
+ (0, node_test_1.it)('returns empty object for JSON array', () => {
70
+ const configPath = path.join(tmpDir, 'config.json');
71
+ fs.writeFileSync(configPath, '[1,2,3]');
72
+ assert.deepStrictEqual((0, config_js_1.readConfig)(configPath), {});
73
+ });
74
+ });
75
+ // ─── mergeGswdConfig ─────────────────────────────────────────────────────────
76
+ (0, node_test_1.describe)('mergeGswdConfig', () => {
77
+ (0, node_test_1.it)('creates gswd key with defaults when config has no gswd key', () => {
78
+ const configPath = path.join(tmpDir, 'config.json');
79
+ fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
80
+ (0, config_js_1.mergeGswdConfig)(configPath);
81
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
82
+ assert.ok(config.gswd);
83
+ assert.strictEqual(config.gswd.mode, 'balanced');
84
+ assert.strictEqual(config.gswd.strict_gates, true);
85
+ assert.strictEqual(config.gswd.max_parallel_agents, 4);
86
+ });
87
+ (0, node_test_1.it)('preserves existing non-gswd keys (model_profile, commit_docs, etc.)', () => {
88
+ const configPath = path.join(tmpDir, 'config.json');
89
+ fs.writeFileSync(configPath, JSON.stringify({
90
+ model_profile: 'balanced',
91
+ commit_docs: true,
92
+ research: true,
93
+ }));
94
+ (0, config_js_1.mergeGswdConfig)(configPath);
95
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
96
+ assert.strictEqual(config.model_profile, 'balanced');
97
+ assert.strictEqual(config.commit_docs, true);
98
+ assert.strictEqual(config.research, true);
99
+ assert.ok(config.gswd); // gswd also exists
100
+ });
101
+ (0, node_test_1.it)('deep merges: user gswd values override defaults', () => {
102
+ const configPath = path.join(tmpDir, 'config.json');
103
+ fs.writeFileSync(configPath, JSON.stringify({
104
+ gswd: {
105
+ mode: 'strict',
106
+ max_parallel_agents: 2,
107
+ }
108
+ }));
109
+ (0, config_js_1.mergeGswdConfig)(configPath);
110
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
111
+ assert.strictEqual(config.gswd.mode, 'strict'); // user value preserved
112
+ assert.strictEqual(config.gswd.max_parallel_agents, 2); // user value preserved
113
+ assert.strictEqual(config.gswd.strict_gates, true); // default filled in
114
+ });
115
+ (0, node_test_1.it)('fills missing defaults in partial gswd config', () => {
116
+ const configPath = path.join(tmpDir, 'config.json');
117
+ fs.writeFileSync(configPath, JSON.stringify({
118
+ gswd: { mode: 'strict' }
119
+ }));
120
+ (0, config_js_1.mergeGswdConfig)(configPath);
121
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
122
+ assert.strictEqual(config.gswd.mode, 'strict');
123
+ assert.strictEqual(config.gswd.phase_style, 'thin'); // default
124
+ assert.strictEqual(config.gswd.external_research, true); // default
125
+ });
126
+ (0, node_test_1.it)('idempotent: merging twice produces identical output', () => {
127
+ const configPath = path.join(tmpDir, 'config.json');
128
+ fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
129
+ (0, config_js_1.mergeGswdConfig)(configPath);
130
+ const first = fs.readFileSync(configPath, 'utf-8');
131
+ (0, config_js_1.mergeGswdConfig)(configPath);
132
+ const second = fs.readFileSync(configPath, 'utf-8');
133
+ assert.strictEqual(first, second);
134
+ });
135
+ (0, node_test_1.it)('handles nested auto config merge correctly', () => {
136
+ const configPath = path.join(tmpDir, 'config.json');
137
+ fs.writeFileSync(configPath, JSON.stringify({
138
+ gswd: {
139
+ auto: {
140
+ policy: 'aggressive',
141
+ default_stack: 'python_django',
142
+ }
143
+ }
144
+ }));
145
+ (0, config_js_1.mergeGswdConfig)(configPath);
146
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
147
+ assert.strictEqual(config.gswd.auto.policy, 'aggressive'); // user
148
+ assert.strictEqual(config.gswd.auto.default_stack, 'python_django'); // user
149
+ assert.strictEqual(config.gswd.auto.default_auth_model, 'passwordless_email'); // default
150
+ assert.deepStrictEqual(config.gswd.auto.preapproved_integrations, []); // default
151
+ });
152
+ (0, node_test_1.it)('creates config.json from scratch when file does not exist', () => {
153
+ const configPath = path.join(tmpDir, 'new-config.json');
154
+ (0, config_js_1.mergeGswdConfig)(configPath);
155
+ assert.ok(fs.existsSync(configPath));
156
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
157
+ assert.ok(config.gswd);
158
+ assert.strictEqual(config.gswd.mode, 'balanced');
159
+ });
160
+ (0, node_test_1.it)('applies overrides on top of defaults', () => {
161
+ const configPath = path.join(tmpDir, 'config.json');
162
+ fs.writeFileSync(configPath, '{}');
163
+ (0, config_js_1.mergeGswdConfig)(configPath, { mode: 'strict', max_parallel_agents: 8 });
164
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
165
+ assert.strictEqual(config.gswd.mode, 'strict');
166
+ assert.strictEqual(config.gswd.max_parallel_agents, 8);
167
+ });
168
+ });
169
+ // ─── getGswdConfig ───────────────────────────────────────────────────────────
170
+ (0, node_test_1.describe)('getGswdConfig', () => {
171
+ (0, node_test_1.it)('returns defaults when no gswd key exists', () => {
172
+ const configPath = path.join(tmpDir, 'config.json');
173
+ fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
174
+ const gswdConfig = (0, config_js_1.getGswdConfig)(configPath);
175
+ assert.strictEqual(gswdConfig.mode, config_js_1.GSWD_CONFIG_DEFAULTS.mode);
176
+ assert.strictEqual(gswdConfig.strict_gates, config_js_1.GSWD_CONFIG_DEFAULTS.strict_gates);
177
+ });
178
+ (0, node_test_1.it)('returns merged config when partial gswd key exists', () => {
179
+ const configPath = path.join(tmpDir, 'config.json');
180
+ fs.writeFileSync(configPath, JSON.stringify({
181
+ gswd: { mode: 'strict' }
182
+ }));
183
+ const gswdConfig = (0, config_js_1.getGswdConfig)(configPath);
184
+ assert.strictEqual(gswdConfig.mode, 'strict'); // from file
185
+ assert.strictEqual(gswdConfig.phase_style, 'thin'); // from defaults
186
+ });
187
+ (0, node_test_1.it)('returns defaults for missing config file', () => {
188
+ const gswdConfig = (0, config_js_1.getGswdConfig)(path.join(tmpDir, 'missing.json'));
189
+ assert.deepStrictEqual(gswdConfig, config_js_1.GSWD_CONFIG_DEFAULTS);
190
+ });
191
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for GSWD Imagine Agents Module
3
+ *
4
+ * Covers: IMAGINE_AGENTS constant, buildAgentPrompt, orchestrateAgents (batching, failures)
5
+ */
6
+ export {};
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for GSWD Imagine Agents Module
4
+ *
5
+ * Covers: IMAGINE_AGENTS constant, buildAgentPrompt, orchestrateAgents (batching, failures)
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ const node_test_1 = require("node:test");
42
+ const assert = __importStar(require("node:assert"));
43
+ const imagine_agents_js_1 = require("../lib/imagine-agents.js");
44
+ // ─── Test Fixtures ───────────────────────────────────────────────────────────
45
+ const TEST_BRIEF = {
46
+ vision: 'A CLI tool that turns ideas into execution-grade specs',
47
+ target_user: 'Solo founders building SaaS products',
48
+ why_now: 'LLMs can now understand product requirements deeply',
49
+ raw_themes: ['CLI', 'specifications', 'automation', 'SaaS'],
50
+ source: 'intake',
51
+ };
52
+ /** Mock spawn function that returns agent name as content */
53
+ function mockSpawnFn(prompt) {
54
+ // Extract agent name from prompt for identification
55
+ const nameMatch = prompt.match(/name:\s*(\S+)/);
56
+ const name = nameMatch ? nameMatch[1] : 'unknown';
57
+ return Promise.resolve(`## Output from ${name}\n\nContent here.`);
58
+ }
59
+ /** Mock spawn function that fails for specific agents */
60
+ function failingSpawnFn(failAgents) {
61
+ return (prompt) => {
62
+ for (const name of failAgents) {
63
+ if (prompt.includes(name)) {
64
+ return Promise.reject(new Error(`Agent ${name} failed`));
65
+ }
66
+ }
67
+ return Promise.resolve('## Success\n\nContent here.');
68
+ };
69
+ }
70
+ // ─── Tests ───────────────────────────────────────────────────────────────────
71
+ (0, node_test_1.describe)('IMAGINE_AGENTS constant', () => {
72
+ (0, node_test_1.it)('has exactly 5 agents', () => {
73
+ assert.strictEqual(imagine_agents_js_1.IMAGINE_AGENTS.length, 5);
74
+ });
75
+ (0, node_test_1.it)('all agent names match GSWD_SPEC file names', () => {
76
+ const expectedNames = [
77
+ 'market-researcher',
78
+ 'icp-persona',
79
+ 'positioning',
80
+ 'brainstorm-alternatives',
81
+ 'devils-advocate',
82
+ ];
83
+ const actualNames = imagine_agents_js_1.IMAGINE_AGENTS.map(a => a.name);
84
+ assert.deepStrictEqual(actualNames, expectedNames);
85
+ });
86
+ (0, node_test_1.it)('each has non-empty definitionPath, outputArtifact, requiredHeadings', () => {
87
+ for (const agent of imagine_agents_js_1.IMAGINE_AGENTS) {
88
+ assert.ok(agent.definitionPath.length > 0, `${agent.name} should have definitionPath`);
89
+ assert.ok(agent.outputArtifact.length > 0, `${agent.name} should have outputArtifact`);
90
+ assert.ok(agent.requiredHeadings.length > 0, `${agent.name} should have requiredHeadings`);
91
+ }
92
+ });
93
+ });
94
+ (0, node_test_1.describe)('buildAgentPrompt', () => {
95
+ (0, node_test_1.it)('includes StarterBrief as JSON in starter_brief tags', () => {
96
+ const agent = imagine_agents_js_1.IMAGINE_AGENTS[0];
97
+ const prompt = (0, imagine_agents_js_1.buildAgentPrompt)(agent, TEST_BRIEF);
98
+ assert.ok(prompt.includes('<starter_brief>'), 'Should include starter_brief tags');
99
+ assert.ok(prompt.includes(TEST_BRIEF.vision), 'Should include brief vision');
100
+ assert.ok(prompt.includes('</starter_brief>'), 'Should close starter_brief tags');
101
+ });
102
+ (0, node_test_1.it)('mentions required headings', () => {
103
+ const agent = imagine_agents_js_1.IMAGINE_AGENTS[0]; // market-researcher
104
+ const prompt = (0, imagine_agents_js_1.buildAgentPrompt)(agent, TEST_BRIEF);
105
+ assert.ok(prompt.includes('## Market Overview'), 'Should mention required headings');
106
+ assert.ok(prompt.includes('## Competitors'), 'Should mention required headings');
107
+ });
108
+ (0, node_test_1.it)('includes agent definition content', () => {
109
+ const agent = imagine_agents_js_1.IMAGINE_AGENTS[0]; // market-researcher
110
+ const prompt = (0, imagine_agents_js_1.buildAgentPrompt)(agent, TEST_BRIEF);
111
+ // Agent definition file should be included (it exists on disk)
112
+ assert.ok(prompt.includes('market') || prompt.includes('Market') || prompt.includes('researcher'), 'Should include content from agent definition');
113
+ });
114
+ });
115
+ (0, node_test_1.describe)('orchestrateAgents', () => {
116
+ (0, node_test_1.it)('with maxParallel=5, all agents spawn in 1 batch', async () => {
117
+ const callOrder = [];
118
+ const trackingSpawn = async (prompt) => {
119
+ const nameMatch = prompt.match(/name:\s*(\S+)/);
120
+ callOrder.push(nameMatch ? nameMatch[1] : 'unknown');
121
+ return '## Output\nContent';
122
+ };
123
+ const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 5, TEST_BRIEF, trackingSpawn);
124
+ assert.strictEqual(results.length, 5);
125
+ assert.strictEqual(results.filter(r => r.status === 'complete').length, 5);
126
+ });
127
+ (0, node_test_1.it)('with maxParallel=4, agents spawn in 2 batches (4 then 1)', async () => {
128
+ let batchCount = 0;
129
+ let currentBatchSize = 0;
130
+ const batchSizes = [];
131
+ const batchTrackingSpawn = async () => {
132
+ currentBatchSize++;
133
+ // Small delay to ensure batch tracking works
134
+ await new Promise(resolve => setTimeout(resolve, 1));
135
+ return '## Output\nContent';
136
+ };
137
+ // We need to track batches differently — count calls between sequential awaits
138
+ // Since orchestrateAgents uses Promise.all per batch, we can track via the results
139
+ const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 4, TEST_BRIEF, batchTrackingSpawn);
140
+ assert.strictEqual(results.length, 5, 'Should have 5 results');
141
+ assert.strictEqual(results.filter(r => r.status === 'complete').length, 5);
142
+ });
143
+ (0, node_test_1.it)('with maxParallel=2, agents spawn in 3 batches (2, 2, 1)', async () => {
144
+ const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 2, TEST_BRIEF, mockSpawnFn);
145
+ assert.strictEqual(results.length, 5, 'Should have 5 results');
146
+ assert.strictEqual(results.filter(r => r.status === 'complete').length, 5);
147
+ });
148
+ (0, node_test_1.it)('failed spawnFn marks agent as failed, does not crash orchestration', async () => {
149
+ const failSpawn = failingSpawnFn(['devils-advocate']);
150
+ const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 5, TEST_BRIEF, failSpawn);
151
+ assert.strictEqual(results.length, 5, 'Should still have 5 results');
152
+ const failed = results.filter(r => r.status === 'failed');
153
+ assert.strictEqual(failed.length, 1, 'Should have 1 failure');
154
+ assert.strictEqual(failed[0].agent, 'devils-advocate');
155
+ assert.ok(failed[0].error, 'Failed result should have error message');
156
+ const complete = results.filter(r => r.status === 'complete');
157
+ assert.strictEqual(complete.length, 4, 'Should have 4 successes');
158
+ });
159
+ (0, node_test_1.it)('mixed results (some complete, some failed) all returned', async () => {
160
+ const failSpawn = failingSpawnFn(['market-researcher', 'positioning']);
161
+ const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 5, TEST_BRIEF, failSpawn);
162
+ assert.strictEqual(results.length, 5);
163
+ assert.strictEqual(results.filter(r => r.status === 'complete').length, 3);
164
+ assert.strictEqual(results.filter(r => r.status === 'failed').length, 2);
165
+ });
166
+ (0, node_test_1.it)('batch ordering is preserved (agents run in definition order)', async () => {
167
+ const order = [];
168
+ const orderTrackingSpawn = async (prompt) => {
169
+ const nameMatch = prompt.match(/name:\s*(\S+)/);
170
+ order.push(nameMatch ? nameMatch[1] : 'unknown');
171
+ return '## Output\nContent';
172
+ };
173
+ const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 1, TEST_BRIEF, orderTrackingSpawn);
174
+ // With maxParallel=1, agents run sequentially in definition order
175
+ const resultOrder = results.map(r => r.agent);
176
+ const expectedOrder = imagine_agents_js_1.IMAGINE_AGENTS.map(a => a.name);
177
+ assert.deepStrictEqual(resultOrder, expectedOrder);
178
+ });
179
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for GSWD Imagine Gate Module
3
+ *
4
+ * Covers: countListItems, validateDecisionGate (passing, heading failures, count failures, combined)
5
+ */
6
+ export {};
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for GSWD Imagine Gate Module
4
+ *
5
+ * Covers: countListItems, validateDecisionGate (passing, heading failures, count failures, combined)
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ const node_test_1 = require("node:test");
42
+ const assert = __importStar(require("node:assert"));
43
+ const imagine_gate_js_1 = require("../lib/imagine-gate.js");
44
+ // ─── Test Fixtures ───────────────────────────────────────────────────────────
45
+ /** Full valid DECISIONS.md with all 5 sections and sufficient counts */
46
+ const VALID_DECISIONS = `# Decisions
47
+
48
+ ## Frozen Decisions
49
+
50
+ - ICP: Solo founders building SaaS products
51
+ - Problem: Founders build before they think
52
+ - Wedge: CLI-first spec generator
53
+ - MVP: Four-stage pipeline (Imagine, Specify, Audit, Compile)
54
+ - Auth: Passwordless email
55
+ - Stack: Node.js + TypeScript
56
+ - Data store: SQLite for v1
57
+ - Success metric: Compile produces deterministic output
58
+ - Distribution: Open source CLI tool
59
+ - Pricing: Free for v1
60
+
61
+ ## Success Metrics
62
+
63
+ - 80% of users complete the full pipeline on first run
64
+ - Compile produces byte-identical output across runs
65
+
66
+ ## Out of Scope
67
+
68
+ - Web UI / dashboard
69
+ - Real-time collaborative editing
70
+ - Automatic GitHub/Jira ticket creation
71
+ - Cost/timeline estimation
72
+
73
+ ## Risks & Mitigations
74
+
75
+ - Risk: LLM output non-determinism — Mitigation: Compile uses zero LLM calls
76
+ - Risk: Agent parallelism limits — Mitigation: Configurable max_parallel_agents
77
+ - Risk: Incomplete specs pass audit — Mitigation: Strict coverage matrix
78
+ - Risk: State corruption on crash — Mitigation: Atomic write pattern
79
+ - Risk: Spec format drift — Mitigation: Required heading enforcement
80
+ - Risk: Users skip stages — Mitigation: Hard gates between stages
81
+
82
+ ## Open Questions
83
+
84
+ - What is the optimal max_parallel_agents default?
85
+ - Should we support custom auto policies?
86
+ `;
87
+ /** Minimal valid: exactly 8 frozen decisions, 1 metric, 5 risks, 1 out-of-scope */
88
+ const MINIMAL_VALID = `# Decisions
89
+
90
+ ## Frozen Decisions
91
+
92
+ - Decision one
93
+ - Decision two
94
+ - Decision three
95
+ - Decision four
96
+ - Decision five
97
+ - Decision six
98
+ - Decision seven
99
+ - Decision eight
100
+
101
+ ## Success Metrics
102
+
103
+ - One metric
104
+
105
+ ## Out of Scope
106
+
107
+ - One exclusion
108
+
109
+ ## Risks & Mitigations
110
+
111
+ - Risk one
112
+ - Risk two
113
+ - Risk three
114
+ - Risk four
115
+ - Risk five
116
+
117
+ ## Open Questions
118
+
119
+ `;
120
+ /** Maximum metrics (3) — still valid */
121
+ const MAX_METRICS = MINIMAL_VALID.replace('- One metric', '- Metric one\n- Metric two\n- Metric three');
122
+ // ─── Tests ───────────────────────────────────────────────────────────────────
123
+ (0, node_test_1.describe)('countListItems', () => {
124
+ (0, node_test_1.it)('counts bullet items (- prefix)', () => {
125
+ const content = '- item one\n- item two\n- item three';
126
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 3);
127
+ });
128
+ (0, node_test_1.it)('counts numbered items (1. prefix)', () => {
129
+ const content = '1. first\n2. second\n3. third';
130
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 3);
131
+ });
132
+ (0, node_test_1.it)('counts sub-heading items (### prefix)', () => {
133
+ const content = '### Decision A\nDetails...\n### Decision B\nMore details...';
134
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 2);
135
+ });
136
+ (0, node_test_1.it)('returns 0 for empty string', () => {
137
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(''), 0);
138
+ });
139
+ (0, node_test_1.it)('returns 0 for null/undefined', () => {
140
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(null), 0);
141
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(undefined), 0);
142
+ });
143
+ (0, node_test_1.it)('handles mixed list styles in same section', () => {
144
+ const content = '- bullet one\n1. numbered one\n### heading one\n* star bullet';
145
+ assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 4);
146
+ });
147
+ });
148
+ (0, node_test_1.describe)('validateDecisionGate — passing', () => {
149
+ (0, node_test_1.it)('full valid DECISIONS.md passes', () => {
150
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(VALID_DECISIONS);
151
+ assert.strictEqual(result.passed, true);
152
+ assert.deepStrictEqual(result.missing_sections, []);
153
+ assert.deepStrictEqual(result.insufficient_counts, []);
154
+ assert.ok(result.summary.includes('PASS'));
155
+ });
156
+ (0, node_test_1.it)('minimal valid (exactly 8 decisions, 1 metric, 5 risks, 1 out-of-scope) passes', () => {
157
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(MINIMAL_VALID);
158
+ assert.strictEqual(result.passed, true);
159
+ assert.deepStrictEqual(result.missing_sections, []);
160
+ assert.deepStrictEqual(result.insufficient_counts, []);
161
+ });
162
+ (0, node_test_1.it)('maximum metrics (exactly 3) passes', () => {
163
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(MAX_METRICS);
164
+ assert.strictEqual(result.passed, true);
165
+ });
166
+ });
167
+ (0, node_test_1.describe)('validateDecisionGate — heading failures', () => {
168
+ (0, node_test_1.it)('missing ## Frozen Decisions heading fails', () => {
169
+ const content = VALID_DECISIONS.replace('## Frozen Decisions', '## Decisions Made');
170
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
171
+ assert.strictEqual(result.passed, false);
172
+ assert.ok(result.missing_sections.includes('## Frozen Decisions'));
173
+ });
174
+ (0, node_test_1.it)('missing ## Success Metrics heading fails', () => {
175
+ const content = VALID_DECISIONS.replace('## Success Metrics', '## Metrics');
176
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
177
+ assert.strictEqual(result.passed, false);
178
+ assert.ok(result.missing_sections.includes('## Success Metrics'));
179
+ });
180
+ (0, node_test_1.it)('missing all headings fails with all 5 in missing_sections', () => {
181
+ const content = '# Some Document\n\nJust plain text with no required headings.';
182
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
183
+ assert.strictEqual(result.passed, false);
184
+ assert.strictEqual(result.missing_sections.length, 5);
185
+ assert.ok(result.missing_sections.includes('## Frozen Decisions'));
186
+ assert.ok(result.missing_sections.includes('## Success Metrics'));
187
+ assert.ok(result.missing_sections.includes('## Out of Scope'));
188
+ assert.ok(result.missing_sections.includes('## Risks & Mitigations'));
189
+ assert.ok(result.missing_sections.includes('## Open Questions'));
190
+ });
191
+ (0, node_test_1.it)('missing ## Open Questions heading fails (section must exist even if empty)', () => {
192
+ const content = VALID_DECISIONS.replace('## Open Questions', '## Questions');
193
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
194
+ assert.strictEqual(result.passed, false);
195
+ assert.ok(result.missing_sections.includes('## Open Questions'));
196
+ });
197
+ });
198
+ (0, node_test_1.describe)('validateDecisionGate — count failures', () => {
199
+ (0, node_test_1.it)('only 7 frozen decisions fails (need >= 8)', () => {
200
+ // Remove one decision from MINIMAL_VALID
201
+ const content = MINIMAL_VALID.replace('- Decision eight\n', '');
202
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
203
+ assert.strictEqual(result.passed, false);
204
+ const frozenIssue = result.insufficient_counts.find(c => c.section === 'Frozen Decisions');
205
+ assert.ok(frozenIssue, 'Should have Frozen Decisions issue');
206
+ assert.strictEqual(frozenIssue.actual, 7);
207
+ assert.strictEqual(frozenIssue.required, 8);
208
+ });
209
+ (0, node_test_1.it)('0 success metrics fails (need >= 1)', () => {
210
+ const content = MINIMAL_VALID.replace('- One metric\n', '');
211
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
212
+ assert.strictEqual(result.passed, false);
213
+ const metricsIssue = result.insufficient_counts.find(c => c.section === 'Success Metrics');
214
+ assert.ok(metricsIssue, 'Should have Success Metrics issue');
215
+ assert.strictEqual(metricsIssue.actual, 0);
216
+ assert.ok(metricsIssue.constraint === '1-3');
217
+ });
218
+ (0, node_test_1.it)('4 success metrics fails (need <= 3)', () => {
219
+ const content = MINIMAL_VALID.replace('- One metric', '- Metric one\n- Metric two\n- Metric three\n- Metric four');
220
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
221
+ assert.strictEqual(result.passed, false);
222
+ const metricsIssue = result.insufficient_counts.find(c => c.section === 'Success Metrics');
223
+ assert.ok(metricsIssue, 'Should have Success Metrics issue');
224
+ assert.strictEqual(metricsIssue.actual, 4);
225
+ assert.ok(metricsIssue.constraint === '1-3');
226
+ });
227
+ (0, node_test_1.it)('only 4 risks fails (need >= 5)', () => {
228
+ const content = MINIMAL_VALID.replace('- Risk five\n', '');
229
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
230
+ assert.strictEqual(result.passed, false);
231
+ const risksIssue = result.insufficient_counts.find(c => c.section === 'Risks & Mitigations');
232
+ assert.ok(risksIssue, 'Should have Risks & Mitigations issue');
233
+ assert.strictEqual(risksIssue.actual, 4);
234
+ assert.strictEqual(risksIssue.required, 5);
235
+ });
236
+ (0, node_test_1.it)('empty Out of Scope section fails (need >= 1 item)', () => {
237
+ const content = MINIMAL_VALID.replace('- One exclusion\n', '');
238
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
239
+ assert.strictEqual(result.passed, false);
240
+ const scopeIssue = result.insufficient_counts.find(c => c.section === 'Out of Scope');
241
+ assert.ok(scopeIssue, 'Should have Out of Scope issue');
242
+ assert.strictEqual(scopeIssue.actual, 0);
243
+ assert.strictEqual(scopeIssue.required, 1);
244
+ });
245
+ });
246
+ (0, node_test_1.describe)('validateDecisionGate — combined failures', () => {
247
+ (0, node_test_1.it)('missing heading AND insufficient count both reported', () => {
248
+ // Remove Frozen Decisions heading AND reduce risks
249
+ const content = VALID_DECISIONS
250
+ .replace('## Frozen Decisions', '## Decisions Made')
251
+ .replace('- Risk: Spec format drift — Mitigation: Required heading enforcement\n', '')
252
+ .replace('- Risk: Users skip stages — Mitigation: Hard gates between stages\n', '');
253
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
254
+ assert.strictEqual(result.passed, false);
255
+ assert.ok(result.missing_sections.length > 0, 'Should have missing sections');
256
+ assert.ok(result.insufficient_counts.length > 0, 'Should have insufficient counts');
257
+ });
258
+ (0, node_test_1.it)('summary includes count of total issues', () => {
259
+ const content = '# Some Document\n\nNo required headings at all.';
260
+ const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
261
+ assert.ok(result.summary.includes('FAIL'));
262
+ assert.ok(result.summary.includes('issue'), 'Summary should mention issues');
263
+ });
264
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for GSWD Imagine Input Module
3
+ *
4
+ * Covers: parseIdeaFile, buildFromIntake, validateBrief, convergence
5
+ */
6
+ export {};