@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 +91 -19
- package/cli/generate.js +12 -0
- package/generators/templates/agents.template.js +192 -0
- package/generators/templates/build-planning.template.js +63 -0
- package/generators/templates/claude.template.js +32 -1
- package/generators/templates/index.js +3 -1
- package/mcp/contracts/mcp-contract.v1.json +34 -1
- package/mcp/registry.js +6 -1
- package/mcp/tools/build-tool.js +504 -0
- package/package.json +1 -1
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 -
|
|
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
|
|
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.
|
|
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
|
-
${
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
`);
|
|
399
|
+
// Auto-queue next task
|
|
400
|
+
const nextTask = buildState.getNextTask(projectRoot);
|
|
343
401
|
|
|
344
402
|
if (nextTask) {
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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.
|
|
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