gswd 1.0.1 → 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 (85) hide show
  1. package/bin/gswd-tools.cjs +228 -0
  2. package/commands/gswd/imagine.md +7 -1
  3. package/commands/gswd/start.md +507 -32
  4. package/dist/lib/audit.d.ts +205 -0
  5. package/dist/lib/audit.js +805 -0
  6. package/dist/lib/bootstrap.d.ts +103 -0
  7. package/dist/lib/bootstrap.js +563 -0
  8. package/dist/lib/compile.d.ts +239 -0
  9. package/dist/lib/compile.js +1152 -0
  10. package/dist/lib/config.d.ts +49 -0
  11. package/dist/lib/config.js +150 -0
  12. package/dist/lib/imagine-agents.d.ts +54 -0
  13. package/dist/lib/imagine-agents.js +185 -0
  14. package/dist/lib/imagine-gate.d.ts +47 -0
  15. package/dist/lib/imagine-gate.js +131 -0
  16. package/dist/lib/imagine-input.d.ts +46 -0
  17. package/dist/lib/imagine-input.js +233 -0
  18. package/dist/lib/imagine-synthesis.d.ts +90 -0
  19. package/dist/lib/imagine-synthesis.js +453 -0
  20. package/dist/lib/imagine.d.ts +56 -0
  21. package/dist/lib/imagine.js +413 -0
  22. package/dist/lib/intake.d.ts +27 -0
  23. package/dist/lib/intake.js +82 -0
  24. package/dist/lib/parse.d.ts +59 -0
  25. package/dist/lib/parse.js +171 -0
  26. package/dist/lib/render.d.ts +309 -0
  27. package/dist/lib/render.js +624 -0
  28. package/dist/lib/specify-agents.d.ts +120 -0
  29. package/dist/lib/specify-agents.js +269 -0
  30. package/dist/lib/specify-journeys.d.ts +124 -0
  31. package/dist/lib/specify-journeys.js +279 -0
  32. package/dist/lib/specify-nfr.d.ts +45 -0
  33. package/dist/lib/specify-nfr.js +159 -0
  34. package/dist/lib/specify-roles.d.ts +46 -0
  35. package/dist/lib/specify-roles.js +88 -0
  36. package/dist/lib/specify.d.ts +70 -0
  37. package/dist/lib/specify.js +676 -0
  38. package/dist/lib/state.d.ts +140 -0
  39. package/dist/lib/state.js +340 -0
  40. package/dist/tests/audit.test.d.ts +4 -0
  41. package/dist/tests/audit.test.js +1579 -0
  42. package/dist/tests/bootstrap.test.d.ts +5 -0
  43. package/dist/tests/bootstrap.test.js +611 -0
  44. package/dist/tests/compile.test.d.ts +4 -0
  45. package/dist/tests/compile.test.js +862 -0
  46. package/dist/tests/config.test.d.ts +4 -0
  47. package/dist/tests/config.test.js +191 -0
  48. package/dist/tests/imagine-agents.test.d.ts +6 -0
  49. package/dist/tests/imagine-agents.test.js +179 -0
  50. package/dist/tests/imagine-gate.test.d.ts +6 -0
  51. package/dist/tests/imagine-gate.test.js +264 -0
  52. package/dist/tests/imagine-input.test.d.ts +6 -0
  53. package/dist/tests/imagine-input.test.js +283 -0
  54. package/dist/tests/imagine-synthesis.test.d.ts +7 -0
  55. package/dist/tests/imagine-synthesis.test.js +380 -0
  56. package/dist/tests/imagine.test.d.ts +8 -0
  57. package/dist/tests/imagine.test.js +406 -0
  58. package/dist/tests/parse.test.d.ts +4 -0
  59. package/dist/tests/parse.test.js +285 -0
  60. package/dist/tests/render.test.d.ts +4 -0
  61. package/dist/tests/render.test.js +236 -0
  62. package/dist/tests/specify-agents.test.d.ts +4 -0
  63. package/dist/tests/specify-agents.test.js +352 -0
  64. package/dist/tests/specify-journeys.test.d.ts +5 -0
  65. package/dist/tests/specify-journeys.test.js +440 -0
  66. package/dist/tests/specify-nfr.test.d.ts +4 -0
  67. package/dist/tests/specify-nfr.test.js +205 -0
  68. package/dist/tests/specify-roles.test.d.ts +4 -0
  69. package/dist/tests/specify-roles.test.js +136 -0
  70. package/dist/tests/specify.test.d.ts +9 -0
  71. package/dist/tests/specify.test.js +544 -0
  72. package/dist/tests/state.test.d.ts +4 -0
  73. package/dist/tests/state.test.js +316 -0
  74. package/lib/bootstrap.ts +37 -11
  75. package/lib/compile.ts +426 -4
  76. package/lib/imagine-agents.ts +53 -7
  77. package/lib/imagine-synthesis.ts +170 -6
  78. package/lib/imagine.ts +59 -5
  79. package/lib/intake.ts +60 -0
  80. package/lib/parse.ts +2 -1
  81. package/lib/render.ts +566 -5
  82. package/lib/specify-agents.ts +25 -3
  83. package/lib/state.ts +115 -0
  84. package/package.json +3 -2
  85. package/templates/gswd/DECISIONS.template.md +3 -0
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ /**
3
+ * State module tests — atomic writes, STATE.json CRUD, idempotent init, ID registry
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 state_js_1 = require("../lib/state.js");
45
+ let tmpDir;
46
+ (0, node_test_1.beforeEach)(() => {
47
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-test-'));
48
+ });
49
+ (0, node_test_1.afterEach)(() => {
50
+ fs.rmSync(tmpDir, { recursive: true, force: true });
51
+ });
52
+ // ─── safeWriteFile ───────────────────────────────────────────────────────────
53
+ (0, node_test_1.describe)('safeWriteFile', () => {
54
+ (0, node_test_1.it)('writes file atomically with correct content', () => {
55
+ const filePath = path.join(tmpDir, 'test.txt');
56
+ (0, state_js_1.safeWriteFile)(filePath, 'hello world');
57
+ assert.strictEqual(fs.readFileSync(filePath, 'utf-8'), 'hello world');
58
+ });
59
+ (0, node_test_1.it)('leaves no .tmp file after successful write', () => {
60
+ const filePath = path.join(tmpDir, 'test.txt');
61
+ (0, state_js_1.safeWriteFile)(filePath, 'content');
62
+ assert.strictEqual(fs.existsSync(filePath + '.tmp'), false);
63
+ });
64
+ (0, node_test_1.it)('creates parent directories if missing', () => {
65
+ const filePath = path.join(tmpDir, 'deep', 'nested', 'dir', 'test.txt');
66
+ (0, state_js_1.safeWriteFile)(filePath, 'nested content');
67
+ assert.strictEqual(fs.readFileSync(filePath, 'utf-8'), 'nested content');
68
+ });
69
+ (0, node_test_1.it)('overwrites existing file atomically', () => {
70
+ const filePath = path.join(tmpDir, 'test.txt');
71
+ (0, state_js_1.safeWriteFile)(filePath, 'first');
72
+ (0, state_js_1.safeWriteFile)(filePath, 'second');
73
+ assert.strictEqual(fs.readFileSync(filePath, 'utf-8'), 'second');
74
+ });
75
+ });
76
+ // ─── safeWriteJson ───────────────────────────────────────────────────────────
77
+ (0, node_test_1.describe)('safeWriteJson', () => {
78
+ (0, node_test_1.it)('writes valid JSON with 2-space indent and trailing newline', () => {
79
+ const filePath = path.join(tmpDir, 'test.json');
80
+ (0, state_js_1.safeWriteJson)(filePath, { key: 'value' });
81
+ const content = fs.readFileSync(filePath, 'utf-8');
82
+ assert.strictEqual(content, '{\n "key": "value"\n}\n');
83
+ });
84
+ (0, node_test_1.it)('round-trips: write then read produces identical object', () => {
85
+ const filePath = path.join(tmpDir, 'test.json');
86
+ const data = { a: 1, b: [2, 3], c: { nested: true } };
87
+ (0, state_js_1.safeWriteJson)(filePath, data);
88
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
89
+ assert.deepStrictEqual(parsed, data);
90
+ });
91
+ });
92
+ // ─── createDefaultState ──────────────────────────────────────────────────────
93
+ (0, node_test_1.describe)('createDefaultState', () => {
94
+ (0, node_test_1.it)('returns object with all required top-level keys', () => {
95
+ const state = (0, state_js_1.createDefaultState)('my-project');
96
+ const keys = Object.keys(state);
97
+ assert.ok(keys.includes('version'));
98
+ assert.ok(keys.includes('project_slug'));
99
+ assert.ok(keys.includes('created_at'));
100
+ assert.ok(keys.includes('updated_at'));
101
+ assert.ok(keys.includes('stage'));
102
+ assert.ok(keys.includes('stage_status'));
103
+ assert.ok(keys.includes('last_checkpoint'));
104
+ assert.ok(keys.includes('auto'));
105
+ assert.ok(keys.includes('approvals'));
106
+ });
107
+ (0, node_test_1.it)('sets version to 1', () => {
108
+ const state = (0, state_js_1.createDefaultState)('test');
109
+ assert.strictEqual(state.version, 1);
110
+ });
111
+ (0, node_test_1.it)('sets project_slug from argument', () => {
112
+ const state = (0, state_js_1.createDefaultState)('my-project');
113
+ assert.strictEqual(state.project_slug, 'my-project');
114
+ });
115
+ (0, node_test_1.it)('sets all four stages to not_started', () => {
116
+ const state = (0, state_js_1.createDefaultState)('test');
117
+ assert.strictEqual(state.stage_status.imagine, 'not_started');
118
+ assert.strictEqual(state.stage_status.specify, 'not_started');
119
+ assert.strictEqual(state.stage_status.audit, 'not_started');
120
+ assert.strictEqual(state.stage_status.compile, 'not_started');
121
+ });
122
+ (0, node_test_1.it)('sets auto.policy to balanced', () => {
123
+ const state = (0, state_js_1.createDefaultState)('test');
124
+ assert.strictEqual(state.auto.policy, 'balanced');
125
+ });
126
+ (0, node_test_1.it)('sets last_checkpoint to null', () => {
127
+ const state = (0, state_js_1.createDefaultState)('test');
128
+ assert.strictEqual(state.last_checkpoint, null);
129
+ });
130
+ (0, node_test_1.it)('sets stage to init', () => {
131
+ const state = (0, state_js_1.createDefaultState)('test');
132
+ assert.strictEqual(state.stage, 'init');
133
+ });
134
+ });
135
+ // ─── readState ───────────────────────────────────────────────────────────────
136
+ (0, node_test_1.describe)('readState', () => {
137
+ (0, node_test_1.it)('returns null for missing file', () => {
138
+ const result = (0, state_js_1.readState)(path.join(tmpDir, 'nonexistent.json'));
139
+ assert.strictEqual(result, null);
140
+ });
141
+ (0, node_test_1.it)('returns null for invalid JSON', () => {
142
+ const filePath = path.join(tmpDir, 'bad.json');
143
+ fs.writeFileSync(filePath, 'not json');
144
+ assert.strictEqual((0, state_js_1.readState)(filePath), null);
145
+ });
146
+ (0, node_test_1.it)('returns null for JSON missing required fields', () => {
147
+ const filePath = path.join(tmpDir, 'incomplete.json');
148
+ fs.writeFileSync(filePath, JSON.stringify({ foo: 'bar' }));
149
+ assert.strictEqual((0, state_js_1.readState)(filePath), null);
150
+ });
151
+ (0, node_test_1.it)('returns parsed state for valid STATE.json', () => {
152
+ const state = (0, state_js_1.createDefaultState)('test');
153
+ const filePath = path.join(tmpDir, 'STATE.json');
154
+ (0, state_js_1.safeWriteJson)(filePath, state);
155
+ const read = (0, state_js_1.readState)(filePath);
156
+ assert.deepStrictEqual(read, state);
157
+ });
158
+ });
159
+ // ─── initState (idempotent) ──────────────────────────────────────────────────
160
+ (0, node_test_1.describe)('initState', () => {
161
+ (0, node_test_1.it)('creates STATE.json when missing', () => {
162
+ const stateDir = path.join(tmpDir, 'gswd');
163
+ const state = (0, state_js_1.initState)(stateDir, 'new-project');
164
+ assert.strictEqual(state.project_slug, 'new-project');
165
+ assert.ok(fs.existsSync(path.join(stateDir, 'STATE.json')));
166
+ });
167
+ (0, node_test_1.it)('returns existing state unchanged when valid STATE.json exists', () => {
168
+ const stateDir = path.join(tmpDir, 'gswd');
169
+ const first = (0, state_js_1.initState)(stateDir, 'project');
170
+ const second = (0, state_js_1.initState)(stateDir, 'project');
171
+ assert.deepStrictEqual(first, second);
172
+ });
173
+ (0, node_test_1.it)('does NOT update timestamps on existing valid state', () => {
174
+ const stateDir = path.join(tmpDir, 'gswd');
175
+ const first = (0, state_js_1.initState)(stateDir, 'project');
176
+ // Wait a tick to ensure timestamp would differ
177
+ const second = (0, state_js_1.initState)(stateDir, 'project');
178
+ assert.strictEqual(first.created_at, second.created_at);
179
+ assert.strictEqual(first.updated_at, second.updated_at);
180
+ });
181
+ (0, node_test_1.it)('creates directory if missing', () => {
182
+ const stateDir = path.join(tmpDir, 'deep', 'nested', 'gswd');
183
+ (0, state_js_1.initState)(stateDir, 'project');
184
+ assert.ok(fs.existsSync(path.join(stateDir, 'STATE.json')));
185
+ });
186
+ (0, node_test_1.it)('handles corrupt JSON by creating fresh state', () => {
187
+ const stateDir = path.join(tmpDir, 'gswd');
188
+ fs.mkdirSync(stateDir, { recursive: true });
189
+ fs.writeFileSync(path.join(stateDir, 'STATE.json'), 'corrupt!!!');
190
+ const state = (0, state_js_1.initState)(stateDir, 'recovered');
191
+ assert.strictEqual(state.project_slug, 'recovered');
192
+ assert.strictEqual(state.version, 1);
193
+ });
194
+ });
195
+ // ─── updateStageStatus ───────────────────────────────────────────────────────
196
+ (0, node_test_1.describe)('updateStageStatus', () => {
197
+ (0, node_test_1.it)('updates specific stage to new status', () => {
198
+ const stateDir = path.join(tmpDir, 'gswd');
199
+ (0, state_js_1.initState)(stateDir, 'project');
200
+ const statePath = path.join(stateDir, 'STATE.json');
201
+ (0, state_js_1.updateStageStatus)(statePath, 'imagine', 'in_progress');
202
+ const state = (0, state_js_1.readState)(statePath);
203
+ assert.strictEqual(state.stage_status.imagine, 'in_progress');
204
+ });
205
+ (0, node_test_1.it)('preserves all other fields', () => {
206
+ const stateDir = path.join(tmpDir, 'gswd');
207
+ (0, state_js_1.initState)(stateDir, 'project');
208
+ const statePath = path.join(stateDir, 'STATE.json');
209
+ (0, state_js_1.updateStageStatus)(statePath, 'imagine', 'done');
210
+ const state = (0, state_js_1.readState)(statePath);
211
+ assert.strictEqual(state.project_slug, 'project');
212
+ assert.strictEqual(state.stage_status.specify, 'not_started');
213
+ assert.strictEqual(state.stage_status.audit, 'not_started');
214
+ assert.strictEqual(state.stage_status.compile, 'not_started');
215
+ });
216
+ });
217
+ // ─── writeCheckpoint ─────────────────────────────────────────────────────────
218
+ (0, node_test_1.describe)('writeCheckpoint', () => {
219
+ (0, node_test_1.it)('sets last_checkpoint with workflow, checkpoint_id, timestamp', () => {
220
+ const stateDir = path.join(tmpDir, 'gswd');
221
+ (0, state_js_1.initState)(stateDir, 'project');
222
+ const statePath = path.join(stateDir, 'STATE.json');
223
+ (0, state_js_1.writeCheckpoint)(statePath, 'gswd/imagine', 'step-1');
224
+ const state = (0, state_js_1.readState)(statePath);
225
+ assert.ok(state.last_checkpoint);
226
+ assert.strictEqual(state.last_checkpoint.workflow, 'gswd/imagine');
227
+ assert.strictEqual(state.last_checkpoint.checkpoint_id, 'step-1');
228
+ assert.ok(state.last_checkpoint.timestamp);
229
+ });
230
+ (0, node_test_1.it)('preserves all other state fields', () => {
231
+ const stateDir = path.join(tmpDir, 'gswd');
232
+ (0, state_js_1.initState)(stateDir, 'project');
233
+ const statePath = path.join(stateDir, 'STATE.json');
234
+ (0, state_js_1.writeCheckpoint)(statePath, 'gswd/specify', 'mid-point');
235
+ const state = (0, state_js_1.readState)(statePath);
236
+ assert.strictEqual(state.project_slug, 'project');
237
+ assert.strictEqual(state.version, 1);
238
+ });
239
+ });
240
+ // ─── ID Registry ─────────────────────────────────────────────────────────────
241
+ (0, node_test_1.describe)('allocateIdRange', () => {
242
+ (0, node_test_1.it)('first allocation returns range starting at 1', () => {
243
+ const stateDir = path.join(tmpDir, 'gswd');
244
+ (0, state_js_1.initState)(stateDir, 'project');
245
+ const statePath = path.join(stateDir, 'STATE.json');
246
+ const range = (0, state_js_1.allocateIdRange)(statePath, 'FR', 'agent-1');
247
+ assert.strictEqual(range.start, 1);
248
+ assert.strictEqual(range.end, 50);
249
+ });
250
+ (0, node_test_1.it)('second allocation continues from previous end', () => {
251
+ const stateDir = path.join(tmpDir, 'gswd');
252
+ (0, state_js_1.initState)(stateDir, 'project');
253
+ const statePath = path.join(stateDir, 'STATE.json');
254
+ (0, state_js_1.allocateIdRange)(statePath, 'FR', 'agent-1');
255
+ const range2 = (0, state_js_1.allocateIdRange)(statePath, 'FR', 'agent-2');
256
+ assert.strictEqual(range2.start, 51);
257
+ assert.strictEqual(range2.end, 100);
258
+ });
259
+ (0, node_test_1.it)('different ID types have independent ranges', () => {
260
+ const stateDir = path.join(tmpDir, 'gswd');
261
+ (0, state_js_1.initState)(stateDir, 'project');
262
+ const statePath = path.join(stateDir, 'STATE.json');
263
+ const fr = (0, state_js_1.allocateIdRange)(statePath, 'FR', 'agent-1');
264
+ const j = (0, state_js_1.allocateIdRange)(statePath, 'J', 'agent-1');
265
+ assert.strictEqual(fr.start, 1);
266
+ assert.strictEqual(j.start, 1);
267
+ });
268
+ (0, node_test_1.it)('respects custom range size', () => {
269
+ const stateDir = path.join(tmpDir, 'gswd');
270
+ (0, state_js_1.initState)(stateDir, 'project');
271
+ const statePath = path.join(stateDir, 'STATE.json');
272
+ const range = (0, state_js_1.allocateIdRange)(statePath, 'FR', 'agent-1', 25);
273
+ assert.strictEqual(range.start, 1);
274
+ assert.strictEqual(range.end, 25);
275
+ });
276
+ });
277
+ (0, node_test_1.describe)('getIdRanges', () => {
278
+ (0, node_test_1.it)('returns empty object when no registry exists', () => {
279
+ const stateDir = path.join(tmpDir, 'gswd');
280
+ (0, state_js_1.initState)(stateDir, 'project');
281
+ const statePath = path.join(stateDir, 'STATE.json');
282
+ const ranges = (0, state_js_1.getIdRanges)(statePath);
283
+ assert.deepStrictEqual(ranges, {});
284
+ });
285
+ (0, node_test_1.it)('returns all ranges when no type filter', () => {
286
+ const stateDir = path.join(tmpDir, 'gswd');
287
+ (0, state_js_1.initState)(stateDir, 'project');
288
+ const statePath = path.join(stateDir, 'STATE.json');
289
+ (0, state_js_1.allocateIdRange)(statePath, 'FR', 'a1');
290
+ (0, state_js_1.allocateIdRange)(statePath, 'J', 'a1');
291
+ const ranges = (0, state_js_1.getIdRanges)(statePath);
292
+ assert.ok(ranges.FR);
293
+ assert.ok(ranges.J);
294
+ });
295
+ (0, node_test_1.it)('filters by type when specified', () => {
296
+ const stateDir = path.join(tmpDir, 'gswd');
297
+ (0, state_js_1.initState)(stateDir, 'project');
298
+ const statePath = path.join(stateDir, 'STATE.json');
299
+ (0, state_js_1.allocateIdRange)(statePath, 'FR', 'a1');
300
+ (0, state_js_1.allocateIdRange)(statePath, 'J', 'a1');
301
+ const ranges = (0, state_js_1.getIdRanges)(statePath, 'FR');
302
+ assert.ok(ranges.FR);
303
+ assert.strictEqual(ranges.J, undefined);
304
+ });
305
+ });
306
+ (0, node_test_1.describe)('resetIdRegistry', () => {
307
+ (0, node_test_1.it)('clears all ranges', () => {
308
+ const stateDir = path.join(tmpDir, 'gswd');
309
+ (0, state_js_1.initState)(stateDir, 'project');
310
+ const statePath = path.join(stateDir, 'STATE.json');
311
+ (0, state_js_1.allocateIdRange)(statePath, 'FR', 'a1');
312
+ (0, state_js_1.resetIdRegistry)(statePath);
313
+ const ranges = (0, state_js_1.getIdRanges)(statePath);
314
+ assert.deepStrictEqual(ranges, {});
315
+ });
316
+ });
package/lib/bootstrap.ts CHANGED
@@ -20,7 +20,8 @@ import { initState, readState, writeState, writeCheckpoint } from './state.js';
20
20
  import type { StageStatus } from './state.js';
21
21
  import { getGswdConfig } from './config.js';
22
22
  import type { GswdConfig } from './config.js';
23
- import { renderBanner, renderCheckpoint, renderNextUp } from './render.js';
23
+ import { renderBanner, renderCheckpoint, renderNextUp, renderFileInventory, renderContextBanner, extractProductContext, STAGE_CONTEXT_TEMPLATES } from './render.js';
24
+ import type { ProductContext } from './render.js';
24
25
  import type { AutoDecision } from './imagine-synthesis.js';
25
26
  import { runImagine } from './imagine.js';
26
27
  import type { ImagineResult } from './imagine.js';
@@ -330,9 +331,9 @@ export function handleInterrupt(
330
331
  // Step 2: Render checkpoint box
331
332
  const box = renderCheckpoint(
332
333
  'Auto Mode Interrupted',
333
- 'Bootstrap cannot continue automatically. Resolve:',
334
+ 'Pipeline cannot continue automatically. Resolve:',
334
335
  reasons.map(r => r.length > 50 ? r.slice(0, 47) + '...' : r),
335
- 'Resolve issues above, then re-run bootstrap'
336
+ 'Resolve issues above, then re-run /gswd:start'
336
337
  );
337
338
  console.log(box);
338
339
 
@@ -446,13 +447,17 @@ export async function runBootstrap(options: BootstrapOptions): Promise<Bootstrap
446
447
  writeState(statePath, initStateObj);
447
448
  }
448
449
 
449
- console.log(renderBanner('BOOTSTRAP'));
450
+ console.log(renderBanner('PIPELINE'));
451
+
452
+ // Extract product context for contextual banners
453
+ const productCtx = extractProductContext(planningDir);
450
454
 
451
455
  // ── IMAGINE ──────────────────────────────────────────────────────────
452
456
  const imagineState = readState(statePath)!;
453
457
  if (!shouldSkipStage('imagine', imagineState, planningDir, options.resume ?? false)) {
454
458
  writeCheckpoint(statePath, 'gswd/bootstrap', 'imagine-start');
455
- console.log(renderBanner('IMAGINE'));
459
+ const imagineCtxLine = STAGE_CONTEXT_TEMPLATES.imagine?.(productCtx) || '';
460
+ console.log(renderContextBanner('IMAGINE', imagineCtxLine));
456
461
  // runImagine is ASYNC — MUST await
457
462
  const imagineResult: ImagineResult = await runImagine({
458
463
  ideaFilePath: options.ideaFilePath,
@@ -502,7 +507,10 @@ export async function runBootstrap(options: BootstrapOptions): Promise<Bootstrap
502
507
  const specifyState = readState(statePath)!;
503
508
  if (!shouldSkipStage('specify', specifyState, planningDir, options.resume ?? false)) {
504
509
  writeCheckpoint(statePath, 'gswd/bootstrap', 'specify-start');
505
- console.log(renderBanner('SPECIFY'));
510
+ // Re-extract after imagine (DECISIONS.md now exists with precise data)
511
+ const specifyCtx = extractProductContext(planningDir);
512
+ const specifyCtxLine = STAGE_CONTEXT_TEMPLATES.specify?.(specifyCtx) || '';
513
+ console.log(renderContextBanner('SPECIFY', specifyCtxLine));
506
514
  // runSpecify is ASYNC — MUST await
507
515
  // When no spawnFn provided, set skipAgents: true (Pitfall 1)
508
516
  const specifyResult: SpecifyResult = await runSpecify({
@@ -542,7 +550,9 @@ export async function runBootstrap(options: BootstrapOptions): Promise<Bootstrap
542
550
  const auditState = readState(statePath)!;
543
551
  if (!shouldSkipStage('audit', auditState, planningDir, options.resume ?? false)) {
544
552
  writeCheckpoint(statePath, 'gswd/bootstrap', 'audit-start');
545
- console.log(renderBanner('AUDIT'));
553
+ const auditCtx = extractProductContext(planningDir);
554
+ const auditCtxLine = STAGE_CONTEXT_TEMPLATES.audit?.(auditCtx) || '';
555
+ console.log(renderContextBanner('AUDIT', auditCtxLine));
546
556
  // runAuditWorkflow is SYNC — no await (Pitfall 7)
547
557
  const auditResult: AuditWorkflowResult = runAuditWorkflow({
548
558
  planningDir,
@@ -602,10 +612,26 @@ export async function runBootstrap(options: BootstrapOptions): Promise<Bootstrap
602
612
  console.log(renderAutoDecisionSummary(allAutoDecisions));
603
613
  }
604
614
 
605
- console.log(renderNextUp(
606
- ['PROJECT.md', 'REQUIREMENTS.md', 'ROADMAP.md', 'STATE.md'],
607
- '/gsd:plan-phase 01'
608
- ));
615
+ // Display dynamic file inventory (matches manual mode output)
616
+ const inventoryFiles = [
617
+ { path: '.planning/IMAGINE.md', description: 'Product vision, target user, and direction' },
618
+ { path: '.planning/DECISIONS.md', description: 'Frozen decisions, success metrics, risks' },
619
+ { path: '.planning/SPEC.md', description: 'Functional requirements with acceptance criteria' },
620
+ { path: '.planning/NFR.md', description: 'Non-functional requirements with thresholds' },
621
+ { path: '.planning/JOURNEYS.md', description: 'User journeys with FR and integration linkages' },
622
+ { path: '.planning/INTEGRATIONS.md', description: 'External integrations with status and fallbacks' },
623
+ { path: '.planning/PROJECT.md', description: 'GSD project context (compiled)' },
624
+ { path: '.planning/REQUIREMENTS.md', description: 'GSD requirements with traceability (compiled)' },
625
+ { path: '.planning/ROADMAP.md', description: 'GSD roadmap with phased journeys (compiled)' },
626
+ { path: '.planning/STATE.md', description: 'GSD project state and decisions (compiled)' },
627
+ { path: '.planning/research/gswd/SUMMARY.md', description: 'GSD-format research bridge (compiled)' },
628
+ ].filter(f => {
629
+ try { fs.accessSync(path.join(process.cwd(), f.path)); return true; }
630
+ catch { return false; }
631
+ });
632
+
633
+ console.log(renderFileInventory(inventoryFiles));
634
+ console.log('\nNext up:\n /gsd:plan-phase 01\n\nTip:\n If context is getting crowded, run /clear first.');
609
635
 
610
636
  return {
611
637
  status: 'done',