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,5 @@
1
+ /**
2
+ * Bootstrap module tests — policy engine, resume gate, interrupt handler, auto decision summary,
3
+ * and runBootstrap orchestrator
4
+ */
5
+ export {};
@@ -0,0 +1,611 @@
1
+ "use strict";
2
+ /**
3
+ * Bootstrap module tests — policy engine, resume gate, interrupt handler, auto decision summary,
4
+ * and runBootstrap orchestrator
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const node_test_1 = require("node:test");
41
+ const assert = __importStar(require("node:assert"));
42
+ const fs = __importStar(require("node:fs"));
43
+ const os = __importStar(require("node:os"));
44
+ const path = __importStar(require("node:path"));
45
+ const bootstrap_js_1 = require("../lib/bootstrap.js");
46
+ const state_js_1 = require("../lib/state.js");
47
+ const config_js_1 = require("../lib/config.js");
48
+ // ─── Helpers ────────────────────────────────────────────────────────────────
49
+ function makeTmpDir() {
50
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-bootstrap-'));
51
+ }
52
+ function makeConfig(overrides) {
53
+ return { ...config_js_1.GSWD_CONFIG_DEFAULTS, ...overrides, auto: { ...config_js_1.GSWD_CONFIG_DEFAULTS.auto, ...overrides?.auto } };
54
+ }
55
+ // ─── checkPolicy ────────────────────────────────────────────────────────────
56
+ (0, node_test_1.describe)('checkPolicy', () => {
57
+ (0, node_test_1.it)('returns shouldInterrupt:false when no violations', () => {
58
+ const result = (0, bootstrap_js_1.checkPolicy)({
59
+ config: makeConfig(),
60
+ integrationsContent: '',
61
+ decisionsContent: '',
62
+ nfrContent: '',
63
+ });
64
+ assert.strictEqual(result.shouldInterrupt, false);
65
+ assert.strictEqual(result.reasons.length, 0);
66
+ });
67
+ (0, node_test_1.it)('detects paid integrations not pre-approved', () => {
68
+ const intContent = `## Integrations
69
+
70
+ ### I-001: Stripe
71
+ Payment processing for subscriptions.
72
+ **Cost:** $50/month
73
+ **Auth:** API key required
74
+ **Status:** approved
75
+ `;
76
+ const result = (0, bootstrap_js_1.checkPolicy)({
77
+ config: makeConfig(),
78
+ integrationsContent: intContent,
79
+ });
80
+ assert.strictEqual(result.shouldInterrupt, true);
81
+ assert.ok(result.reasons.some(r => r.includes('I-001')));
82
+ });
83
+ (0, node_test_1.it)('ignores free integrations ($0)', () => {
84
+ const intContent = `## Integrations
85
+
86
+ ### I-001: SendGrid Free Tier
87
+ Email sending service.
88
+ **Cost:** $0/month
89
+ **Status:** approved
90
+ `;
91
+ const result = (0, bootstrap_js_1.checkPolicy)({
92
+ config: makeConfig(),
93
+ integrationsContent: intContent,
94
+ });
95
+ assert.strictEqual(result.shouldInterrupt, false);
96
+ });
97
+ (0, node_test_1.it)('skips paid integration check when pre-approved', () => {
98
+ const intContent = `## Integrations
99
+
100
+ ### I-001: Stripe
101
+ Payment processing.
102
+ **Cost:** $50/month
103
+ **Status:** approved
104
+ `;
105
+ const config = makeConfig({ auto: { ...config_js_1.GSWD_CONFIG_DEFAULTS.auto, preapproved_integrations: ['I-001'] } });
106
+ const result = (0, bootstrap_js_1.checkPolicy)({
107
+ config,
108
+ integrationsContent: intContent,
109
+ });
110
+ // Should not interrupt because I-001 is pre-approved
111
+ const stripeReasons = result.reasons.filter(r => r.includes('I-001') && r.includes('pre-approval'));
112
+ assert.strictEqual(stripeReasons.length, 0);
113
+ });
114
+ (0, node_test_1.it)('detects credential requirements', () => {
115
+ const intContent = `## Integrations
116
+
117
+ ### I-002: Twilio
118
+ SMS messaging service.
119
+ **Auth:** API key required
120
+ **Cost:** $25/month
121
+ **Status:** approved
122
+ `;
123
+ const result = (0, bootstrap_js_1.checkPolicy)({
124
+ config: makeConfig(),
125
+ integrationsContent: intContent,
126
+ });
127
+ assert.strictEqual(result.shouldInterrupt, true);
128
+ assert.ok(result.reasons.some(r => r.includes('credential') || r.includes('I-002')));
129
+ });
130
+ (0, node_test_1.it)('detects conflicting requirements', () => {
131
+ const decisionsContent = `## Frozen Decisions
132
+
133
+ 1. **Auth model:** no accounts — anonymous usage only
134
+ 2. **Data store:** local storage
135
+ `;
136
+ const intContent = `## Integrations
137
+
138
+ ### I-001: Stripe
139
+ Payment processing for subscriptions.
140
+ **Cost:** $50/month
141
+ `;
142
+ const result = (0, bootstrap_js_1.checkPolicy)({
143
+ config: makeConfig(),
144
+ integrationsContent: intContent,
145
+ decisionsContent: decisionsContent,
146
+ });
147
+ assert.strictEqual(result.shouldInterrupt, true);
148
+ assert.ok(result.reasons.some(r => r.toLowerCase().includes('conflict')));
149
+ });
150
+ (0, node_test_1.it)('detects security posture mismatch', () => {
151
+ const nfrContent = `## Non-Functional Requirements
152
+
153
+ ### NFR-001: HIPAA Compliance
154
+ All patient health data must be encrypted at rest and in transit.
155
+ HIPAA compliance is mandatory for healthcare use case.
156
+ `;
157
+ const result = (0, bootstrap_js_1.checkPolicy)({
158
+ config: makeConfig(),
159
+ nfrContent: nfrContent,
160
+ });
161
+ assert.strictEqual(result.shouldInterrupt, true);
162
+ assert.ok(result.reasons.some(r => r.toLowerCase().includes('security') || r.toLowerCase().includes('hipaa')));
163
+ });
164
+ (0, node_test_1.it)('combines multiple reasons', () => {
165
+ const intContent = `## Integrations
166
+
167
+ ### I-001: Stripe
168
+ Payment processing.
169
+ **Cost:** $50/month
170
+ **Auth:** API key required
171
+
172
+ ### I-002: Twilio
173
+ SMS service.
174
+ **Cost:** $25/month
175
+ **Auth:** OAuth secret required
176
+ `;
177
+ const result = (0, bootstrap_js_1.checkPolicy)({
178
+ config: makeConfig(),
179
+ integrationsContent: intContent,
180
+ });
181
+ assert.strictEqual(result.shouldInterrupt, true);
182
+ assert.ok(result.reasons.length >= 2, `Expected 2+ reasons, got ${result.reasons.length}`);
183
+ });
184
+ });
185
+ // ─── validateStageArtifacts ─────────────────────────────────────────────────
186
+ (0, node_test_1.describe)('validateStageArtifacts', () => {
187
+ (0, node_test_1.it)('returns complete:true when all artifacts exist', () => {
188
+ const tmpDir = makeTmpDir();
189
+ try {
190
+ // Create all imagine artifacts
191
+ for (const f of bootstrap_js_1.STAGE_ARTIFACTS.imagine) {
192
+ fs.writeFileSync(path.join(tmpDir, f), 'content');
193
+ }
194
+ const result = (0, bootstrap_js_1.validateStageArtifacts)(tmpDir, 'imagine');
195
+ assert.strictEqual(result.complete, true);
196
+ assert.strictEqual(result.missing.length, 0);
197
+ }
198
+ finally {
199
+ fs.rmSync(tmpDir, { recursive: true, force: true });
200
+ }
201
+ });
202
+ (0, node_test_1.it)('returns complete:false with missing files listed', () => {
203
+ const tmpDir = makeTmpDir();
204
+ try {
205
+ // Create only 3 of 5 imagine artifacts
206
+ const imagineFiles = bootstrap_js_1.STAGE_ARTIFACTS.imagine;
207
+ for (let i = 0; i < 3; i++) {
208
+ fs.writeFileSync(path.join(tmpDir, imagineFiles[i]), 'content');
209
+ }
210
+ const result = (0, bootstrap_js_1.validateStageArtifacts)(tmpDir, 'imagine');
211
+ assert.strictEqual(result.complete, false);
212
+ assert.strictEqual(result.missing.length, 2);
213
+ assert.ok(result.missing.includes(imagineFiles[3]));
214
+ assert.ok(result.missing.includes(imagineFiles[4]));
215
+ }
216
+ finally {
217
+ fs.rmSync(tmpDir, { recursive: true, force: true });
218
+ }
219
+ });
220
+ (0, node_test_1.it)('returns complete:false for empty files (size 0)', () => {
221
+ const tmpDir = makeTmpDir();
222
+ try {
223
+ const imagineFiles = bootstrap_js_1.STAGE_ARTIFACTS.imagine;
224
+ // Create all but make one empty
225
+ for (let i = 0; i < imagineFiles.length; i++) {
226
+ if (i === 0) {
227
+ fs.writeFileSync(path.join(tmpDir, imagineFiles[i]), '');
228
+ }
229
+ else {
230
+ fs.writeFileSync(path.join(tmpDir, imagineFiles[i]), 'content');
231
+ }
232
+ }
233
+ const result = (0, bootstrap_js_1.validateStageArtifacts)(tmpDir, 'imagine');
234
+ assert.strictEqual(result.complete, false);
235
+ assert.ok(result.missing.includes(imagineFiles[0]));
236
+ }
237
+ finally {
238
+ fs.rmSync(tmpDir, { recursive: true, force: true });
239
+ }
240
+ });
241
+ (0, node_test_1.it)('validates specify stage (5 files)', () => {
242
+ const tmpDir = makeTmpDir();
243
+ try {
244
+ for (const f of bootstrap_js_1.STAGE_ARTIFACTS.specify) {
245
+ fs.writeFileSync(path.join(tmpDir, f), 'content');
246
+ }
247
+ const result = (0, bootstrap_js_1.validateStageArtifacts)(tmpDir, 'specify');
248
+ assert.strictEqual(result.complete, true);
249
+ assert.strictEqual(result.missing.length, 0);
250
+ }
251
+ finally {
252
+ fs.rmSync(tmpDir, { recursive: true, force: true });
253
+ }
254
+ });
255
+ (0, node_test_1.it)('validates audit stage (1 file)', () => {
256
+ const tmpDir = makeTmpDir();
257
+ try {
258
+ for (const f of bootstrap_js_1.STAGE_ARTIFACTS.audit) {
259
+ fs.writeFileSync(path.join(tmpDir, f), 'content');
260
+ }
261
+ const result = (0, bootstrap_js_1.validateStageArtifacts)(tmpDir, 'audit');
262
+ assert.strictEqual(result.complete, true);
263
+ }
264
+ finally {
265
+ fs.rmSync(tmpDir, { recursive: true, force: true });
266
+ }
267
+ });
268
+ (0, node_test_1.it)('validates compile stage (4 files)', () => {
269
+ const tmpDir = makeTmpDir();
270
+ try {
271
+ for (const f of bootstrap_js_1.STAGE_ARTIFACTS.compile) {
272
+ fs.writeFileSync(path.join(tmpDir, f), 'content');
273
+ }
274
+ const result = (0, bootstrap_js_1.validateStageArtifacts)(tmpDir, 'compile');
275
+ assert.strictEqual(result.complete, true);
276
+ }
277
+ finally {
278
+ fs.rmSync(tmpDir, { recursive: true, force: true });
279
+ }
280
+ });
281
+ });
282
+ // ─── shouldSkipStage ────────────────────────────────────────────────────────
283
+ (0, node_test_1.describe)('shouldSkipStage', () => {
284
+ (0, node_test_1.it)('returns false when resume is false', () => {
285
+ const tmpDir = makeTmpDir();
286
+ try {
287
+ const gswdDir = path.join(tmpDir, 'gswd');
288
+ fs.mkdirSync(gswdDir, { recursive: true });
289
+ const state = (0, state_js_1.initState)(gswdDir, 'test');
290
+ state.stage_status.imagine = 'done';
291
+ // Create all imagine artifacts
292
+ for (const f of bootstrap_js_1.STAGE_ARTIFACTS.imagine) {
293
+ fs.writeFileSync(path.join(tmpDir, f), 'content');
294
+ }
295
+ const result = (0, bootstrap_js_1.shouldSkipStage)('imagine', state, tmpDir, false);
296
+ assert.strictEqual(result, false);
297
+ }
298
+ finally {
299
+ fs.rmSync(tmpDir, { recursive: true, force: true });
300
+ }
301
+ });
302
+ (0, node_test_1.it)('returns false when stage status is not done/pass', () => {
303
+ const tmpDir = makeTmpDir();
304
+ try {
305
+ const gswdDir = path.join(tmpDir, 'gswd');
306
+ fs.mkdirSync(gswdDir, { recursive: true });
307
+ const state = (0, state_js_1.initState)(gswdDir, 'test');
308
+ state.stage_status.imagine = 'in_progress';
309
+ const result = (0, bootstrap_js_1.shouldSkipStage)('imagine', state, tmpDir, true);
310
+ assert.strictEqual(result, false);
311
+ }
312
+ finally {
313
+ fs.rmSync(tmpDir, { recursive: true, force: true });
314
+ }
315
+ });
316
+ (0, node_test_1.it)('returns true when resume=true, status=done, all artifacts present', () => {
317
+ const tmpDir = makeTmpDir();
318
+ try {
319
+ const gswdDir = path.join(tmpDir, 'gswd');
320
+ fs.mkdirSync(gswdDir, { recursive: true });
321
+ const state = (0, state_js_1.initState)(gswdDir, 'test');
322
+ state.stage_status.imagine = 'done';
323
+ for (const f of bootstrap_js_1.STAGE_ARTIFACTS.imagine) {
324
+ fs.writeFileSync(path.join(tmpDir, f), 'content');
325
+ }
326
+ const result = (0, bootstrap_js_1.shouldSkipStage)('imagine', state, tmpDir, true);
327
+ assert.strictEqual(result, true);
328
+ }
329
+ finally {
330
+ fs.rmSync(tmpDir, { recursive: true, force: true });
331
+ }
332
+ });
333
+ (0, node_test_1.it)('returns false when resume=true, status=done, but artifact missing (RESM-03)', () => {
334
+ const tmpDir = makeTmpDir();
335
+ try {
336
+ const gswdDir = path.join(tmpDir, 'gswd');
337
+ fs.mkdirSync(gswdDir, { recursive: true });
338
+ const state = (0, state_js_1.initState)(gswdDir, 'test');
339
+ state.stage_status.imagine = 'done';
340
+ // Only create some artifacts, not all
341
+ fs.writeFileSync(path.join(tmpDir, 'IMAGINE.md'), 'content');
342
+ fs.writeFileSync(path.join(tmpDir, 'ICP.md'), 'content');
343
+ const result = (0, bootstrap_js_1.shouldSkipStage)('imagine', state, tmpDir, true);
344
+ assert.strictEqual(result, false);
345
+ }
346
+ finally {
347
+ fs.rmSync(tmpDir, { recursive: true, force: true });
348
+ }
349
+ });
350
+ (0, node_test_1.it)('handles audit stage with pass status', () => {
351
+ const tmpDir = makeTmpDir();
352
+ try {
353
+ const gswdDir = path.join(tmpDir, 'gswd');
354
+ fs.mkdirSync(gswdDir, { recursive: true });
355
+ const state = (0, state_js_1.initState)(gswdDir, 'test');
356
+ state.stage_status.audit = 'pass';
357
+ fs.writeFileSync(path.join(tmpDir, 'AUDIT.md'), 'content');
358
+ const result = (0, bootstrap_js_1.shouldSkipStage)('audit', state, tmpDir, true);
359
+ assert.strictEqual(result, true);
360
+ }
361
+ finally {
362
+ fs.rmSync(tmpDir, { recursive: true, force: true });
363
+ }
364
+ });
365
+ });
366
+ // ─── handleInterrupt ────────────────────────────────────────────────────────
367
+ (0, node_test_1.describe)('handleInterrupt', () => {
368
+ (0, node_test_1.it)('writes reasons to state.auto.interrupt_reasons', () => {
369
+ const tmpDir = makeTmpDir();
370
+ try {
371
+ const gswdDir = path.join(tmpDir, 'gswd');
372
+ fs.mkdirSync(gswdDir, { recursive: true });
373
+ (0, state_js_1.initState)(gswdDir, 'test');
374
+ const statePath = path.join(gswdDir, 'STATE.json');
375
+ const result = (0, bootstrap_js_1.handleInterrupt)(['Paid integration I-001 requires approval', 'Credentials needed for I-002'], statePath, ['imagine'], ['IMAGINE.md'], [], 0);
376
+ const state = (0, state_js_1.readState)(statePath);
377
+ assert.ok(state);
378
+ assert.ok(state.auto.interrupt_reasons.length >= 2);
379
+ assert.ok(state.auto.interrupt_reasons.some(r => r.includes('I-001')));
380
+ assert.ok(state.auto.interrupt_reasons.some(r => r.includes('I-002')));
381
+ }
382
+ finally {
383
+ fs.rmSync(tmpDir, { recursive: true, force: true });
384
+ }
385
+ });
386
+ (0, node_test_1.it)('returns BootstrapResult with status interrupted', () => {
387
+ const tmpDir = makeTmpDir();
388
+ try {
389
+ const gswdDir = path.join(tmpDir, 'gswd');
390
+ fs.mkdirSync(gswdDir, { recursive: true });
391
+ (0, state_js_1.initState)(gswdDir, 'test');
392
+ const statePath = path.join(gswdDir, 'STATE.json');
393
+ const result = (0, bootstrap_js_1.handleInterrupt)(['Some reason'], statePath, ['imagine'], ['IMAGINE.md'], [], 0);
394
+ assert.strictEqual(result.status, 'interrupted');
395
+ assert.deepStrictEqual(result.interruptReasons, ['Some reason']);
396
+ }
397
+ finally {
398
+ fs.rmSync(tmpDir, { recursive: true, force: true });
399
+ }
400
+ });
401
+ });
402
+ // ─── renderAutoDecisionSummary ──────────────────────────────────────────────
403
+ (0, node_test_1.describe)('renderAutoDecisionSummary', () => {
404
+ (0, node_test_1.it)('returns empty string for no decisions', () => {
405
+ const result = (0, bootstrap_js_1.renderAutoDecisionSummary)([]);
406
+ assert.strictEqual(result, '');
407
+ });
408
+ (0, node_test_1.it)('formats decisions with type, chosen, rationale', () => {
409
+ const decisions = [
410
+ { type: 'direction', chosen: 'Direction 1: MVP', rationale: 'Highest score', recorded_at: '2026-01-01T00:00:00.000Z' },
411
+ { type: 'icp', chosen: 'Solo developers', rationale: 'Best pain score', recorded_at: '2026-01-01T00:00:00.000Z' },
412
+ ];
413
+ const result = (0, bootstrap_js_1.renderAutoDecisionSummary)(decisions);
414
+ assert.ok(result.includes('direction'));
415
+ assert.ok(result.includes('Direction 1: MVP'));
416
+ assert.ok(result.includes('icp'));
417
+ assert.ok(result.includes('Solo developers'));
418
+ });
419
+ });
420
+ // ─── runBootstrap ───────────────────────────────────────────────────────────
421
+ (0, node_test_1.describe)('runBootstrap', () => {
422
+ (0, node_test_1.it)('returns failed when no ideaFilePath in auto mode', async () => {
423
+ const result = await (0, bootstrap_js_1.runBootstrap)({ auto: true });
424
+ assert.strictEqual(result.status, 'failed');
425
+ assert.ok(result.error?.includes('idea'));
426
+ });
427
+ (0, node_test_1.it)('returns failed when ideaFilePath does not exist', async () => {
428
+ const tmpDir = makeTmpDir();
429
+ try {
430
+ const result = await (0, bootstrap_js_1.runBootstrap)({
431
+ auto: true,
432
+ ideaFilePath: path.join(tmpDir, 'nonexistent-idea.md'),
433
+ planningDir: tmpDir,
434
+ configPath: path.join(tmpDir, 'config.json'),
435
+ skipResearch: true,
436
+ });
437
+ // Should fail because the idea file doesn't exist
438
+ assert.strictEqual(result.status, 'failed');
439
+ }
440
+ finally {
441
+ fs.rmSync(tmpDir, { recursive: true, force: true });
442
+ }
443
+ });
444
+ });
445
+ // ─── e2e: bootstrap --auto @idea.md ─────────────────────────────────────────
446
+ /**
447
+ * Helper: create a complete tmpDir environment for e2e bootstrap testing.
448
+ * Sets up: planningDir, gswd/STATE.json, config.json, templates, idea.md.
449
+ */
450
+ function makeBootstrapEnv() {
451
+ const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-e2e-bootstrap-'));
452
+ const planningDir = path.join(rootDir, '.planning');
453
+ fs.mkdirSync(planningDir, { recursive: true });
454
+ // Config
455
+ const configPath = path.join(planningDir, 'config.json');
456
+ const config = {
457
+ gswd: {
458
+ mode: 'balanced',
459
+ strict_gates: true,
460
+ max_parallel_agents: 4,
461
+ external_research: false,
462
+ integration_budget_usd_month: 0,
463
+ doc_verbosity: 'standard',
464
+ phase_style: 'thin',
465
+ auto: {
466
+ policy: 'balanced',
467
+ allow_paid_integrations_under_budget: false,
468
+ default_auth_model: 'passwordless-email',
469
+ default_data_store: 'sqlite',
470
+ default_stack: 'Next.js + TypeScript',
471
+ preapproved_integrations: [],
472
+ },
473
+ },
474
+ };
475
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
476
+ // Templates (specify needs these)
477
+ const templatesDir = path.join(rootDir, 'templates', 'gswd');
478
+ fs.mkdirSync(templatesDir, { recursive: true });
479
+ fs.writeFileSync(path.join(templatesDir, 'SPEC.template.md'), `# Specification\n\n## Roles & Permissions\n\n<!-- GSWD:CONTENT:ROLES -->\n\n## Functional Requirements\n\n<!-- GSWD:CONTENT:FRS -->\n\n## Acceptance Criteria\n\n<!-- GSWD:CONTENT:ACCEPTANCE -->\n\n## Traceability\n\n<!-- GSWD:CONTENT:TRACEABILITY -->\n\n<!-- GSWD:COMPLETE -->\n`);
480
+ fs.writeFileSync(path.join(templatesDir, 'JOURNEYS.template.md'), `# User Journeys\n\n## Journeys\n\n<!-- GSWD:CONTENT:JOURNEYS -->\n\n<!-- GSWD:COMPLETE -->\n`);
481
+ fs.writeFileSync(path.join(templatesDir, 'NFR.template.md'), `# Non-Functional Requirements\n\n## Non-Functional Requirements\n\n<!-- GSWD:CONTENT:NFRS -->\n\n<!-- GSWD:COMPLETE -->\n`);
482
+ fs.writeFileSync(path.join(templatesDir, 'ARCHITECTURE.template.md'), `# Architecture\n\n## Architecture\n\n### Components\n\n<!-- GSWD:CONTENT:COMPONENTS -->\n\n### Data Model\n\n<!-- GSWD:CONTENT:DATA_MODEL -->\n\n### Ownership Boundaries\n\n<!-- GSWD:CONTENT:OWNERSHIP -->\n\n<!-- GSWD:COMPLETE -->\n`);
483
+ fs.writeFileSync(path.join(templatesDir, 'INTEGRATIONS.template.md'), `# External Integrations\n\n## Integrations\n\n<!-- GSWD:CONTENT:INTEGRATIONS -->\n\n<!-- GSWD:COMPLETE -->\n`);
484
+ // Idea fixture (use the real fixture)
485
+ const ideaFilePath = path.resolve(__dirname, 'fixtures/bootstrap/idea.md');
486
+ return {
487
+ rootDir,
488
+ planningDir,
489
+ configPath,
490
+ ideaFilePath,
491
+ cleanup: () => {
492
+ try {
493
+ fs.rmSync(rootDir, { recursive: true, force: true });
494
+ }
495
+ catch { /* ignore */ }
496
+ },
497
+ };
498
+ }
499
+ (0, node_test_1.describe)('e2e: bootstrap --auto @idea.md', () => {
500
+ (0, node_test_1.it)('runs full pipeline and produces done state (TEST-05)', async () => {
501
+ const env = makeBootstrapEnv();
502
+ try {
503
+ const result = await (0, bootstrap_js_1.runBootstrap)({
504
+ ideaFilePath: env.ideaFilePath,
505
+ auto: true,
506
+ skipResearch: true,
507
+ planningDir: env.planningDir,
508
+ configPath: env.configPath,
509
+ // No spawnFn — specify uses skipAgents=true
510
+ });
511
+ // Pipeline completed
512
+ assert.strictEqual(result.status, 'done', `Expected done but got ${result.status}: ${result.error}`);
513
+ // State verification
514
+ const statePath = path.join(env.planningDir, 'gswd', 'STATE.json');
515
+ const finalState = (0, state_js_1.readState)(statePath);
516
+ assert.ok(finalState, 'STATE.json must be readable');
517
+ assert.strictEqual(finalState.stage, 'done', 'state.stage must be done (Pitfall 8)');
518
+ assert.strictEqual(finalState.stage_status.audit, 'pass', 'audit must have passed');
519
+ assert.strictEqual(finalState.stage_status.compile, 'done', 'compile must be done');
520
+ // Contract docs exist
521
+ const contractDocs = ['PROJECT.md', 'REQUIREMENTS.md', 'ROADMAP.md', 'STATE.md'];
522
+ for (const doc of contractDocs) {
523
+ assert.ok(fs.existsSync(path.join(env.planningDir, doc)), `${doc} must exist in planningDir`);
524
+ }
525
+ // All 4 stages completed
526
+ assert.strictEqual(result.stagesCompleted.length, 4, 'All 4 stages must complete');
527
+ assert.ok(result.stagesCompleted.includes('imagine'));
528
+ assert.ok(result.stagesCompleted.includes('specify'));
529
+ assert.ok(result.stagesCompleted.includes('audit'));
530
+ assert.ok(result.stagesCompleted.includes('compile'));
531
+ }
532
+ finally {
533
+ env.cleanup();
534
+ }
535
+ });
536
+ (0, node_test_1.it)('records auto decisions in state when auto mode', async () => {
537
+ const env = makeBootstrapEnv();
538
+ try {
539
+ const result = await (0, bootstrap_js_1.runBootstrap)({
540
+ ideaFilePath: env.ideaFilePath,
541
+ auto: true,
542
+ skipResearch: true,
543
+ planningDir: env.planningDir,
544
+ configPath: env.configPath,
545
+ });
546
+ assert.strictEqual(result.status, 'done');
547
+ const statePath = path.join(env.planningDir, 'gswd', 'STATE.json');
548
+ const finalState = (0, state_js_1.readState)(statePath);
549
+ assert.ok(finalState);
550
+ assert.ok(finalState.auto.decisions, 'auto.decisions must exist');
551
+ assert.ok(finalState.auto.decisions.length > 0, 'at least one auto decision recorded');
552
+ assert.strictEqual(finalState.auto.enabled, true, 'auto.enabled must be true');
553
+ }
554
+ finally {
555
+ env.cleanup();
556
+ }
557
+ });
558
+ (0, node_test_1.it)('produces imagine and specify artifacts', async () => {
559
+ const env = makeBootstrapEnv();
560
+ try {
561
+ const result = await (0, bootstrap_js_1.runBootstrap)({
562
+ ideaFilePath: env.ideaFilePath,
563
+ auto: true,
564
+ skipResearch: true,
565
+ planningDir: env.planningDir,
566
+ configPath: env.configPath,
567
+ });
568
+ assert.strictEqual(result.status, 'done');
569
+ // Imagine artifacts
570
+ assert.ok(fs.existsSync(path.join(env.planningDir, 'DECISIONS.md')), 'DECISIONS.md must exist');
571
+ assert.ok(fs.existsSync(path.join(env.planningDir, 'IMAGINE.md')), 'IMAGINE.md must exist');
572
+ // Specify artifacts
573
+ assert.ok(fs.existsSync(path.join(env.planningDir, 'SPEC.md')), 'SPEC.md must exist');
574
+ assert.ok(fs.existsSync(path.join(env.planningDir, 'JOURNEYS.md')), 'JOURNEYS.md must exist');
575
+ // Audit report
576
+ assert.ok(fs.existsSync(path.join(env.planningDir, 'AUDIT.md')), 'AUDIT.md must exist');
577
+ }
578
+ finally {
579
+ env.cleanup();
580
+ }
581
+ });
582
+ (0, node_test_1.it)('resume skips completed stages', async () => {
583
+ const env = makeBootstrapEnv();
584
+ try {
585
+ // First run
586
+ const result1 = await (0, bootstrap_js_1.runBootstrap)({
587
+ ideaFilePath: env.ideaFilePath,
588
+ auto: true,
589
+ skipResearch: true,
590
+ planningDir: env.planningDir,
591
+ configPath: env.configPath,
592
+ });
593
+ assert.strictEqual(result1.status, 'done');
594
+ // Second run with resume=true
595
+ const result2 = await (0, bootstrap_js_1.runBootstrap)({
596
+ ideaFilePath: env.ideaFilePath,
597
+ auto: true,
598
+ resume: true,
599
+ skipResearch: true,
600
+ planningDir: env.planningDir,
601
+ configPath: env.configPath,
602
+ });
603
+ assert.strictEqual(result2.status, 'done');
604
+ // All stages should be in completed list (via skip)
605
+ assert.strictEqual(result2.stagesCompleted.length, 4);
606
+ }
607
+ finally {
608
+ env.cleanup();
609
+ }
610
+ });
611
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Compile module tests — spec bundle parsing, sort helpers, and 4 document generators
3
+ */
4
+ export {};