@girardmedia/bootspring 2.0.21 → 2.0.23

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 (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,621 @@
1
+ /**
2
+ * Bootspring Build State Management
3
+ *
4
+ * Manages BUILD_STATE.json for continuous build loop persistence.
5
+ * Tracks phases, implementation queue, MVP criteria, and loop session.
6
+ *
7
+ * @package bootspring
8
+ * @module core/build-state
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ export const BUILD_STATE_FILE = 'BUILD_STATE.json';
15
+ export const PLANNING_DIR = 'planning';
16
+
17
+ // Type definitions
18
+ export interface Task {
19
+ id: string;
20
+ title: string;
21
+ description?: string | undefined;
22
+ source?: string | undefined;
23
+ sourceSection?: string | null | undefined;
24
+ phase?: string | undefined;
25
+ status: 'pending' | 'in_progress' | 'completed' | 'blocked' | 'skipped';
26
+ acceptanceCriteria?: string[] | undefined;
27
+ dependencies?: string[] | undefined;
28
+ estimatedComplexity?: string | undefined;
29
+ createdAt?: string | undefined;
30
+ updatedAt?: string | undefined;
31
+ completedAt?: string | undefined;
32
+ error?: string | undefined;
33
+ lastOutput?: string | undefined;
34
+ }
35
+
36
+ export interface MvpFeature {
37
+ name: string;
38
+ status: 'pending' | 'completed';
39
+ }
40
+
41
+ export interface PhaseData {
42
+ status: 'pending' | 'in_progress' | 'completed';
43
+ tasks: { id: string; title: string; status: string }[];
44
+ }
45
+
46
+ export interface LoopSession {
47
+ sessionId: string | null;
48
+ currentIteration: number;
49
+ maxIterations: number;
50
+ startedAt: string | null;
51
+ lastUpdated: string | null;
52
+ pausedAt: string | null;
53
+ completedAt?: string | undefined;
54
+ failedAt?: string | undefined;
55
+ failureReason?: string | undefined;
56
+ }
57
+
58
+ export interface MvpCriteria {
59
+ features: MvpFeature[];
60
+ completionPercentage: number;
61
+ allCriteriaMet: boolean;
62
+ }
63
+
64
+ export interface BuildMetadata {
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ seedSource: string | null;
68
+ preseedDocs: string[];
69
+ }
70
+
71
+ export interface BuildState {
72
+ version: string;
73
+ projectName: string;
74
+ status: 'pending' | 'in_progress' | 'paused' | 'completed' | 'failed';
75
+ currentPhase: string | null;
76
+ phases: Record<string, PhaseData>;
77
+ implementationQueue: Task[];
78
+ mvpCriteria: MvpCriteria;
79
+ loopSession: LoopSession;
80
+ metadata: BuildMetadata;
81
+ }
82
+
83
+ export interface BuildStats {
84
+ total: number;
85
+ completed: number;
86
+ pending: number;
87
+ inProgress: number;
88
+ blocked: number;
89
+ skipped: number;
90
+ progress: number;
91
+ currentPhase: string | null;
92
+ mvpProgress: number;
93
+ mvpComplete: boolean;
94
+ iteration: number;
95
+ maxIterations: number;
96
+ status: string;
97
+ }
98
+
99
+ export interface InitConfig {
100
+ projectName?: string | undefined;
101
+ phases?: Record<string, PhaseData> | undefined;
102
+ implementationQueue?: Task[] | undefined;
103
+ mvpCriteria?: Partial<MvpCriteria> | undefined;
104
+ maxIterations?: number | undefined;
105
+ seedSource?: string | undefined;
106
+ preseedDocs?: string[] | undefined;
107
+ }
108
+
109
+ export interface UpdateDetails {
110
+ completedAt?: string | undefined;
111
+ error?: string | undefined;
112
+ output?: string | undefined;
113
+ }
114
+
115
+ /**
116
+ * Default build state schema
117
+ */
118
+ export function getDefaultState(projectName: string = 'Project'): BuildState {
119
+ return {
120
+ version: '1.0.0',
121
+ projectName,
122
+ status: 'pending',
123
+ currentPhase: null,
124
+
125
+ phases: {
126
+ foundation: { status: 'pending', tasks: [] },
127
+ mvp: { status: 'pending', tasks: [] },
128
+ launch: { status: 'pending', tasks: [] }
129
+ },
130
+
131
+ implementationQueue: [],
132
+
133
+ mvpCriteria: {
134
+ features: [],
135
+ completionPercentage: 0,
136
+ allCriteriaMet: false
137
+ },
138
+
139
+ loopSession: {
140
+ sessionId: null,
141
+ currentIteration: 0,
142
+ maxIterations: 50,
143
+ startedAt: null,
144
+ lastUpdated: null,
145
+ pausedAt: null
146
+ },
147
+
148
+ metadata: {
149
+ createdAt: new Date().toISOString(),
150
+ updatedAt: new Date().toISOString(),
151
+ seedSource: null,
152
+ preseedDocs: []
153
+ }
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Get the planning directory path
159
+ */
160
+ export function getPlanningDir(projectRoot: string): string {
161
+ return path.join(projectRoot, PLANNING_DIR);
162
+ }
163
+
164
+ /**
165
+ * Get the build state file path
166
+ */
167
+ export function getStatePath(projectRoot: string): string {
168
+ return path.join(getPlanningDir(projectRoot), BUILD_STATE_FILE);
169
+ }
170
+
171
+ /**
172
+ * Check if build state exists
173
+ */
174
+ export function exists(projectRoot: string): boolean {
175
+ return fs.existsSync(getStatePath(projectRoot));
176
+ }
177
+
178
+ /**
179
+ * Generate a unique session ID
180
+ */
181
+ export function generateSessionId(): string {
182
+ return `build-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
183
+ }
184
+
185
+ /**
186
+ * Initialize a new build state
187
+ */
188
+ export function initialize(projectRoot: string, config: InitConfig = {}): BuildState {
189
+ const planningDir = getPlanningDir(projectRoot);
190
+
191
+ // Create planning directory if it doesn't exist
192
+ if (!fs.existsSync(planningDir)) {
193
+ fs.mkdirSync(planningDir, { recursive: true });
194
+ }
195
+
196
+ const state = getDefaultState(config.projectName || 'Project');
197
+
198
+ // Apply config overrides
199
+ if (config.phases) {
200
+ state.phases = { ...state.phases, ...config.phases };
201
+ }
202
+
203
+ if (config.implementationQueue) {
204
+ state.implementationQueue = config.implementationQueue;
205
+ }
206
+
207
+ if (config.mvpCriteria) {
208
+ state.mvpCriteria = { ...state.mvpCriteria, ...config.mvpCriteria };
209
+ }
210
+
211
+ if (config.maxIterations) {
212
+ state.loopSession.maxIterations = config.maxIterations;
213
+ }
214
+
215
+ if (config.seedSource) {
216
+ state.metadata.seedSource = config.seedSource;
217
+ }
218
+
219
+ if (config.preseedDocs) {
220
+ state.metadata.preseedDocs = config.preseedDocs;
221
+ }
222
+
223
+ // Generate session ID
224
+ state.loopSession.sessionId = generateSessionId();
225
+
226
+ // Save initial state
227
+ save(projectRoot, state);
228
+
229
+ return state;
230
+ }
231
+
232
+ /**
233
+ * Load build state from disk
234
+ */
235
+ export function load(projectRoot: string): BuildState | null {
236
+ const statePath = getStatePath(projectRoot);
237
+
238
+ if (!fs.existsSync(statePath)) {
239
+ return null;
240
+ }
241
+
242
+ try {
243
+ const content = fs.readFileSync(statePath, 'utf-8');
244
+ return JSON.parse(content) as BuildState;
245
+ } catch (error) {
246
+ console.error(`Failed to load build state: ${(error as Error).message}`);
247
+ return null;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Save build state to disk
253
+ */
254
+ export function save(projectRoot: string, state: BuildState): boolean {
255
+ const statePath = getStatePath(projectRoot);
256
+ const planningDir = getPlanningDir(projectRoot);
257
+
258
+ // Ensure planning directory exists
259
+ if (!fs.existsSync(planningDir)) {
260
+ fs.mkdirSync(planningDir, { recursive: true });
261
+ }
262
+
263
+ // Update timestamp
264
+ state.metadata.updatedAt = new Date().toISOString();
265
+ state.loopSession.lastUpdated = new Date().toISOString();
266
+
267
+ try {
268
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
269
+ return true;
270
+ } catch (error) {
271
+ console.error(`Failed to save build state: ${(error as Error).message}`);
272
+ return false;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Update phase progress based on task statuses
278
+ */
279
+ function updatePhaseProgress(state: BuildState, phaseName: string): void {
280
+ const phaseTasks = state.implementationQueue.filter(t => t.phase === phaseName);
281
+ const completedTasks = phaseTasks.filter(t => t.status === 'completed');
282
+
283
+ if (phaseTasks.length === 0) return;
284
+
285
+ const progress = completedTasks.length / phaseTasks.length;
286
+ const phase = state.phases[phaseName];
287
+
288
+ if (!phase) return;
289
+
290
+ if (progress === 0) {
291
+ phase.status = 'pending';
292
+ } else if (progress === 1) {
293
+ phase.status = 'completed';
294
+ } else {
295
+ phase.status = 'in_progress';
296
+ }
297
+
298
+ phase.tasks = phaseTasks.map(t => ({
299
+ id: t.id,
300
+ title: t.title,
301
+ status: t.status
302
+ }));
303
+ }
304
+
305
+ /**
306
+ * Update MVP criteria progress
307
+ */
308
+ function updateMvpProgress(state: BuildState): void {
309
+ const mvpTasks = state.implementationQueue.filter(t => t.phase === 'mvp');
310
+ const completedMvpTasks = mvpTasks.filter(t => t.status === 'completed');
311
+
312
+ if (mvpTasks.length > 0) {
313
+ state.mvpCriteria.completionPercentage = Math.round(
314
+ (completedMvpTasks.length / mvpTasks.length) * 100
315
+ );
316
+ }
317
+
318
+ // Update feature completion status
319
+ state.mvpCriteria.features = state.mvpCriteria.features.map(feature => {
320
+ const relatedTasks = mvpTasks.filter(t =>
321
+ t.title.toLowerCase().includes(feature.name.toLowerCase()) ||
322
+ t.sourceSection?.toLowerCase().includes(feature.name.toLowerCase())
323
+ );
324
+
325
+ const allComplete = relatedTasks.length > 0 &&
326
+ relatedTasks.every(t => t.status === 'completed');
327
+
328
+ return {
329
+ ...feature,
330
+ status: allComplete ? 'completed' as const : 'pending' as const
331
+ };
332
+ });
333
+
334
+ // Check if all MVP criteria are met
335
+ state.mvpCriteria.allCriteriaMet =
336
+ state.mvpCriteria.completionPercentage === 100 ||
337
+ state.mvpCriteria.features.every(f => f.status === 'completed');
338
+ }
339
+
340
+ /**
341
+ * Update task progress
342
+ */
343
+ export function updateProgress(
344
+ projectRoot: string,
345
+ taskId: string,
346
+ status: Task['status'],
347
+ details: UpdateDetails = {}
348
+ ): BuildState | null {
349
+ const state = load(projectRoot);
350
+ if (!state) return null;
351
+
352
+ // Find task in queue
353
+ const taskIndex = state.implementationQueue.findIndex(t => t.id === taskId);
354
+ if (taskIndex === -1) return null;
355
+
356
+ const task = state.implementationQueue[taskIndex];
357
+ if (!task) return null;
358
+
359
+ // Update task status
360
+ task.status = status;
361
+ task.updatedAt = new Date().toISOString();
362
+
363
+ // Add any additional details
364
+ if (details.completedAt) {
365
+ task.completedAt = details.completedAt;
366
+ }
367
+ if (details.error) {
368
+ task.error = details.error;
369
+ }
370
+ if (details.output) {
371
+ task.lastOutput = details.output;
372
+ }
373
+
374
+ // Update phase progress
375
+ if (task.phase && state.phases[task.phase]) {
376
+ updatePhaseProgress(state, task.phase);
377
+ }
378
+
379
+ // Update MVP criteria progress
380
+ updateMvpProgress(state);
381
+
382
+ // Update iteration count if completing
383
+ if (status === 'completed' || status === 'skipped') {
384
+ state.loopSession.currentIteration++;
385
+ }
386
+
387
+ // Save updated state
388
+ save(projectRoot, state);
389
+
390
+ return state;
391
+ }
392
+
393
+ /**
394
+ * Get the next pending task
395
+ */
396
+ export function getNextTask(projectRoot: string): Task | null {
397
+ const state = load(projectRoot);
398
+ if (!state) return null;
399
+
400
+ // First, check for any in_progress tasks
401
+ const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
402
+ if (inProgress) return inProgress;
403
+
404
+ // Then get next pending task (respecting dependencies)
405
+ const pendingTasks = state.implementationQueue.filter(t => t.status === 'pending');
406
+
407
+ for (const task of pendingTasks) {
408
+ // Check if all dependencies are completed
409
+ if (!task.dependencies || task.dependencies.length === 0) {
410
+ return task;
411
+ }
412
+
413
+ const depsCompleted = task.dependencies.every(depId => {
414
+ const depTask = state.implementationQueue.find(t => t.id === depId);
415
+ return depTask && depTask.status === 'completed';
416
+ });
417
+
418
+ if (depsCompleted) {
419
+ return task;
420
+ }
421
+ }
422
+
423
+ return null;
424
+ }
425
+
426
+ /**
427
+ * Add a task to the implementation queue
428
+ */
429
+ export function addTask(projectRoot: string, task: Partial<Task>): BuildState | null {
430
+ const state = load(projectRoot);
431
+ if (!state) return null;
432
+
433
+ // Generate task ID if not provided
434
+ if (!task.id) {
435
+ const count = state.implementationQueue.length + 1;
436
+ task.id = `task-${count.toString().padStart(3, '0')}`;
437
+ }
438
+
439
+ // Set defaults
440
+ const newTask: Task = {
441
+ id: task.id,
442
+ title: task.title || 'Untitled Task',
443
+ description: task.description,
444
+ source: task.source,
445
+ sourceSection: task.sourceSection,
446
+ phase: task.phase,
447
+ status: task.status || 'pending',
448
+ acceptanceCriteria: task.acceptanceCriteria,
449
+ dependencies: task.dependencies,
450
+ estimatedComplexity: task.estimatedComplexity,
451
+ createdAt: new Date().toISOString()
452
+ };
453
+
454
+ state.implementationQueue.push(newTask);
455
+ save(projectRoot, state);
456
+
457
+ return state;
458
+ }
459
+
460
+ /**
461
+ * Set tasks in batch
462
+ */
463
+ export function setTasks(projectRoot: string, tasks: Partial<Task>[]): BuildState | null {
464
+ const state = load(projectRoot);
465
+ if (!state) return null;
466
+
467
+ // Process tasks with defaults
468
+ state.implementationQueue = tasks.map((task, index) => ({
469
+ id: task.id || `task-${(index + 1).toString().padStart(3, '0')}`,
470
+ title: task.title || 'Untitled Task',
471
+ description: task.description || '',
472
+ source: task.source || 'manual',
473
+ sourceSection: task.sourceSection || null,
474
+ phase: task.phase || 'mvp',
475
+ status: task.status || 'pending',
476
+ acceptanceCriteria: task.acceptanceCriteria || [],
477
+ dependencies: task.dependencies || [],
478
+ estimatedComplexity: task.estimatedComplexity || 'medium',
479
+ createdAt: new Date().toISOString()
480
+ }));
481
+
482
+ save(projectRoot, state);
483
+ return state;
484
+ }
485
+
486
+ /**
487
+ * Set MVP features
488
+ */
489
+ export function setMvpFeatures(projectRoot: string, features: (string | { name: string })[]): BuildState | null {
490
+ const state = load(projectRoot);
491
+ if (!state) return null;
492
+
493
+ state.mvpCriteria.features = features.map(f => ({
494
+ name: typeof f === 'string' ? f : f.name,
495
+ status: 'pending' as const
496
+ }));
497
+
498
+ save(projectRoot, state);
499
+ return state;
500
+ }
501
+
502
+ /**
503
+ * Update loop session
504
+ */
505
+ export function updateSession(projectRoot: string, updates: Partial<LoopSession>): BuildState | null {
506
+ const state = load(projectRoot);
507
+ if (!state) return null;
508
+
509
+ state.loopSession = {
510
+ ...state.loopSession,
511
+ ...updates
512
+ };
513
+
514
+ save(projectRoot, state);
515
+ return state;
516
+ }
517
+
518
+ /**
519
+ * Pause the build loop
520
+ */
521
+ export function pause(projectRoot: string): BuildState | null {
522
+ const state = load(projectRoot);
523
+ if (!state) return null;
524
+
525
+ state.status = 'paused';
526
+ state.loopSession.pausedAt = new Date().toISOString();
527
+
528
+ save(projectRoot, state);
529
+ return state;
530
+ }
531
+
532
+ /**
533
+ * Resume the build loop
534
+ */
535
+ export function resume(projectRoot: string): BuildState | null {
536
+ const state = load(projectRoot);
537
+ if (!state) return null;
538
+
539
+ state.status = 'in_progress';
540
+ state.loopSession.pausedAt = null;
541
+
542
+ // Generate new session ID if resuming after long pause
543
+ const pausedAt = state.loopSession.pausedAt ? new Date(state.loopSession.pausedAt) : null;
544
+ const now = new Date();
545
+ if (pausedAt && (now.getTime() - pausedAt.getTime()) > 3600000) { // 1 hour
546
+ state.loopSession.sessionId = generateSessionId();
547
+ }
548
+
549
+ save(projectRoot, state);
550
+ return state;
551
+ }
552
+
553
+ /**
554
+ * Mark build as complete
555
+ */
556
+ export function complete(projectRoot: string): BuildState | null {
557
+ const state = load(projectRoot);
558
+ if (!state) return null;
559
+
560
+ state.status = 'completed';
561
+ state.loopSession.completedAt = new Date().toISOString();
562
+
563
+ save(projectRoot, state);
564
+ return state;
565
+ }
566
+
567
+ /**
568
+ * Mark build as failed
569
+ */
570
+ export function fail(projectRoot: string, reason: string): BuildState | null {
571
+ const state = load(projectRoot);
572
+ if (!state) return null;
573
+
574
+ state.status = 'failed';
575
+ state.loopSession.failedAt = new Date().toISOString();
576
+ state.loopSession.failureReason = reason;
577
+
578
+ save(projectRoot, state);
579
+ return state;
580
+ }
581
+
582
+ /**
583
+ * Get build statistics
584
+ */
585
+ export function getStats(projectRoot: string): BuildStats | null {
586
+ const state = load(projectRoot);
587
+ if (!state) return null;
588
+
589
+ const tasks = state.implementationQueue;
590
+ const completed = tasks.filter(t => t.status === 'completed').length;
591
+ const pending = tasks.filter(t => t.status === 'pending').length;
592
+ const inProgress = tasks.filter(t => t.status === 'in_progress').length;
593
+ const blocked = tasks.filter(t => t.status === 'blocked').length;
594
+ const skipped = tasks.filter(t => t.status === 'skipped').length;
595
+
596
+ return {
597
+ total: tasks.length,
598
+ completed,
599
+ pending,
600
+ inProgress,
601
+ blocked,
602
+ skipped,
603
+ progress: tasks.length > 0 ? Math.round((completed / tasks.length) * 100) : 0,
604
+ currentPhase: state.currentPhase,
605
+ mvpProgress: state.mvpCriteria.completionPercentage,
606
+ mvpComplete: state.mvpCriteria.allCriteriaMet,
607
+ iteration: state.loopSession.currentIteration,
608
+ maxIterations: state.loopSession.maxIterations,
609
+ status: state.status
610
+ };
611
+ }
612
+
613
+ /**
614
+ * Reset build state
615
+ */
616
+ export function reset(projectRoot: string): BuildState {
617
+ const state = load(projectRoot);
618
+ const projectName = state?.projectName || 'Project';
619
+
620
+ return initialize(projectRoot, { projectName });
621
+ }