@girardmedia/bootspring 2.1.3 → 2.2.1
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/bootspring.js +157 -83
- package/claude-commands/agent.md +34 -0
- package/claude-commands/bs.md +31 -0
- package/claude-commands/build.md +25 -0
- package/claude-commands/skill.md +31 -0
- package/claude-commands/todo.md +25 -0
- package/dist/core/index.d.ts +5814 -0
- package/dist/core.js +5779 -0
- package/dist/index.js +93883 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp-server.js +2298 -0
- package/generators/api-docs.js +3 -3
- package/generators/decisions.js +14 -14
- package/generators/health.js +6 -6
- package/generators/sprint.js +4 -4
- package/generators/templates/build-planning.template.js +2 -2
- package/generators/visual-doc-generator.js +1 -1
- package/package.json +22 -68
- package/cli/agent.js +0 -799
- package/cli/auth.js +0 -896
- package/cli/billing.js +0 -320
- package/cli/build.js +0 -1442
- package/cli/dashboard.js +0 -123
- package/cli/init.js +0 -669
- package/cli/mcp.js +0 -240
- package/cli/orchestrator.js +0 -240
- package/cli/project.js +0 -825
- package/cli/quality.js +0 -281
- package/cli/skill.js +0 -503
- package/cli/switch.js +0 -453
- package/cli/todo.js +0 -629
- package/cli/update.js +0 -132
- package/core/api-client.d.ts +0 -69
- package/core/api-client.js +0 -1482
- package/core/auth.d.ts +0 -98
- package/core/auth.js +0 -737
- package/core/build-orchestrator.js +0 -508
- package/core/build-state.js +0 -612
- package/core/config.d.ts +0 -106
- package/core/config.js +0 -1328
- package/core/context-loader.js +0 -580
- package/core/context.d.ts +0 -61
- package/core/context.js +0 -327
- package/core/entitlements.d.ts +0 -70
- package/core/entitlements.js +0 -322
- package/core/index.d.ts +0 -53
- package/core/index.js +0 -62
- package/core/mcp-config.js +0 -115
- package/core/policies.d.ts +0 -43
- package/core/policies.js +0 -113
- package/core/policy-matrix.js +0 -303
- package/core/project-activity.js +0 -175
- package/core/redaction.d.ts +0 -5
- package/core/redaction.js +0 -63
- package/core/self-update.js +0 -259
- package/core/session.js +0 -353
- package/core/task-extractor.js +0 -1098
- package/core/telemetry.d.ts +0 -55
- package/core/telemetry.js +0 -617
- package/core/tier-enforcement.js +0 -928
- package/core/utils.d.ts +0 -90
- package/core/utils.js +0 -455
- package/core/validation.js +0 -572
- package/mcp/server.d.ts +0 -57
- package/mcp/server.js +0 -264
package/core/build-state.js
DELETED
|
@@ -1,612 +0,0 @@
|
|
|
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
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
|
|
14
|
-
const BUILD_STATE_FILE = 'BUILD_STATE.json';
|
|
15
|
-
const PLANNING_DIR = 'planning';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Default build state schema
|
|
19
|
-
*/
|
|
20
|
-
function getDefaultState(projectName = 'Project') {
|
|
21
|
-
return {
|
|
22
|
-
version: '1.0.0',
|
|
23
|
-
projectName,
|
|
24
|
-
status: 'pending', // pending, in_progress, paused, completed, failed
|
|
25
|
-
currentPhase: null,
|
|
26
|
-
|
|
27
|
-
phases: {
|
|
28
|
-
foundation: { status: 'pending', tasks: [] },
|
|
29
|
-
mvp: { status: 'pending', tasks: [] },
|
|
30
|
-
launch: { status: 'pending', tasks: [] }
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
implementationQueue: [],
|
|
34
|
-
|
|
35
|
-
mvpCriteria: {
|
|
36
|
-
features: [],
|
|
37
|
-
completionPercentage: 0,
|
|
38
|
-
allCriteriaMet: false
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
loopSession: {
|
|
42
|
-
sessionId: null,
|
|
43
|
-
currentIteration: 0,
|
|
44
|
-
maxIterations: 50,
|
|
45
|
-
startedAt: null,
|
|
46
|
-
lastUpdated: null,
|
|
47
|
-
pausedAt: null
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
metadata: {
|
|
51
|
-
createdAt: new Date().toISOString(),
|
|
52
|
-
updatedAt: new Date().toISOString(),
|
|
53
|
-
seedSource: null,
|
|
54
|
-
preseedDocs: []
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get the planning directory path
|
|
61
|
-
* @param {string} projectRoot - Project root path
|
|
62
|
-
* @returns {string} Planning directory path
|
|
63
|
-
*/
|
|
64
|
-
function getPlanningDir(projectRoot) {
|
|
65
|
-
return path.join(projectRoot, PLANNING_DIR);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get the build state file path
|
|
70
|
-
* @param {string} projectRoot - Project root path
|
|
71
|
-
* @returns {string} Build state file path
|
|
72
|
-
*/
|
|
73
|
-
function getStatePath(projectRoot) {
|
|
74
|
-
return path.join(getPlanningDir(projectRoot), BUILD_STATE_FILE);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Check if build state exists
|
|
79
|
-
* @param {string} projectRoot - Project root path
|
|
80
|
-
* @returns {boolean} Whether build state exists
|
|
81
|
-
*/
|
|
82
|
-
function exists(projectRoot) {
|
|
83
|
-
return fs.existsSync(getStatePath(projectRoot));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Initialize a new build state
|
|
88
|
-
* @param {string} projectRoot - Project root path
|
|
89
|
-
* @param {object} config - Initial configuration
|
|
90
|
-
* @returns {object} Initialized build state
|
|
91
|
-
*/
|
|
92
|
-
function initialize(projectRoot, config = {}) {
|
|
93
|
-
const planningDir = getPlanningDir(projectRoot);
|
|
94
|
-
|
|
95
|
-
// Create planning directory if it doesn't exist
|
|
96
|
-
if (!fs.existsSync(planningDir)) {
|
|
97
|
-
fs.mkdirSync(planningDir, { recursive: true });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const state = getDefaultState(config.projectName || 'Project');
|
|
101
|
-
|
|
102
|
-
// Apply config overrides
|
|
103
|
-
if (config.phases) {
|
|
104
|
-
state.phases = { ...state.phases, ...config.phases };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (config.implementationQueue) {
|
|
108
|
-
state.implementationQueue = config.implementationQueue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (config.mvpCriteria) {
|
|
112
|
-
state.mvpCriteria = { ...state.mvpCriteria, ...config.mvpCriteria };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (config.maxIterations) {
|
|
116
|
-
state.loopSession.maxIterations = config.maxIterations;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (config.seedSource) {
|
|
120
|
-
state.metadata.seedSource = config.seedSource;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (config.preseedDocs) {
|
|
124
|
-
state.metadata.preseedDocs = config.preseedDocs;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Generate session ID
|
|
128
|
-
state.loopSession.sessionId = generateSessionId();
|
|
129
|
-
|
|
130
|
-
// Save initial state
|
|
131
|
-
save(projectRoot, state);
|
|
132
|
-
|
|
133
|
-
return state;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Generate a unique session ID
|
|
138
|
-
* @returns {string} Session ID
|
|
139
|
-
*/
|
|
140
|
-
function generateSessionId() {
|
|
141
|
-
return `build-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Load build state from disk
|
|
146
|
-
* @param {string} projectRoot - Project root path
|
|
147
|
-
* @returns {object|null} Build state or null if not found
|
|
148
|
-
*/
|
|
149
|
-
function load(projectRoot) {
|
|
150
|
-
const statePath = getStatePath(projectRoot);
|
|
151
|
-
|
|
152
|
-
if (!fs.existsSync(statePath)) {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const content = fs.readFileSync(statePath, 'utf-8');
|
|
158
|
-
return JSON.parse(content);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.error(`Failed to load build state: ${error.message}`);
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Save build state to disk
|
|
167
|
-
* @param {string} projectRoot - Project root path
|
|
168
|
-
* @param {object} state - Build state to save
|
|
169
|
-
* @returns {boolean} Success
|
|
170
|
-
*/
|
|
171
|
-
function save(projectRoot, state) {
|
|
172
|
-
const statePath = getStatePath(projectRoot);
|
|
173
|
-
const planningDir = getPlanningDir(projectRoot);
|
|
174
|
-
|
|
175
|
-
// Ensure planning directory exists
|
|
176
|
-
if (!fs.existsSync(planningDir)) {
|
|
177
|
-
fs.mkdirSync(planningDir, { recursive: true });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Update timestamp
|
|
181
|
-
state.metadata.updatedAt = new Date().toISOString();
|
|
182
|
-
state.loopSession.lastUpdated = new Date().toISOString();
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
186
|
-
return true;
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error(`Failed to save build state: ${error.message}`);
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Update task progress
|
|
195
|
-
* @param {string} projectRoot - Project root path
|
|
196
|
-
* @param {string} taskId - Task ID
|
|
197
|
-
* @param {string} status - New status (pending, in_progress, completed, blocked, skipped)
|
|
198
|
-
* @param {object} details - Additional details
|
|
199
|
-
* @returns {object|null} Updated state or null on failure
|
|
200
|
-
*/
|
|
201
|
-
function updateProgress(projectRoot, taskId, status, details = {}) {
|
|
202
|
-
const state = load(projectRoot);
|
|
203
|
-
if (!state) return null;
|
|
204
|
-
|
|
205
|
-
// Find task in queue
|
|
206
|
-
const taskIndex = state.implementationQueue.findIndex(t => t.id === taskId);
|
|
207
|
-
if (taskIndex === -1) return null;
|
|
208
|
-
|
|
209
|
-
// Update task status
|
|
210
|
-
state.implementationQueue[taskIndex].status = status;
|
|
211
|
-
state.implementationQueue[taskIndex].updatedAt = new Date().toISOString();
|
|
212
|
-
|
|
213
|
-
// Add any additional details
|
|
214
|
-
if (details.completedAt) {
|
|
215
|
-
state.implementationQueue[taskIndex].completedAt = details.completedAt;
|
|
216
|
-
}
|
|
217
|
-
if (details.error) {
|
|
218
|
-
state.implementationQueue[taskIndex].error = details.error;
|
|
219
|
-
}
|
|
220
|
-
if (details.output) {
|
|
221
|
-
state.implementationQueue[taskIndex].lastOutput = details.output;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Update phase progress
|
|
225
|
-
const task = state.implementationQueue[taskIndex];
|
|
226
|
-
if (task.phase && state.phases[task.phase]) {
|
|
227
|
-
updatePhaseProgress(state, task.phase);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Update MVP criteria progress
|
|
231
|
-
updateMvpProgress(state);
|
|
232
|
-
|
|
233
|
-
// Update iteration count if completing
|
|
234
|
-
if (status === 'completed' || status === 'skipped') {
|
|
235
|
-
state.loopSession.currentIteration++;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Save updated state
|
|
239
|
-
const saved = save(projectRoot, state);
|
|
240
|
-
if (!saved) return null;
|
|
241
|
-
|
|
242
|
-
return state;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Update phase progress based on task statuses
|
|
247
|
-
* @param {object} state - Build state
|
|
248
|
-
* @param {string} phaseName - Phase name
|
|
249
|
-
*/
|
|
250
|
-
function updatePhaseProgress(state, phaseName) {
|
|
251
|
-
const phaseTasks = state.implementationQueue.filter(t => t.phase === phaseName);
|
|
252
|
-
const completedTasks = phaseTasks.filter(t => t.status === 'completed');
|
|
253
|
-
|
|
254
|
-
if (phaseTasks.length === 0) return;
|
|
255
|
-
|
|
256
|
-
const progress = completedTasks.length / phaseTasks.length;
|
|
257
|
-
|
|
258
|
-
if (progress === 0) {
|
|
259
|
-
state.phases[phaseName].status = 'pending';
|
|
260
|
-
} else if (progress === 1) {
|
|
261
|
-
state.phases[phaseName].status = 'completed';
|
|
262
|
-
} else {
|
|
263
|
-
state.phases[phaseName].status = 'in_progress';
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
state.phases[phaseName].tasks = phaseTasks.map(t => ({
|
|
267
|
-
id: t.id,
|
|
268
|
-
title: t.title,
|
|
269
|
-
status: t.status
|
|
270
|
-
}));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Update MVP criteria progress
|
|
275
|
-
* @param {object} state - Build state
|
|
276
|
-
*/
|
|
277
|
-
function updateMvpProgress(state) {
|
|
278
|
-
if (!state.mvpCriteria || typeof state.mvpCriteria !== 'object') {
|
|
279
|
-
state.mvpCriteria = { features: [], completionPercentage: 0, allCriteriaMet: false };
|
|
280
|
-
}
|
|
281
|
-
if (!Array.isArray(state.mvpCriteria.features)) {
|
|
282
|
-
state.mvpCriteria.features = [];
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const mvpTasks = state.implementationQueue.filter(t => t.phase === 'mvp');
|
|
286
|
-
const completedMvpTasks = mvpTasks.filter(t => t.status === 'completed');
|
|
287
|
-
|
|
288
|
-
if (mvpTasks.length > 0) {
|
|
289
|
-
state.mvpCriteria.completionPercentage = Math.round(
|
|
290
|
-
(completedMvpTasks.length / mvpTasks.length) * 100
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Update feature completion status
|
|
295
|
-
const safeFeatures = (state.mvpCriteria.features || []).filter(feature =>
|
|
296
|
-
feature &&
|
|
297
|
-
typeof feature.name === 'string' &&
|
|
298
|
-
feature.name.trim().length > 0
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
state.mvpCriteria.features = safeFeatures.map(feature => {
|
|
302
|
-
const featureName = feature.name.trim();
|
|
303
|
-
const relatedTasks = mvpTasks.filter(t =>
|
|
304
|
-
t.title.toLowerCase().includes(featureName.toLowerCase()) ||
|
|
305
|
-
t.sourceSection?.toLowerCase().includes(featureName.toLowerCase())
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
const allComplete = relatedTasks.length > 0 &&
|
|
309
|
-
relatedTasks.every(t => t.status === 'completed');
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
...feature,
|
|
313
|
-
name: featureName,
|
|
314
|
-
status: allComplete ? 'completed' : 'pending'
|
|
315
|
-
};
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// Check if all MVP criteria are met
|
|
319
|
-
const hasMvpTasks = mvpTasks.length > 0;
|
|
320
|
-
const hasMvpFeatures = state.mvpCriteria.features.length > 0;
|
|
321
|
-
const featuresComplete = hasMvpFeatures && state.mvpCriteria.features.every(f => f.status === 'completed');
|
|
322
|
-
|
|
323
|
-
if (hasMvpTasks) {
|
|
324
|
-
state.mvpCriteria.allCriteriaMet =
|
|
325
|
-
state.mvpCriteria.completionPercentage === 100 &&
|
|
326
|
-
(!hasMvpFeatures || featuresComplete);
|
|
327
|
-
} else {
|
|
328
|
-
state.mvpCriteria.allCriteriaMet = featuresComplete;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Get the next pending task
|
|
334
|
-
* @param {string} projectRoot - Project root path
|
|
335
|
-
* @returns {object|null} Next task or null if none
|
|
336
|
-
*/
|
|
337
|
-
function getNextTask(projectRoot) {
|
|
338
|
-
const state = load(projectRoot);
|
|
339
|
-
if (!state) return null;
|
|
340
|
-
|
|
341
|
-
// First, check for any in_progress tasks
|
|
342
|
-
const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
|
|
343
|
-
if (inProgress) return inProgress;
|
|
344
|
-
|
|
345
|
-
// Then get next pending task (respecting dependencies)
|
|
346
|
-
const pendingTasks = state.implementationQueue.filter(t => t.status === 'pending');
|
|
347
|
-
|
|
348
|
-
for (const task of pendingTasks) {
|
|
349
|
-
// Check if all dependencies are completed
|
|
350
|
-
if (!task.dependencies || task.dependencies.length === 0) {
|
|
351
|
-
return task;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const depsCompleted = task.dependencies.every(depId => {
|
|
355
|
-
const depTask = state.implementationQueue.find(t => t.id === depId);
|
|
356
|
-
return depTask && depTask.status === 'completed';
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
if (depsCompleted) {
|
|
360
|
-
return task;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Add a task to the implementation queue
|
|
369
|
-
* @param {string} projectRoot - Project root path
|
|
370
|
-
* @param {object} task - Task to add
|
|
371
|
-
* @returns {object|null} Updated state or null on failure
|
|
372
|
-
*/
|
|
373
|
-
function addTask(projectRoot, task) {
|
|
374
|
-
const state = load(projectRoot);
|
|
375
|
-
if (!state) return null;
|
|
376
|
-
|
|
377
|
-
// Generate task ID if not provided
|
|
378
|
-
if (!task.id) {
|
|
379
|
-
const count = state.implementationQueue.length + 1;
|
|
380
|
-
task.id = `task-${count.toString().padStart(3, '0')}`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Set defaults
|
|
384
|
-
task.status = task.status || 'pending';
|
|
385
|
-
task.createdAt = new Date().toISOString();
|
|
386
|
-
|
|
387
|
-
state.implementationQueue.push(task);
|
|
388
|
-
save(projectRoot, state);
|
|
389
|
-
|
|
390
|
-
return state;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Set tasks in batch
|
|
395
|
-
* @param {string} projectRoot - Project root path
|
|
396
|
-
* @param {array} tasks - Tasks to set
|
|
397
|
-
* @returns {object|null} Updated state or null on failure
|
|
398
|
-
*/
|
|
399
|
-
function setTasks(projectRoot, tasks) {
|
|
400
|
-
const state = load(projectRoot);
|
|
401
|
-
if (!state) return null;
|
|
402
|
-
|
|
403
|
-
// Process tasks with defaults
|
|
404
|
-
state.implementationQueue = tasks.map((task, index) => ({
|
|
405
|
-
id: task.id || `task-${(index + 1).toString().padStart(3, '0')}`,
|
|
406
|
-
title: task.title,
|
|
407
|
-
description: task.description || '',
|
|
408
|
-
source: task.source || 'manual',
|
|
409
|
-
sourceSection: task.sourceSection || null,
|
|
410
|
-
phase: task.phase || 'mvp',
|
|
411
|
-
status: task.status || 'pending',
|
|
412
|
-
acceptanceCriteria: task.acceptanceCriteria || [],
|
|
413
|
-
dependencies: task.dependencies || [],
|
|
414
|
-
estimatedComplexity: task.estimatedComplexity || 'medium',
|
|
415
|
-
createdAt: new Date().toISOString()
|
|
416
|
-
}));
|
|
417
|
-
|
|
418
|
-
save(projectRoot, state);
|
|
419
|
-
return state;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Set MVP features
|
|
424
|
-
* @param {string} projectRoot - Project root path
|
|
425
|
-
* @param {array} features - MVP features
|
|
426
|
-
* @returns {object|null} Updated state or null on failure
|
|
427
|
-
*/
|
|
428
|
-
function setMvpFeatures(projectRoot, features) {
|
|
429
|
-
const state = load(projectRoot);
|
|
430
|
-
if (!state) return null;
|
|
431
|
-
|
|
432
|
-
state.mvpCriteria.features = features.map(f => ({
|
|
433
|
-
name: typeof f === 'string' ? f : f.name,
|
|
434
|
-
status: 'pending'
|
|
435
|
-
}));
|
|
436
|
-
|
|
437
|
-
save(projectRoot, state);
|
|
438
|
-
return state;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Update loop session
|
|
443
|
-
* @param {string} projectRoot - Project root path
|
|
444
|
-
* @param {object} updates - Session updates
|
|
445
|
-
* @returns {object|null} Updated state or null on failure
|
|
446
|
-
*/
|
|
447
|
-
function updateSession(projectRoot, updates) {
|
|
448
|
-
const state = load(projectRoot);
|
|
449
|
-
if (!state) return null;
|
|
450
|
-
|
|
451
|
-
state.loopSession = {
|
|
452
|
-
...state.loopSession,
|
|
453
|
-
...updates
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
save(projectRoot, state);
|
|
457
|
-
return state;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Pause the build loop
|
|
462
|
-
* @param {string} projectRoot - Project root path
|
|
463
|
-
* @returns {object|null} Updated state or null on failure
|
|
464
|
-
*/
|
|
465
|
-
function pause(projectRoot) {
|
|
466
|
-
const state = load(projectRoot);
|
|
467
|
-
if (!state) return null;
|
|
468
|
-
|
|
469
|
-
state.status = 'paused';
|
|
470
|
-
state.loopSession.pausedAt = new Date().toISOString();
|
|
471
|
-
|
|
472
|
-
save(projectRoot, state);
|
|
473
|
-
return state;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Resume the build loop
|
|
478
|
-
* @param {string} projectRoot - Project root path
|
|
479
|
-
* @returns {object|null} Updated state or null on failure
|
|
480
|
-
*/
|
|
481
|
-
function resume(projectRoot) {
|
|
482
|
-
const state = load(projectRoot);
|
|
483
|
-
if (!state) return null;
|
|
484
|
-
|
|
485
|
-
state.status = 'in_progress';
|
|
486
|
-
state.loopSession.pausedAt = null;
|
|
487
|
-
|
|
488
|
-
// Generate new session ID if resuming after long pause
|
|
489
|
-
const pausedAt = state.loopSession.pausedAt ? new Date(state.loopSession.pausedAt) : null;
|
|
490
|
-
const now = new Date();
|
|
491
|
-
if (pausedAt && (now - pausedAt) > 3600000) { // 1 hour
|
|
492
|
-
state.loopSession.sessionId = generateSessionId();
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
save(projectRoot, state);
|
|
496
|
-
return state;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Mark build as complete
|
|
501
|
-
* @param {string} projectRoot - Project root path
|
|
502
|
-
* @returns {object|null} Updated state or null on failure
|
|
503
|
-
*/
|
|
504
|
-
function complete(projectRoot) {
|
|
505
|
-
const state = load(projectRoot);
|
|
506
|
-
if (!state) return null;
|
|
507
|
-
|
|
508
|
-
state.status = 'completed';
|
|
509
|
-
state.loopSession.completedAt = new Date().toISOString();
|
|
510
|
-
|
|
511
|
-
save(projectRoot, state);
|
|
512
|
-
return state;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Mark build as failed
|
|
517
|
-
* @param {string} projectRoot - Project root path
|
|
518
|
-
* @param {string} reason - Failure reason
|
|
519
|
-
* @returns {object|null} Updated state or null on failure
|
|
520
|
-
*/
|
|
521
|
-
function fail(projectRoot, reason) {
|
|
522
|
-
const state = load(projectRoot);
|
|
523
|
-
if (!state) return null;
|
|
524
|
-
|
|
525
|
-
state.status = 'failed';
|
|
526
|
-
state.loopSession.failedAt = new Date().toISOString();
|
|
527
|
-
state.loopSession.failureReason = reason;
|
|
528
|
-
|
|
529
|
-
save(projectRoot, state);
|
|
530
|
-
return state;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Get build statistics
|
|
535
|
-
* @param {string} projectRoot - Project root path
|
|
536
|
-
* @returns {object|null} Statistics or null if no state
|
|
537
|
-
*/
|
|
538
|
-
function getStats(projectRoot) {
|
|
539
|
-
const state = load(projectRoot);
|
|
540
|
-
if (!state) return null;
|
|
541
|
-
const mvpFeatures = Array.isArray(state.mvpCriteria?.features) ? state.mvpCriteria.features : [];
|
|
542
|
-
|
|
543
|
-
const tasks = state.implementationQueue;
|
|
544
|
-
const completed = tasks.filter(t => t.status === 'completed').length;
|
|
545
|
-
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
546
|
-
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
|
547
|
-
const blocked = tasks.filter(t => t.status === 'blocked').length;
|
|
548
|
-
const skipped = tasks.filter(t => t.status === 'skipped').length;
|
|
549
|
-
const mvpTasks = tasks.filter(t => t.phase === 'mvp');
|
|
550
|
-
const completedMvpTasks = mvpTasks.filter(t => t.status === 'completed').length;
|
|
551
|
-
const computedMvpProgress = mvpTasks.length > 0
|
|
552
|
-
? Math.round((completedMvpTasks / mvpTasks.length) * 100)
|
|
553
|
-
: state.mvpCriteria.completionPercentage;
|
|
554
|
-
const computedMvpComplete = mvpTasks.length > 0
|
|
555
|
-
? computedMvpProgress === 100
|
|
556
|
-
: (mvpFeatures.length > 0 && mvpFeatures.every(f => f.status === 'completed'));
|
|
557
|
-
const completedPercent = tasks.length > 0 ? (completed / tasks.length) * 100 : 0;
|
|
558
|
-
|
|
559
|
-
return {
|
|
560
|
-
total: tasks.length,
|
|
561
|
-
completed,
|
|
562
|
-
pending,
|
|
563
|
-
inProgress,
|
|
564
|
-
blocked,
|
|
565
|
-
skipped,
|
|
566
|
-
progress: Math.round(completedPercent),
|
|
567
|
-
completedPercent,
|
|
568
|
-
currentPhase: state.currentPhase,
|
|
569
|
-
mvpProgress: computedMvpProgress,
|
|
570
|
-
mvpComplete: computedMvpComplete,
|
|
571
|
-
iteration: state.loopSession.currentIteration,
|
|
572
|
-
maxIterations: state.loopSession.maxIterations,
|
|
573
|
-
status: state.status
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Reset build state
|
|
579
|
-
* @param {string} projectRoot - Project root path
|
|
580
|
-
* @returns {object} Fresh state
|
|
581
|
-
*/
|
|
582
|
-
function reset(projectRoot) {
|
|
583
|
-
const state = load(projectRoot);
|
|
584
|
-
const projectName = state?.projectName || 'Project';
|
|
585
|
-
|
|
586
|
-
return initialize(projectRoot, { projectName });
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
module.exports = {
|
|
590
|
-
BUILD_STATE_FILE,
|
|
591
|
-
PLANNING_DIR,
|
|
592
|
-
getDefaultState,
|
|
593
|
-
getPlanningDir,
|
|
594
|
-
getStatePath,
|
|
595
|
-
exists,
|
|
596
|
-
initialize,
|
|
597
|
-
generateSessionId,
|
|
598
|
-
load,
|
|
599
|
-
save,
|
|
600
|
-
updateProgress,
|
|
601
|
-
getNextTask,
|
|
602
|
-
addTask,
|
|
603
|
-
setTasks,
|
|
604
|
-
setMvpFeatures,
|
|
605
|
-
updateSession,
|
|
606
|
-
pause,
|
|
607
|
-
resume,
|
|
608
|
-
complete,
|
|
609
|
-
fail,
|
|
610
|
-
getStats,
|
|
611
|
-
reset
|
|
612
|
-
};
|
package/core/config.d.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bootspring Configuration Types
|
|
3
|
-
* @module core/config
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface ProjectConfig {
|
|
7
|
-
name: string;
|
|
8
|
-
description: string;
|
|
9
|
-
version: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface StackConfig {
|
|
13
|
-
framework: string;
|
|
14
|
-
language: string;
|
|
15
|
-
database: string;
|
|
16
|
-
hosting: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface PluginConfig {
|
|
20
|
-
enabled: boolean;
|
|
21
|
-
provider?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface PluginsConfig {
|
|
25
|
-
auth: PluginConfig;
|
|
26
|
-
payments: PluginConfig;
|
|
27
|
-
database: PluginConfig;
|
|
28
|
-
testing: PluginConfig;
|
|
29
|
-
security: PluginConfig;
|
|
30
|
-
ai: PluginConfig;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface DashboardConfig {
|
|
34
|
-
port: number;
|
|
35
|
-
autoOpen: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface QualityConfig {
|
|
39
|
-
preCommit: boolean;
|
|
40
|
-
prePush: boolean;
|
|
41
|
-
strictMode: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface PathsConfig {
|
|
45
|
-
context: string;
|
|
46
|
-
config: string;
|
|
47
|
-
todo: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface BootspringConfig {
|
|
51
|
-
project: ProjectConfig;
|
|
52
|
-
stack: StackConfig;
|
|
53
|
-
plugins: PluginsConfig;
|
|
54
|
-
dashboard: DashboardConfig;
|
|
55
|
-
quality: QualityConfig;
|
|
56
|
-
paths: PathsConfig;
|
|
57
|
-
/** Internal: project root path */
|
|
58
|
-
_projectRoot?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Default configuration values */
|
|
62
|
-
export const DEFAULT_CONFIG: BootspringConfig;
|
|
63
|
-
|
|
64
|
-
/** Config file names to search for */
|
|
65
|
-
export const CONFIG_FILES: string[];
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Load configuration from bootspring.config.js
|
|
69
|
-
* @param projectRoot - Optional project root path
|
|
70
|
-
* @returns Loaded configuration merged with defaults
|
|
71
|
-
*/
|
|
72
|
-
export function load(projectRoot?: string): BootspringConfig;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Save configuration to bootspring.config.js
|
|
76
|
-
* @param config - Configuration to save
|
|
77
|
-
* @param targetPath - Path to save to
|
|
78
|
-
* @returns True if saved successfully
|
|
79
|
-
*/
|
|
80
|
-
export function save(config: Partial<BootspringConfig>, targetPath: string): boolean;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Validate configuration object
|
|
84
|
-
* @param config - Configuration to validate
|
|
85
|
-
* @returns Validation result with errors array
|
|
86
|
-
*/
|
|
87
|
-
export function validate(config: unknown): { valid: boolean; errors: string[] };
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get default configuration
|
|
91
|
-
* @returns Default configuration object
|
|
92
|
-
*/
|
|
93
|
-
export function getDefaults(): BootspringConfig;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Find the project root directory
|
|
97
|
-
* @returns Project root path or null
|
|
98
|
-
*/
|
|
99
|
-
export function findProjectRoot(): string | null;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Find configuration file in directory
|
|
103
|
-
* @param dir - Directory to search
|
|
104
|
-
* @returns Config file path or null
|
|
105
|
-
*/
|
|
106
|
-
export function findConfigFile(dir: string): string | null;
|