@girardmedia/bootspring 2.0.7 → 2.0.9

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/cli/build.js CHANGED
@@ -235,7 +235,7 @@ Some tasks may be blocked. Run ${c.cyan}bootspring build status${c.reset} for de
235
235
  }
236
236
 
237
237
  /**
238
- * Build the next task - outputs prompt for Claude Code to execute
238
+ * Build the next task - writes to CURRENT_TASK.md for Claude Code
239
239
  */
240
240
  async function buildNextTask(projectRoot, _args = {}) {
241
241
  const state = buildState.load(projectRoot);
@@ -249,29 +249,88 @@ async function buildNextTask(projectRoot, _args = {}) {
249
249
 
250
250
  if (!nextTask) {
251
251
  console.log(`${c.green}All tasks complete!${c.reset}`);
252
+ clearCurrentTask(projectRoot);
252
253
  return;
253
254
  }
254
255
 
255
256
  // Mark task as in progress
256
257
  buildState.updateProgress(projectRoot, nextTask.id, 'in_progress');
257
258
 
258
- // Generate the task prompt for Claude Code
259
+ // Generate the task prompt
259
260
  const prompt = generateTaskPrompt(nextTask, projectRoot);
260
261
 
262
+ // Write to CURRENT_TASK.md for Claude Code to read
263
+ const taskFile = writeCurrentTask(projectRoot, nextTask, prompt);
264
+
261
265
  console.log(`
262
- ${c.cyan}${c.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
263
- ${c.bold}TASK: ${nextTask.title}${c.reset}
264
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
266
+ ${c.green}${c.bold}✓ Task ready for Claude Code${c.reset}
265
267
 
266
- ${prompt}
268
+ ${c.bold}Task:${c.reset} ${nextTask.title}
269
+ ${c.bold}File:${c.reset} ${path.relative(projectRoot, taskFile)}
270
+
271
+ ${c.cyan}${c.bold}Tell Claude Code:${c.reset}
272
+ "Read planning/CURRENT_TASK.md and implement it"
267
273
 
268
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
269
- ${c.dim}When complete, run: ${c.reset}${c.bold}bootspring build done${c.reset}
270
- ${c.dim}To skip this task: ${c.reset}${c.bold}bootspring build skip${c.reset}
271
- ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
274
+ ${c.dim}Or for the loop, select option 2 to run autonomously.${c.reset}
272
275
  `);
273
276
  }
274
277
 
278
+ /**
279
+ * Write current task to planning/CURRENT_TASK.md
280
+ */
281
+ function writeCurrentTask(projectRoot, task, prompt) {
282
+ const planningDir = path.join(projectRoot, 'planning');
283
+ if (!fs.existsSync(planningDir)) {
284
+ fs.mkdirSync(planningDir, { recursive: true });
285
+ }
286
+
287
+ const taskFile = path.join(planningDir, 'CURRENT_TASK.md');
288
+ const content = `# Current Task
289
+
290
+ > This file is auto-generated by bootspring. Claude Code should read this and implement the task.
291
+
292
+ ---
293
+
294
+ ## ${task.title}
295
+
296
+ ${prompt}
297
+
298
+ ---
299
+
300
+ ## When Complete
301
+
302
+ Run this command to mark the task done and get the next task:
303
+
304
+ \`\`\`bash
305
+ bootspring build done
306
+ \`\`\`
307
+
308
+ Or to skip this task:
309
+
310
+ \`\`\`bash
311
+ bootspring build skip
312
+ \`\`\`
313
+
314
+ ---
315
+
316
+ *Task ID: ${task.id}*
317
+ *Generated: ${new Date().toISOString()}*
318
+ `;
319
+
320
+ fs.writeFileSync(taskFile, content);
321
+ return taskFile;
322
+ }
323
+
324
+ /**
325
+ * Clear the current task file
326
+ */
327
+ function clearCurrentTask(projectRoot) {
328
+ const taskFile = path.join(projectRoot, 'planning', 'CURRENT_TASK.md');
329
+ if (fs.existsSync(taskFile)) {
330
+ fs.unlinkSync(taskFile);
331
+ }
332
+ }
333
+
275
334
  /**
276
335
  * Generate a prompt for Claude Code to execute
277
336
  */
@@ -310,7 +369,7 @@ function generateTaskPrompt(task, projectRoot) {
310
369
  }
311
370
 
312
371
  /**
313
- * Mark current task as done
372
+ * Mark current task as done and auto-queue next
314
373
  */
315
374
  async function markTaskDone(projectRoot, _args = {}) {
316
375
  const state = buildState.load(projectRoot);
@@ -333,19 +392,32 @@ async function markTaskDone(projectRoot, _args = {}) {
333
392
 
334
393
  console.log(`${c.green}✓${c.reset} Completed: ${c.bold}${inProgressTask.title}${c.reset}`);
335
394
 
336
- // Show next task
337
- const nextTask = buildState.getNextTask(projectRoot);
395
+ // Get stats
338
396
  const stats = buildState.getStats(projectRoot);
397
+ console.log(`${c.dim}Progress: ${stats.completed}/${stats.total} tasks (${Math.round(stats.completedPercent)}%)${c.reset}`);
339
398
 
340
- console.log(`
341
- ${c.dim}Progress: ${stats.completed}/${stats.total} tasks (${Math.round(stats.completedPercent)}%)${c.reset}
342
- `);
399
+ // Auto-queue next task
400
+ const nextTask = buildState.getNextTask(projectRoot);
343
401
 
344
402
  if (nextTask) {
345
- console.log(`${c.bold}Next up:${c.reset} ${nextTask.title}`);
346
- console.log(`\n${c.dim}Run ${c.reset}${c.bold}bootspring build${c.reset}${c.dim} to continue${c.reset}\n`);
403
+ // Mark as in progress and write to CURRENT_TASK.md
404
+ buildState.updateProgress(projectRoot, nextTask.id, 'in_progress');
405
+ const prompt = generateTaskPrompt(nextTask, projectRoot);
406
+ const taskFile = writeCurrentTask(projectRoot, nextTask, prompt);
407
+
408
+ console.log(`
409
+ ${c.cyan}${c.bold}Next task ready:${c.reset} ${nextTask.title}
410
+ ${c.dim}File: ${path.relative(projectRoot, taskFile)}${c.reset}
411
+
412
+ ${c.bold}Continue working on planning/CURRENT_TASK.md${c.reset}
413
+ `);
347
414
  } else {
348
- console.log(`${c.green}${c.bold}🎉 All tasks complete!${c.reset}\n`);
415
+ clearCurrentTask(projectRoot);
416
+ console.log(`
417
+ ${c.green}${c.bold}🎉 All tasks complete!${c.reset}
418
+
419
+ Your MVP build is finished.
420
+ `);
349
421
  }
350
422
  }
351
423
 
package/cli/generate.js CHANGED
@@ -11,6 +11,7 @@ const config = require('../core/config');
11
11
  const context = require('../core/context');
12
12
  const utils = require('../core/utils');
13
13
  const gitMemory = require('../intelligence/git-memory');
14
+ const agentsTemplate = require('../generators/templates/agents.template');
14
15
 
15
16
  /**
16
17
  * Generate CLAUDE.md content from config and project state
@@ -283,6 +284,17 @@ ${utils.COLORS.bold}Score:${utils.COLORS.reset} ${validation.score}/${validation
283
284
  utils.writeFile(todoPath, todoContent);
284
285
  todoSpinner.succeed('Generated todo.md');
285
286
  }
287
+
288
+ // Generate AGENTS.md for multi-tool compatibility
289
+ const agentsPath = path.join(cfg._projectRoot, 'AGENTS.md');
290
+ const agentsSpinner = utils.createSpinner('Generating AGENTS.md').start();
291
+ try {
292
+ const agentsContent = agentsTemplate.generate(cfg);
293
+ utils.writeFile(agentsPath, agentsContent);
294
+ agentsSpinner.succeed('Generated AGENTS.md (Codex, Cursor, Amp, Kilo)');
295
+ } catch (error) {
296
+ agentsSpinner.fail(`Failed to generate AGENTS.md: ${error.message}`);
297
+ }
286
298
  }
287
299
 
288
300
  console.log(`
@@ -0,0 +1,192 @@
1
+ /**
2
+ * AGENTS.md Template Generator
3
+ *
4
+ * Generates AGENTS.md following the open standard for AI coding agents.
5
+ * Supported by: OpenAI Codex, Cursor, Amp, Jules (Google), Factory, Kilo Code
6
+ *
7
+ * Best practices from https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/
8
+ *
9
+ * @package bootspring
10
+ * @module generators/templates/agents
11
+ */
12
+
13
+ /**
14
+ * Generate root AGENTS.md content from config
15
+ */
16
+ function generate(config) {
17
+ const sections = [];
18
+
19
+ // Persona - Be specific, not vague
20
+ sections.push(`# ${config.project.name}`);
21
+ sections.push('');
22
+ sections.push(`You are a senior full-stack developer working on ${config.project.name}, a ${formatFramework(config.stack.framework)} application with ${config.stack.language}.`);
23
+ sections.push('');
24
+
25
+ // Commands - Put early
26
+ sections.push('## Commands');
27
+ sections.push('');
28
+ sections.push('```bash');
29
+ sections.push('# Development');
30
+ sections.push('npm run dev');
31
+ sections.push('');
32
+ sections.push('# Quality checks (run before committing)');
33
+ sections.push('npm run lint');
34
+ sections.push('npm run test');
35
+ sections.push('npm run typecheck');
36
+ sections.push('');
37
+ sections.push('# Build tasks');
38
+ sections.push('bootspring build next # Get next task');
39
+ sections.push('bootspring build done # Mark task complete');
40
+ sections.push('bootspring build status # Check progress');
41
+ sections.push('```');
42
+ sections.push('');
43
+
44
+ // Task System
45
+ sections.push('## Task System');
46
+ sections.push('');
47
+ sections.push('This project uses Bootspring for autonomous task management.');
48
+ sections.push('');
49
+ sections.push('1. Check `planning/CURRENT_TASK.md` for the current task');
50
+ sections.push('2. Implement the task following acceptance criteria');
51
+ sections.push('3. Run quality checks');
52
+ sections.push('4. Commit changes');
53
+ sections.push('5. Run `bootspring build done` to get the next task');
54
+ sections.push('');
55
+
56
+ // Stack - Be specific about versions
57
+ sections.push('## Stack');
58
+ sections.push('');
59
+ sections.push(`- **Framework**: ${formatFramework(config.stack.framework)}`);
60
+ sections.push(`- **Language**: ${config.stack.language}`);
61
+ if (config.stack.database !== 'none') {
62
+ sections.push(`- **Database**: ${config.stack.database}`);
63
+ if (config.stack.orm) {
64
+ sections.push(`- **ORM**: ${config.stack.orm}`);
65
+ }
66
+ }
67
+ sections.push('');
68
+
69
+ // Code Style
70
+ sections.push('## Code Style');
71
+ sections.push('');
72
+ if (config.stack.framework === 'nextjs') {
73
+ sections.push('- Use Server Components by default');
74
+ sections.push('- Use Server Actions for mutations (not API routes)');
75
+ }
76
+ sections.push('- Use Zod for input validation');
77
+ sections.push('- Keep files under 300 lines');
78
+ sections.push('- Use meaningful variable and function names');
79
+ sections.push('- Follow existing patterns in the codebase');
80
+ sections.push('');
81
+
82
+ // Git Workflow
83
+ sections.push('## Git Workflow');
84
+ sections.push('');
85
+ sections.push('- Conventional commits: `feat:`, `fix:`, `docs:`, `refactor:`');
86
+ sections.push('- Atomic commits - one logical change per commit');
87
+ sections.push('- Run quality checks before committing');
88
+ sections.push('- Never force push to main');
89
+ sections.push('');
90
+
91
+ // Boundaries - Critical
92
+ sections.push('## Boundaries');
93
+ sections.push('');
94
+ sections.push('**Never:**');
95
+ sections.push('- Commit secrets, API keys, or credentials');
96
+ sections.push('- Modify `.env` files with real values');
97
+ sections.push('- Delete or overwrite migration files');
98
+ sections.push('- Skip tests to make code compile');
99
+ sections.push('- Introduce breaking changes without discussion');
100
+ sections.push('');
101
+ sections.push('**Always:**');
102
+ sections.push('- Read existing code before modifying');
103
+ sections.push('- Run tests after changes');
104
+ sections.push('- Check for TypeScript errors');
105
+ sections.push('- Follow the acceptance criteria exactly');
106
+ sections.push('');
107
+
108
+ // Testing
109
+ sections.push('## Testing');
110
+ sections.push('');
111
+ sections.push('- Write tests for new features');
112
+ sections.push('- Ensure existing tests still pass');
113
+ sections.push('- Test edge cases and error conditions');
114
+ sections.push('');
115
+
116
+ return sections.join('\n');
117
+ }
118
+
119
+ /**
120
+ * Generate planning-specific AGENTS.md for the planning directory
121
+ */
122
+ function generatePlanningAgents(config) {
123
+ const sections = [];
124
+
125
+ sections.push('# Planning Directory');
126
+ sections.push('');
127
+ sections.push('You are working on build tasks for the MVP. This directory contains build planning files.');
128
+ sections.push('');
129
+
130
+ sections.push('## Current Task');
131
+ sections.push('');
132
+ sections.push('Read `CURRENT_TASK.md` for the task you should implement now.');
133
+ sections.push('');
134
+
135
+ sections.push('## Commands');
136
+ sections.push('');
137
+ sections.push('```bash');
138
+ sections.push('bootspring build done # Mark task complete, get next');
139
+ sections.push('bootspring build skip # Skip task, get next');
140
+ sections.push('bootspring build status # View progress');
141
+ sections.push('```');
142
+ sections.push('');
143
+
144
+ sections.push('## Files');
145
+ sections.push('');
146
+ sections.push('| File | Purpose |');
147
+ sections.push('|------|---------|');
148
+ sections.push('| `CURRENT_TASK.md` | Task to implement now |');
149
+ sections.push('| `TODO.md` | Full task checklist |');
150
+ sections.push('| `BUILD_STATE.json` | Build state (do not edit) |');
151
+ sections.push('| `IMPLEMENTATION_QUEUE.md` | Ordered task queue |');
152
+ sections.push('| `CONTEXT.md` | Build context summary |');
153
+ sections.push('');
154
+
155
+ sections.push('## Workflow');
156
+ sections.push('');
157
+ sections.push('1. Read `CURRENT_TASK.md`');
158
+ sections.push('2. Implement in the main codebase (not here)');
159
+ sections.push('3. Ensure acceptance criteria met');
160
+ sections.push('4. Run `npm run lint && npm run test`');
161
+ sections.push('5. Commit changes');
162
+ sections.push('6. Run `bootspring build done`');
163
+ sections.push('');
164
+
165
+ sections.push('## Boundaries');
166
+ sections.push('');
167
+ sections.push('- Do NOT edit `BUILD_STATE.json` directly');
168
+ sections.push('- Do NOT skip quality checks');
169
+ sections.push('- Do NOT work on multiple tasks at once');
170
+ sections.push('');
171
+
172
+ return sections.join('\n');
173
+ }
174
+
175
+ // Helper functions
176
+ function formatFramework(framework) {
177
+ const names = {
178
+ 'nextjs': 'Next.js 14+ (App Router)',
179
+ 'remix': 'Remix',
180
+ 'nuxt': 'Nuxt 3',
181
+ 'sveltekit': 'SvelteKit',
182
+ 'express': 'Express.js',
183
+ 'fastify': 'Fastify',
184
+ 'hono': 'Hono'
185
+ };
186
+ return names[framework] || framework;
187
+ }
188
+
189
+ module.exports = {
190
+ generate,
191
+ generatePlanningAgents
192
+ };
@@ -56,9 +56,72 @@ function generateAll(projectRoot, docs, tasks, options = {}) {
56
56
  fs.writeFileSync(contextPath, context);
57
57
  files.context = contextPath;
58
58
 
59
+ // Generate planning/AGENTS.md for AI coding agents
60
+ const agentsContent = generatePlanningAgents(options);
61
+ const agentsPath = path.join(planningDir, 'AGENTS.md');
62
+ fs.writeFileSync(agentsPath, agentsContent);
63
+ files.agents = agentsPath;
64
+
59
65
  return files;
60
66
  }
61
67
 
68
+ /**
69
+ * Generate AGENTS.md for the planning directory
70
+ * Following the open standard for AI coding agents
71
+ */
72
+ function generatePlanningAgents(options = {}) {
73
+ const projectName = options.projectName || 'Project';
74
+
75
+ return `# Planning Directory
76
+
77
+ You are working on build tasks for ${projectName}. This directory contains build planning files.
78
+
79
+ ## Commands
80
+
81
+ \`\`\`bash
82
+ bootspring build done # Mark task complete, get next
83
+ bootspring build skip # Skip current task
84
+ bootspring build status # View progress
85
+ \`\`\`
86
+
87
+ ## Current Task
88
+
89
+ Read \`CURRENT_TASK.md\` for the task you should implement now.
90
+
91
+ ## Workflow
92
+
93
+ 1. Read \`CURRENT_TASK.md\`
94
+ 2. Implement in the main codebase (not in this folder)
95
+ 3. Ensure acceptance criteria are met
96
+ 4. Run quality checks: \`npm run lint && npm run test\`
97
+ 5. Commit your changes
98
+ 6. Run \`bootspring build done\`
99
+
100
+ ## Files
101
+
102
+ | File | Purpose |
103
+ |------|---------|
104
+ | \`CURRENT_TASK.md\` | Task to implement now |
105
+ | \`TODO.md\` | Full task checklist |
106
+ | \`BUILD_STATE.json\` | Build state (do not edit directly) |
107
+ | \`IMPLEMENTATION_QUEUE.md\` | Ordered task queue |
108
+ | \`CONTEXT.md\` | Build context summary |
109
+
110
+ ## Boundaries
111
+
112
+ **Never:**
113
+ - Edit \`BUILD_STATE.json\` directly
114
+ - Skip quality checks
115
+ - Work on multiple tasks simultaneously
116
+ - Implement tasks in this planning folder
117
+
118
+ **Always:**
119
+ - Follow acceptance criteria exactly
120
+ - Run tests before marking complete
121
+ - Commit with conventional commit format
122
+ `;
123
+ }
124
+
62
125
  /**
63
126
  * Generate MASTER_PLAN.md
64
127
  * @param {object} docs - Source documents
@@ -160,6 +160,36 @@ function generate(config) {
160
160
  sections.push('');
161
161
  }
162
162
 
163
+ // Autonomous Build System
164
+ sections.push('## Autonomous Build System');
165
+ sections.push('');
166
+ sections.push('Bootspring manages an autonomous task queue for building your MVP.');
167
+ sections.push('');
168
+ sections.push('### Check for Tasks');
169
+ sections.push('');
170
+ sections.push('If `planning/CURRENT_TASK.md` exists, read it and implement the task.');
171
+ sections.push('When complete, run `bootspring build done` to get the next task.');
172
+ sections.push('');
173
+ sections.push('### MCP Commands (Claude Code)');
174
+ sections.push('');
175
+ sections.push('```');
176
+ sections.push('bootspring_build action=next # Get next task');
177
+ sections.push('bootspring_build action=done # Mark complete, get next');
178
+ sections.push('bootspring_build action=status # Check progress');
179
+ sections.push('```');
180
+ sections.push('');
181
+ sections.push('### CLI Commands (Terminal)');
182
+ sections.push('');
183
+ sections.push('```bash');
184
+ sections.push('bootspring build # Interactive menu');
185
+ sections.push('bootspring build next # Get next task');
186
+ sections.push('bootspring build done # Mark complete');
187
+ sections.push('bootspring build status # Check progress');
188
+ sections.push('```');
189
+ sections.push('');
190
+ sections.push('---');
191
+ sections.push('');
192
+
163
193
  // Bootspring Commands
164
194
  sections.push('## Bootspring Commands');
165
195
  sections.push('');
@@ -167,12 +197,13 @@ function generate(config) {
167
197
  sections.push('');
168
198
  sections.push('| Command | Description |');
169
199
  sections.push('|---------|-------------|');
200
+ sections.push('| `bootspring_build action=next` | Get next build task |');
201
+ sections.push('| `bootspring_build action=done` | Mark task complete |');
170
202
  sections.push('| `bootspring_assist` | Natural language help |');
171
203
  sections.push('| `bootspring_agent action=list` | List available experts |');
172
204
  sections.push('| `bootspring_todo action=list` | List todos |');
173
205
  sections.push('| `bootspring_skill action=search query="..."` | Find patterns |');
174
206
  sections.push('| `bootspring_quality gate=pre-commit` | Run quality checks |');
175
- sections.push('| `bootspring_orchestrator action=workflow workflow=...` | Start workflow |');
176
207
  sections.push('');
177
208
  sections.push('---');
178
209
  sections.push('');
@@ -7,7 +7,9 @@
7
7
 
8
8
  module.exports = {
9
9
  claude: require('./claude.template'),
10
+ agents: require('./agents.template'),
10
11
  seed: require('./seed.template'),
11
12
  planning: require('./planning.template'),
12
- content: require('./content.template')
13
+ content: require('./content.template'),
14
+ buildPlanning: require('./build-planning.template')
13
15
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "contractVersion": "v1",
3
3
  "packageName": "@girardmedia/bootspring",
4
- "packageVersion": "2.0.7",
4
+ "packageVersion": "2.0.9",
5
5
  "tools": [
6
6
  {
7
7
  "name": "bootspring_assist",
@@ -841,6 +841,39 @@
841
841
  },
842
842
  "required": []
843
843
  }
844
+ },
845
+ {
846
+ "name": "bootspring_build",
847
+ "description": "Autonomous MVP build system. Get tasks, execute them, mark complete.\n\nUse this tool to:\n- Get the current/next task to work on\n- Mark tasks as complete when done\n- Check build progress\n- Run tasks in a loop until MVP is complete\n\nTypical workflow:\n1. action=next → Get the next task\n2. Implement the task in the codebase\n3. action=done → Mark complete and get next task\n4. Repeat until all tasks done",
848
+ "inputSchema": {
849
+ "type": "object",
850
+ "properties": {
851
+ "action": {
852
+ "type": "string",
853
+ "enum": [
854
+ "next",
855
+ "current",
856
+ "done",
857
+ "skip",
858
+ "status",
859
+ "list",
860
+ "init"
861
+ ],
862
+ "description": "Action to perform:\n- next: Get the next pending task (marks it in_progress)\n- current: Get the current in_progress task\n- done: Mark current task complete, get next task\n- skip: Skip current task, get next task\n- status: Get build progress summary\n- list: List all tasks with status\n- init: Initialize build from seed docs"
863
+ },
864
+ "reason": {
865
+ "type": "string",
866
+ "description": "Reason for skipping (only for skip action)"
867
+ },
868
+ "projectRoot": {
869
+ "type": "string",
870
+ "description": "Project root path (auto-detected if not provided)"
871
+ }
872
+ },
873
+ "required": [
874
+ "action"
875
+ ]
876
+ }
844
877
  }
845
878
  ],
846
879
  "resources": [
package/mcp/registry.js CHANGED
@@ -83,6 +83,7 @@ const prdTool = require('./tools/prd-tool');
83
83
  const autopilotTool = require('./tools/autopilot-tool');
84
84
  const contentTool = require('./tools/content-tool');
85
85
  const suggestTool = require('./tools/suggest-tool');
86
+ const buildTool = require('./tools/build-tool');
86
87
 
87
88
  // Natural language and context detection
88
89
  const natural = require('../natural');
@@ -123,7 +124,8 @@ const TOOLS = [
123
124
  prdTool.getToolDefinition(),
124
125
  autopilotTool.getToolDefinition(),
125
126
  contentTool.getToolDefinition(),
126
- suggestTool.getToolDefinition()
127
+ suggestTool.getToolDefinition(),
128
+ buildTool.getToolDefinition()
127
129
  ];
128
130
 
129
131
  const RESOURCES = [
@@ -283,6 +285,9 @@ const toolHandlers = {
283
285
  config,
284
286
  format
285
287
  }),
288
+ bootspring_build: buildTool.createHandler({
289
+ configModule: config
290
+ }),
286
291
  };
287
292
 
288
293
  const resourceHandlers = {
@@ -0,0 +1,504 @@
1
+ /**
2
+ * MCP Build Tool
3
+ *
4
+ * Provides Claude Code with direct access to the bootspring build system.
5
+ * This enables autonomous task execution within Claude Code sessions.
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+
11
+ function getToolDefinition() {
12
+ return {
13
+ name: 'bootspring_build',
14
+ description: `Autonomous MVP build system. Get tasks, execute them, mark complete.
15
+
16
+ Use this tool to:
17
+ - Get the current/next task to work on
18
+ - Mark tasks as complete when done
19
+ - Check build progress
20
+ - Run tasks in a loop until MVP is complete
21
+
22
+ Typical workflow:
23
+ 1. action=next → Get the next task
24
+ 2. Implement the task in the codebase
25
+ 3. action=done → Mark complete and get next task
26
+ 4. Repeat until all tasks done`,
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ action: {
31
+ type: 'string',
32
+ enum: ['next', 'current', 'done', 'skip', 'status', 'list', 'init'],
33
+ description: `Action to perform:
34
+ - next: Get the next pending task (marks it in_progress)
35
+ - current: Get the current in_progress task
36
+ - done: Mark current task complete, get next task
37
+ - skip: Skip current task, get next task
38
+ - status: Get build progress summary
39
+ - list: List all tasks with status
40
+ - init: Initialize build from seed docs`
41
+ },
42
+ reason: {
43
+ type: 'string',
44
+ description: 'Reason for skipping (only for skip action)'
45
+ },
46
+ projectRoot: {
47
+ type: 'string',
48
+ description: 'Project root path (auto-detected if not provided)'
49
+ }
50
+ },
51
+ required: ['action']
52
+ }
53
+ };
54
+ }
55
+
56
+ function createHandler({ configModule }) {
57
+ return async (args) => {
58
+ const { action, reason, projectRoot: providedRoot } = args;
59
+
60
+ // Get project root
61
+ const cfg = configModule.load();
62
+ const projectRoot = providedRoot || cfg._projectRoot || process.cwd();
63
+
64
+ // Load build modules
65
+ const buildState = require('../../core/build-state');
66
+ const { BuildOrchestrator } = require('../../core/build-orchestrator');
67
+
68
+ switch (action) {
69
+ case 'init': {
70
+ // Initialize build from seed docs
71
+ const orchestrator = new BuildOrchestrator(projectRoot);
72
+ await orchestrator.initialize();
73
+ const state = buildState.load(projectRoot);
74
+ const stats = buildState.getStats(projectRoot);
75
+
76
+ return {
77
+ content: [{
78
+ type: 'text',
79
+ text: JSON.stringify({
80
+ success: true,
81
+ message: 'Build initialized from seed documents',
82
+ project: state?.projectName || 'Unknown',
83
+ phase: state?.currentPhase || 'foundation',
84
+ totalTasks: stats?.total || 0,
85
+ planningFolder: path.join(projectRoot, 'planning'),
86
+ nextStep: 'Use action=next to get the first task'
87
+ }, null, 2)
88
+ }]
89
+ };
90
+ }
91
+
92
+ case 'status': {
93
+ const state = buildState.load(projectRoot);
94
+ if (!state) {
95
+ return {
96
+ content: [{
97
+ type: 'text',
98
+ text: JSON.stringify({
99
+ initialized: false,
100
+ message: 'No build state found. Use action=init to initialize.',
101
+ hint: 'Run: bootspring seed build'
102
+ }, null, 2)
103
+ }]
104
+ };
105
+ }
106
+
107
+ const stats = buildState.getStats(projectRoot);
108
+ const currentTask = state.implementationQueue.find(t => t.status === 'in_progress');
109
+
110
+ return {
111
+ content: [{
112
+ type: 'text',
113
+ text: JSON.stringify({
114
+ project: state.projectName,
115
+ phase: state.currentPhase,
116
+ status: state.status,
117
+ progress: {
118
+ completed: stats.completed,
119
+ pending: stats.pending,
120
+ inProgress: stats.inProgress,
121
+ total: stats.total,
122
+ percent: Math.round(stats.completedPercent)
123
+ },
124
+ currentTask: currentTask ? {
125
+ id: currentTask.id,
126
+ title: currentTask.title
127
+ } : null,
128
+ nextAction: currentTask
129
+ ? 'Complete the current task, then use action=done'
130
+ : 'Use action=next to get the next task'
131
+ }, null, 2)
132
+ }]
133
+ };
134
+ }
135
+
136
+ case 'list': {
137
+ const state = buildState.load(projectRoot);
138
+ if (!state) {
139
+ return {
140
+ content: [{
141
+ type: 'text',
142
+ text: JSON.stringify({
143
+ error: 'No build state found',
144
+ hint: 'Use action=init to initialize'
145
+ }, null, 2)
146
+ }]
147
+ };
148
+ }
149
+
150
+ const tasks = state.implementationQueue.map(t => ({
151
+ id: t.id,
152
+ title: t.title,
153
+ status: t.status,
154
+ phase: t.phase
155
+ }));
156
+
157
+ return {
158
+ content: [{
159
+ type: 'text',
160
+ text: JSON.stringify({
161
+ project: state.projectName,
162
+ tasks: tasks
163
+ }, null, 2)
164
+ }]
165
+ };
166
+ }
167
+
168
+ case 'current': {
169
+ const state = buildState.load(projectRoot);
170
+ if (!state) {
171
+ return {
172
+ content: [{
173
+ type: 'text',
174
+ text: JSON.stringify({
175
+ error: 'No build state found',
176
+ hint: 'Use action=init to initialize'
177
+ }, null, 2)
178
+ }]
179
+ };
180
+ }
181
+
182
+ const currentTask = state.implementationQueue.find(t => t.status === 'in_progress');
183
+ if (!currentTask) {
184
+ return {
185
+ content: [{
186
+ type: 'text',
187
+ text: JSON.stringify({
188
+ message: 'No task currently in progress',
189
+ hint: 'Use action=next to get the next task'
190
+ }, null, 2)
191
+ }]
192
+ };
193
+ }
194
+
195
+ return {
196
+ content: [{
197
+ type: 'text',
198
+ text: JSON.stringify({
199
+ task: {
200
+ id: currentTask.id,
201
+ title: currentTask.title,
202
+ description: currentTask.description,
203
+ phase: currentTask.phase,
204
+ source: currentTask.source,
205
+ sourceSection: currentTask.sourceSection,
206
+ acceptanceCriteria: currentTask.acceptanceCriteria || []
207
+ },
208
+ instructions: [
209
+ 'Implement this task in the codebase',
210
+ 'Run quality checks (lint, test)',
211
+ 'Commit your changes',
212
+ 'Use action=done to mark complete'
213
+ ]
214
+ }, null, 2)
215
+ }]
216
+ };
217
+ }
218
+
219
+ case 'next': {
220
+ const state = buildState.load(projectRoot);
221
+ if (!state) {
222
+ return {
223
+ content: [{
224
+ type: 'text',
225
+ text: JSON.stringify({
226
+ error: 'No build state found',
227
+ hint: 'Use action=init to initialize'
228
+ }, null, 2)
229
+ }]
230
+ };
231
+ }
232
+
233
+ // Check if there's already an in-progress task
234
+ const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
235
+ if (inProgress) {
236
+ return {
237
+ content: [{
238
+ type: 'text',
239
+ text: JSON.stringify({
240
+ message: 'Task already in progress',
241
+ task: {
242
+ id: inProgress.id,
243
+ title: inProgress.title,
244
+ description: inProgress.description,
245
+ acceptanceCriteria: inProgress.acceptanceCriteria || []
246
+ },
247
+ hint: 'Complete this task first with action=done, or use action=skip to skip it'
248
+ }, null, 2)
249
+ }]
250
+ };
251
+ }
252
+
253
+ const nextTask = buildState.getNextTask(projectRoot);
254
+ if (!nextTask) {
255
+ const stats = buildState.getStats(projectRoot);
256
+ return {
257
+ content: [{
258
+ type: 'text',
259
+ text: JSON.stringify({
260
+ message: 'All tasks complete!',
261
+ progress: {
262
+ completed: stats.completed,
263
+ total: stats.total
264
+ },
265
+ celebration: 'MVP build finished!'
266
+ }, null, 2)
267
+ }]
268
+ };
269
+ }
270
+
271
+ // Mark as in progress
272
+ buildState.updateProgress(projectRoot, nextTask.id, 'in_progress');
273
+
274
+ // Write to CURRENT_TASK.md
275
+ writeCurrentTaskFile(projectRoot, nextTask);
276
+
277
+ return {
278
+ content: [{
279
+ type: 'text',
280
+ text: JSON.stringify({
281
+ task: {
282
+ id: nextTask.id,
283
+ title: nextTask.title,
284
+ description: nextTask.description,
285
+ phase: nextTask.phase,
286
+ source: nextTask.source,
287
+ sourceSection: nextTask.sourceSection,
288
+ acceptanceCriteria: nextTask.acceptanceCriteria || []
289
+ },
290
+ file: 'planning/CURRENT_TASK.md',
291
+ instructions: [
292
+ 'Read the task details above',
293
+ 'Implement the task in the codebase',
294
+ 'Ensure acceptance criteria are met',
295
+ 'Run quality checks (lint, test)',
296
+ 'Commit changes with descriptive message',
297
+ 'Use action=done when complete'
298
+ ]
299
+ }, null, 2)
300
+ }]
301
+ };
302
+ }
303
+
304
+ case 'done': {
305
+ const state = buildState.load(projectRoot);
306
+ if (!state) {
307
+ return {
308
+ content: [{
309
+ type: 'text',
310
+ text: JSON.stringify({
311
+ error: 'No build state found'
312
+ }, null, 2)
313
+ }]
314
+ };
315
+ }
316
+
317
+ const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
318
+ if (!inProgress) {
319
+ return {
320
+ content: [{
321
+ type: 'text',
322
+ text: JSON.stringify({
323
+ error: 'No task currently in progress',
324
+ hint: 'Use action=next to get a task first'
325
+ }, null, 2)
326
+ }]
327
+ };
328
+ }
329
+
330
+ // Mark complete
331
+ buildState.updateProgress(projectRoot, inProgress.id, 'completed');
332
+
333
+ const stats = buildState.getStats(projectRoot);
334
+ const nextTask = buildState.getNextTask(projectRoot);
335
+
336
+ // Auto-queue next task
337
+ if (nextTask) {
338
+ buildState.updateProgress(projectRoot, nextTask.id, 'in_progress');
339
+ writeCurrentTaskFile(projectRoot, nextTask);
340
+ } else {
341
+ clearCurrentTaskFile(projectRoot);
342
+ }
343
+
344
+ return {
345
+ content: [{
346
+ type: 'text',
347
+ text: JSON.stringify({
348
+ completed: {
349
+ id: inProgress.id,
350
+ title: inProgress.title
351
+ },
352
+ progress: {
353
+ completed: stats.completed,
354
+ total: stats.total,
355
+ percent: Math.round(stats.completedPercent)
356
+ },
357
+ nextTask: nextTask ? {
358
+ id: nextTask.id,
359
+ title: nextTask.title,
360
+ description: nextTask.description,
361
+ acceptanceCriteria: nextTask.acceptanceCriteria || []
362
+ } : null,
363
+ allComplete: !nextTask,
364
+ message: nextTask
365
+ ? `Task complete! Next: ${nextTask.title}`
366
+ : 'All tasks complete! MVP build finished!'
367
+ }, null, 2)
368
+ }]
369
+ };
370
+ }
371
+
372
+ case 'skip': {
373
+ const state = buildState.load(projectRoot);
374
+ if (!state) {
375
+ return {
376
+ content: [{
377
+ type: 'text',
378
+ text: JSON.stringify({
379
+ error: 'No build state found'
380
+ }, null, 2)
381
+ }]
382
+ };
383
+ }
384
+
385
+ const inProgress = state.implementationQueue.find(t => t.status === 'in_progress');
386
+ if (!inProgress) {
387
+ return {
388
+ content: [{
389
+ type: 'text',
390
+ text: JSON.stringify({
391
+ error: 'No task to skip',
392
+ hint: 'Use action=next to get a task first'
393
+ }, null, 2)
394
+ }]
395
+ };
396
+ }
397
+
398
+ // Mark as skipped (back to pending with skip flag)
399
+ buildState.updateProgress(projectRoot, inProgress.id, 'skipped');
400
+
401
+ const nextTask = buildState.getNextTask(projectRoot);
402
+
403
+ if (nextTask) {
404
+ buildState.updateProgress(projectRoot, nextTask.id, 'in_progress');
405
+ writeCurrentTaskFile(projectRoot, nextTask);
406
+ }
407
+
408
+ return {
409
+ content: [{
410
+ type: 'text',
411
+ text: JSON.stringify({
412
+ skipped: {
413
+ id: inProgress.id,
414
+ title: inProgress.title,
415
+ reason: reason || 'Skipped by user'
416
+ },
417
+ nextTask: nextTask ? {
418
+ id: nextTask.id,
419
+ title: nextTask.title,
420
+ description: nextTask.description
421
+ } : null,
422
+ message: nextTask
423
+ ? `Skipped. Next: ${nextTask.title}`
424
+ : 'No more tasks available'
425
+ }, null, 2)
426
+ }]
427
+ };
428
+ }
429
+
430
+ default:
431
+ return {
432
+ content: [{
433
+ type: 'text',
434
+ text: JSON.stringify({
435
+ error: `Unknown action: ${action}`,
436
+ validActions: ['next', 'current', 'done', 'skip', 'status', 'list', 'init']
437
+ }, null, 2)
438
+ }]
439
+ };
440
+ }
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Write task to planning/CURRENT_TASK.md
446
+ */
447
+ function writeCurrentTaskFile(projectRoot, task) {
448
+ const planningDir = path.join(projectRoot, 'planning');
449
+ if (!fs.existsSync(planningDir)) {
450
+ fs.mkdirSync(planningDir, { recursive: true });
451
+ }
452
+
453
+ const content = `# Current Task
454
+
455
+ > Auto-generated by bootspring. Implement this task.
456
+
457
+ ---
458
+
459
+ ## ${task.title}
460
+
461
+ ${task.description || ''}
462
+
463
+ ${task.acceptanceCriteria?.length ? `### Acceptance Criteria
464
+
465
+ ${task.acceptanceCriteria.map(ac => `- [ ] ${ac}`).join('\n')}
466
+ ` : ''}
467
+
468
+ ### Source
469
+
470
+ ${task.source}${task.sourceSection ? ` - ${task.sourceSection}` : ''}
471
+
472
+ ---
473
+
474
+ ## Instructions
475
+
476
+ 1. Implement the task as described above
477
+ 2. Ensure all acceptance criteria are met
478
+ 3. Run quality checks (lint, test)
479
+ 4. Commit your changes
480
+ 5. Mark complete with: \`bootspring build done\` or MCP action=done
481
+
482
+ ---
483
+
484
+ *Task ID: ${task.id}*
485
+ *Generated: ${new Date().toISOString()}*
486
+ `;
487
+
488
+ fs.writeFileSync(path.join(planningDir, 'CURRENT_TASK.md'), content);
489
+ }
490
+
491
+ /**
492
+ * Clear CURRENT_TASK.md
493
+ */
494
+ function clearCurrentTaskFile(projectRoot) {
495
+ const taskFile = path.join(projectRoot, 'planning', 'CURRENT_TASK.md');
496
+ if (fs.existsSync(taskFile)) {
497
+ fs.unlinkSync(taskFile);
498
+ }
499
+ }
500
+
501
+ module.exports = {
502
+ getToolDefinition,
503
+ createHandler
504
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@girardmedia/bootspring",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Development scaffolding with intelligence - AI-powered context, agents, and workflows for any MCP-compatible assistant",
5
5
  "keywords": [
6
6
  "ai",