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
|
@@ -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
|
+
});
|