@girardmedia/bootspring 2.0.8 → 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/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/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