@girardmedia/bootspring 2.2.0 → 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.
Files changed (45) hide show
  1. package/bin/bootspring.js +127 -73
  2. package/claude-commands/agent.md +34 -0
  3. package/claude-commands/bs.md +31 -0
  4. package/claude-commands/build.md +25 -0
  5. package/claude-commands/skill.md +31 -0
  6. package/claude-commands/todo.md +25 -0
  7. package/dist/core/index.d.ts +5814 -0
  8. package/dist/core.js +5779 -0
  9. package/dist/index.js +93883 -0
  10. package/dist/mcp/index.d.ts +1 -0
  11. package/dist/mcp-server.js +2298 -0
  12. package/package.json +22 -55
  13. package/core/api-client.d.ts +0 -69
  14. package/core/api-client.js +0 -1482
  15. package/core/auth.d.ts +0 -98
  16. package/core/auth.js +0 -737
  17. package/core/build-orchestrator.js +0 -508
  18. package/core/build-state.js +0 -612
  19. package/core/config.d.ts +0 -106
  20. package/core/config.js +0 -1328
  21. package/core/context-loader.js +0 -580
  22. package/core/context.d.ts +0 -61
  23. package/core/context.js +0 -327
  24. package/core/entitlements.d.ts +0 -70
  25. package/core/entitlements.js +0 -322
  26. package/core/index.d.ts +0 -53
  27. package/core/index.js +0 -62
  28. package/core/mcp-config.js +0 -115
  29. package/core/policies.d.ts +0 -43
  30. package/core/policies.js +0 -113
  31. package/core/policy-matrix.js +0 -303
  32. package/core/project-activity.js +0 -175
  33. package/core/redaction.d.ts +0 -5
  34. package/core/redaction.js +0 -63
  35. package/core/self-update.js +0 -259
  36. package/core/session.js +0 -353
  37. package/core/task-extractor.js +0 -1098
  38. package/core/telemetry.d.ts +0 -55
  39. package/core/telemetry.js +0 -617
  40. package/core/tier-enforcement.js +0 -928
  41. package/core/utils.d.ts +0 -90
  42. package/core/utils.js +0 -455
  43. package/core/validation.js +0 -572
  44. package/mcp/server.d.ts +0 -57
  45. package/mcp/server.js +0 -264
@@ -1,508 +0,0 @@
1
- /**
2
- * Bootspring Build Orchestrator
3
- *
4
- * Main orchestration engine for the autonomous build loop.
5
- * Coordinates between seed documents, task extraction, planning,
6
- * and the continuous build loop.
7
- *
8
- * @package bootspring
9
- * @module core/build-orchestrator
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const buildState = require('./build-state');
15
- const taskExtractor = require('./task-extractor');
16
- const planningTemplate = require('../generators/templates/build-planning.template');
17
-
18
- /**
19
- * Detect if the project root contains an existing codebase
20
- */
21
- function isExistingCodebase(projectRoot) {
22
- const indicators = [
23
- 'package.json', 'Cargo.toml', 'go.mod', 'requirements.txt',
24
- 'pyproject.toml', 'Gemfile', 'pom.xml', 'build.gradle', 'composer.json', '.git'
25
- ];
26
- const srcDirs = ['src', 'lib', 'app', 'pages', 'components'];
27
-
28
- for (const indicator of indicators) {
29
- if (fs.existsSync(path.join(projectRoot, indicator))) return true;
30
- }
31
- for (const dir of srcDirs) {
32
- const dirPath = path.join(projectRoot, dir);
33
- if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) return true;
34
- }
35
- return false;
36
- }
37
-
38
- /**
39
- * Get the appropriate preseed command suggestion
40
- */
41
- function getPreseedSuggestion(projectRoot) {
42
- return isExistingCodebase(projectRoot) ? 'bootspring preseed codebase' : 'bootspring preseed start';
43
- }
44
-
45
- /**
46
- * Build Orchestrator Class
47
- */
48
- class BuildOrchestrator {
49
- /**
50
- * Create a new orchestrator instance
51
- * @param {string} projectRoot - Project root path
52
- * @param {object} options - Options
53
- */
54
- constructor(projectRoot, options = {}) {
55
- this.projectRoot = projectRoot;
56
- this.options = options;
57
- this.state = null;
58
- this.docs = {};
59
- }
60
-
61
- /**
62
- * Initialize the build orchestrator
63
- * Parses docs, extracts tasks, generates planning folder, initializes state
64
- * @returns {object} Initialization result
65
- */
66
- async initialize() {
67
- const result = {
68
- success: false,
69
- state: null,
70
- files: {},
71
- taskCount: 0,
72
- error: null
73
- };
74
-
75
- try {
76
- // Step 1: Load seed documents
77
- this.docs = this.loadSeedDocuments();
78
-
79
- if (Object.keys(this.docs).length === 0) {
80
- const suggestion = getPreseedSuggestion(this.projectRoot);
81
- result.error = `No seed documents found. Run "${suggestion}" first.`;
82
- return result;
83
- }
84
-
85
- // Step 2: Extract project name
86
- const projectName = this.extractProjectName();
87
-
88
- // Step 3: Extract tasks from documents
89
- const extracted = taskExtractor.extractFromDocs(this.docs, {
90
- projectName
91
- });
92
-
93
- if (extracted.tasks.length === 0) {
94
- result.error = 'No tasks could be extracted from seed documents.';
95
- return result;
96
- }
97
-
98
- // Step 4: Initialize build state
99
- this.state = buildState.initialize(this.projectRoot, {
100
- projectName,
101
- preseedDocs: Object.keys(this.docs),
102
- seedSource: this.getSeedSource()
103
- });
104
-
105
- // Step 5: Set tasks in state
106
- buildState.setTasks(this.projectRoot, extracted.tasks);
107
-
108
- // Step 6: Set MVP features/criteria
109
- if (extracted.mvpCriteria.length > 0) {
110
- buildState.setMvpFeatures(this.projectRoot, extracted.mvpCriteria);
111
- }
112
-
113
- // Step 7: Generate planning artifacts
114
- result.files = planningTemplate.generateAll(
115
- this.projectRoot,
116
- this.docs,
117
- extracted.tasks,
118
- {
119
- projectName,
120
- currentPhase: 'foundation'
121
- }
122
- );
123
-
124
- // Step 8: Update state with planning info
125
- this.state = buildState.load(this.projectRoot);
126
- this.state.status = 'pending';
127
- this.state.currentPhase = 'foundation';
128
- buildState.save(this.projectRoot, this.state);
129
-
130
- result.success = true;
131
- result.state = this.state;
132
- result.taskCount = extracted.tasks.length;
133
-
134
- return result;
135
-
136
- } catch (error) {
137
- result.error = error.message;
138
- return result;
139
- }
140
- }
141
-
142
- /**
143
- * Load seed documents from preseed directory and SEED.md
144
- * @returns {object} Document contents
145
- */
146
- loadSeedDocuments() {
147
- const docs = {};
148
-
149
- // Load from preseed directory
150
- const preseedDir = path.join(this.projectRoot, '.bootspring', 'preseed');
151
- if (fs.existsSync(preseedDir)) {
152
- const validDocs = [
153
- 'VISION.md', 'AUDIENCE.md', 'MARKET.md', 'COMPETITORS.md',
154
- 'BUSINESS_MODEL.md', 'PRD.md', 'TECHNICAL_SPEC.md', 'ROADMAP.md'
155
- ];
156
-
157
- for (const file of validDocs) {
158
- const filePath = path.join(preseedDir, file);
159
- if (fs.existsSync(filePath)) {
160
- const docName = file.replace('.md', '');
161
- docs[docName] = fs.readFileSync(filePath, 'utf-8');
162
- }
163
- }
164
- }
165
-
166
- // Load SEED.md if exists
167
- const seedPath = path.join(this.projectRoot, 'SEED.md');
168
- if (fs.existsSync(seedPath)) {
169
- docs.SEED = fs.readFileSync(seedPath, 'utf-8');
170
- }
171
-
172
- return docs;
173
- }
174
-
175
- /**
176
- * Get the seed source type
177
- * @returns {string} Source type
178
- */
179
- getSeedSource() {
180
- const hasPreseed = fs.existsSync(path.join(this.projectRoot, '.bootspring', 'preseed'));
181
- const hasSeed = fs.existsSync(path.join(this.projectRoot, 'SEED.md'));
182
-
183
- if (hasPreseed && hasSeed) return 'preseed+seed';
184
- if (hasPreseed) return 'preseed';
185
- if (hasSeed) return 'seed';
186
- return 'none';
187
- }
188
-
189
- /**
190
- * Extract project name from documents
191
- * @returns {string} Project name
192
- */
193
- extractProjectName() {
194
- // Try VISION first
195
- const visionDoc = this.docs.VISION || this.docs.vision;
196
- if (visionDoc) {
197
- const nameMatch = visionDoc.match(/\*{0,2}Application\s+Name:?\*{0,2}\s*(.+)/i);
198
- if (nameMatch) return nameMatch[1].trim();
199
-
200
- const titleMatch = visionDoc.match(/^#\s+([^—\-\n]+)/m);
201
- if (titleMatch) return titleMatch[1].trim();
202
- }
203
-
204
- // Try SEED
205
- const seedDoc = this.docs.SEED || this.docs.seed;
206
- if (seedDoc) {
207
- const titleMatch = seedDoc.match(/^#\s+([^—\-\n]+)/m);
208
- if (titleMatch) return titleMatch[1].trim();
209
- }
210
-
211
- // Try PRD
212
- const prdDoc = this.docs.PRD || this.docs.prd;
213
- if (prdDoc) {
214
- const titleMatch = prdDoc.match(/^#\s+([^—\-\n]+)/m);
215
- if (titleMatch) return titleMatch[1].trim();
216
- }
217
-
218
- return 'Project';
219
- }
220
-
221
- /**
222
- * Get the next task to execute
223
- * @returns {object|null} Next task or null
224
- */
225
- getNextTask() {
226
- return buildState.getNextTask(this.projectRoot);
227
- }
228
-
229
- /**
230
- * Get all tasks for the loop
231
- * @returns {array} Tasks formatted for loop execution
232
- */
233
- getTasksForLoop() {
234
- const state = buildState.load(this.projectRoot);
235
- if (!state) return [];
236
-
237
- return state.implementationQueue.map(task => ({
238
- id: task.id,
239
- title: task.title,
240
- description: task.description || '',
241
- status: task.status,
242
- acceptance: task.acceptanceCriteria || [],
243
- phase: task.phase,
244
- source: task.source
245
- }));
246
- }
247
-
248
- /**
249
- * Complete a task
250
- * @param {string} taskId - Task ID
251
- * @param {object} details - Completion details
252
- * @returns {object|null} Updated state
253
- */
254
- completeTask(taskId, details = {}) {
255
- return buildState.updateProgress(this.projectRoot, taskId, 'completed', {
256
- completedAt: new Date().toISOString(),
257
- ...details
258
- });
259
- }
260
-
261
- /**
262
- * Mark task as blocked
263
- * @param {string} taskId - Task ID
264
- * @param {string} reason - Block reason
265
- * @returns {object|null} Updated state
266
- */
267
- blockTask(taskId, reason) {
268
- return buildState.updateProgress(this.projectRoot, taskId, 'blocked', {
269
- error: reason
270
- });
271
- }
272
-
273
- /**
274
- * Skip a task
275
- * @param {string} taskId - Task ID
276
- * @param {string} reason - Skip reason
277
- * @returns {object|null} Updated state
278
- */
279
- skipTask(taskId, reason) {
280
- return buildState.updateProgress(this.projectRoot, taskId, 'skipped', {
281
- error: reason
282
- });
283
- }
284
-
285
- /**
286
- * Start a task
287
- * @param {string} taskId - Task ID
288
- * @returns {object|null} Updated state
289
- */
290
- startTask(taskId) {
291
- return buildState.updateProgress(this.projectRoot, taskId, 'in_progress');
292
- }
293
-
294
- /**
295
- * Check if MVP criteria are met
296
- * @returns {boolean} Whether MVP is complete
297
- */
298
- isMvpComplete() {
299
- const state = buildState.load(this.projectRoot);
300
- if (!state) return false;
301
-
302
- return state.mvpCriteria.allCriteriaMet;
303
- }
304
-
305
- /**
306
- * Check if should exit the loop
307
- * @returns {object} Exit check result
308
- */
309
- shouldExit() {
310
- const state = buildState.load(this.projectRoot);
311
- if (!state) return { shouldExit: true, reason: 'no_state' };
312
-
313
- // Check MVP completion
314
- if (this.isMvpComplete()) {
315
- return { shouldExit: true, reason: 'mvp_complete' };
316
- }
317
-
318
- // Check max iterations
319
- if (state.loopSession.currentIteration >= state.loopSession.maxIterations) {
320
- return { shouldExit: true, reason: 'max_iterations' };
321
- }
322
-
323
- // Check if all tasks are done
324
- const pendingTasks = state.implementationQueue.filter(
325
- t => t.status === 'pending' || t.status === 'in_progress'
326
- );
327
- if (pendingTasks.length === 0) {
328
- return { shouldExit: true, reason: 'all_tasks_complete' };
329
- }
330
-
331
- // Check if paused
332
- if (state.status === 'paused') {
333
- return { shouldExit: true, reason: 'paused' };
334
- }
335
-
336
- // Check if failed
337
- if (state.status === 'failed') {
338
- return { shouldExit: true, reason: 'failed' };
339
- }
340
-
341
- return { shouldExit: false };
342
- }
343
-
344
- /**
345
- * Generate a prompt for a specific task
346
- * @param {object} task - Task to generate prompt for
347
- * @returns {string} Task prompt
348
- */
349
- generateTaskPrompt(task) {
350
- const state = buildState.load(this.projectRoot);
351
- const projectName = state?.projectName || 'Project';
352
-
353
- let prompt = `# Bootspring Build Task
354
-
355
- You are executing a single task from the autonomous build loop.
356
-
357
- ## Project: ${projectName}
358
-
359
- ## Current Task
360
-
361
- **ID:** ${task.id}
362
- **Title:** ${task.title}
363
- ${task.description ? `**Description:** ${task.description}` : ''}
364
- **Phase:** ${task.phase || 'MVP'}
365
- **Source:** ${task.source || 'Manual'} ${task.sourceSection ? `(${task.sourceSection})` : ''}
366
- **Complexity:** ${task.estimatedComplexity || 'medium'}
367
-
368
- `;
369
-
370
- if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
371
- prompt += `## Acceptance Criteria
372
-
373
- ${task.acceptanceCriteria.map(c => `- [ ] ${c}`).join('\n')}
374
-
375
- `;
376
- }
377
-
378
- prompt += `## Rules
379
-
380
- 1. **Focus on THIS task only** - Do not work on other tasks
381
- 2. **Read CLAUDE.md first** - Understand project patterns
382
- 3. **Run quality checks** - \`npm run lint && npm run test\`
383
- 4. **Commit when done** - Use conventional commits (feat:, fix:, etc.)
384
- 5. **Update BUILD_STATE.json** - Mark task as completed
385
-
386
- ## Exit Signals
387
-
388
- When you complete the task successfully, output:
389
- \`\`\`
390
- <loop-status>TASK_COMPLETE</loop-status>
391
- \`\`\`
392
-
393
- If you cannot complete the task, output:
394
- \`\`\`
395
- <loop-status>TASK_BLOCKED</loop-status>
396
- Reason: [explanation]
397
- \`\`\`
398
-
399
- ## Session Info
400
-
401
- - Session: ${state?.loopSession?.sessionId || 'N/A'}
402
- - Iteration: ${(state?.loopSession?.currentIteration || 0) + 1} of ${state?.loopSession?.maxIterations || 50}
403
- - Timestamp: ${new Date().toISOString()}
404
-
405
- ## Context Files
406
-
407
- Read these files for context:
408
- - \`CLAUDE.md\` - Project patterns and decisions
409
- - \`planning/CONTEXT.md\` - Build context
410
- - \`planning/BUILD_STATE.json\` - Current state
411
-
412
- Begin working on the task now.
413
- `;
414
-
415
- return prompt;
416
- }
417
-
418
- /**
419
- * Get build statistics
420
- * @returns {object} Build statistics
421
- */
422
- getStats() {
423
- return buildState.getStats(this.projectRoot);
424
- }
425
-
426
- /**
427
- * Pause the build loop
428
- * @returns {object|null} Updated state
429
- */
430
- pause() {
431
- return buildState.pause(this.projectRoot);
432
- }
433
-
434
- /**
435
- * Resume the build loop
436
- * @returns {object|null} Updated state
437
- */
438
- resume() {
439
- return buildState.resume(this.projectRoot);
440
- }
441
-
442
- /**
443
- * Mark build as complete
444
- * @returns {object|null} Updated state
445
- */
446
- complete() {
447
- return buildState.complete(this.projectRoot);
448
- }
449
-
450
- /**
451
- * Mark build as failed
452
- * @param {string} reason - Failure reason
453
- * @returns {object|null} Updated state
454
- */
455
- fail(reason) {
456
- return buildState.fail(this.projectRoot, reason);
457
- }
458
-
459
- /**
460
- * Reset the build state
461
- * @returns {object} Fresh state
462
- */
463
- reset() {
464
- return buildState.reset(this.projectRoot);
465
- }
466
-
467
- /**
468
- * Update planning files from current state
469
- */
470
- updatePlanningFiles() {
471
- const state = buildState.load(this.projectRoot);
472
- if (state) {
473
- planningTemplate.updateFromState(this.projectRoot, state);
474
- }
475
- }
476
-
477
- /**
478
- * Check if build state exists
479
- * @returns {boolean}
480
- */
481
- hasState() {
482
- return buildState.exists(this.projectRoot);
483
- }
484
-
485
- /**
486
- * Load existing state
487
- * @returns {object|null}
488
- */
489
- loadState() {
490
- this.state = buildState.load(this.projectRoot);
491
- return this.state;
492
- }
493
- }
494
-
495
- /**
496
- * Create a new build orchestrator
497
- * @param {string} projectRoot - Project root path
498
- * @param {object} options - Options
499
- * @returns {BuildOrchestrator}
500
- */
501
- function create(projectRoot, options = {}) {
502
- return new BuildOrchestrator(projectRoot, options);
503
- }
504
-
505
- module.exports = {
506
- BuildOrchestrator,
507
- create
508
- };