@girardmedia/bootspring 2.0.8 → 2.0.10
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 +2 -2
- package/cli/generate.js +12 -0
- package/cli/seed.js +171 -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
|
@@ -341,7 +341,7 @@ function generateTaskPrompt(task, projectRoot) {
|
|
|
341
341
|
lines.push('');
|
|
342
342
|
|
|
343
343
|
if (task.description) {
|
|
344
|
-
lines.push(
|
|
344
|
+
lines.push('**Description:**');
|
|
345
345
|
lines.push(task.description);
|
|
346
346
|
lines.push('');
|
|
347
347
|
}
|
|
@@ -363,7 +363,7 @@ function generateTaskPrompt(task, projectRoot) {
|
|
|
363
363
|
lines.push('1. Implement the task as described above');
|
|
364
364
|
lines.push('2. Ensure all acceptance criteria are met');
|
|
365
365
|
lines.push('3. Test your implementation');
|
|
366
|
-
lines.push(
|
|
366
|
+
lines.push('4. When done, run: bootspring build done');
|
|
367
367
|
|
|
368
368
|
return lines.join('\n');
|
|
369
369
|
}
|
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(`
|
package/cli/seed.js
CHANGED
|
@@ -25,6 +25,8 @@ const config = require('../core/config');
|
|
|
25
25
|
const utils = require('../core/utils');
|
|
26
26
|
const { runQuestionnaire } = require('../generators/questionnaire');
|
|
27
27
|
const seedTemplate = require('../generators/templates/seed.template');
|
|
28
|
+
const claudeTemplate = require('../generators/templates/claude.template');
|
|
29
|
+
const agentsTemplate = require('../generators/templates/agents.template');
|
|
28
30
|
const scaffold = require('../core/scaffold');
|
|
29
31
|
const projectState = require('../core/project-state');
|
|
30
32
|
const checkpointEngine = require('../core/checkpoint-engine');
|
|
@@ -39,6 +41,79 @@ function getIngest() {
|
|
|
39
41
|
return ingest;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Generate AI context files (CLAUDE.md and AGENTS.md)
|
|
46
|
+
* @param {string} projectRoot - Project root path
|
|
47
|
+
* @param {object} scaffoldConfig - Configuration object
|
|
48
|
+
* @returns {object} Results of generation
|
|
49
|
+
*/
|
|
50
|
+
function generateAIContextFiles(projectRoot, scaffoldConfig) {
|
|
51
|
+
const results = { claudeMd: false, agentsMd: false, planningAgentsMd: false };
|
|
52
|
+
|
|
53
|
+
// Build a config object suitable for the templates
|
|
54
|
+
const cfg = {
|
|
55
|
+
project: {
|
|
56
|
+
name: scaffoldConfig.project?.name || 'My Project',
|
|
57
|
+
description: scaffoldConfig.project?.description || '',
|
|
58
|
+
version: scaffoldConfig.project?.version || '0.1.0',
|
|
59
|
+
status: 'development'
|
|
60
|
+
},
|
|
61
|
+
stack: scaffoldConfig.stack || {
|
|
62
|
+
framework: 'nextjs',
|
|
63
|
+
language: 'typescript',
|
|
64
|
+
database: 'postgresql',
|
|
65
|
+
hosting: 'vercel'
|
|
66
|
+
},
|
|
67
|
+
frontend: scaffoldConfig.frontend || {
|
|
68
|
+
uiLibrary: 'shadcn',
|
|
69
|
+
styling: 'tailwind'
|
|
70
|
+
},
|
|
71
|
+
backend: scaffoldConfig.backend || {
|
|
72
|
+
orm: 'prisma',
|
|
73
|
+
auth: 'clerk'
|
|
74
|
+
},
|
|
75
|
+
plugins: scaffoldConfig.plugins || {},
|
|
76
|
+
instructions: scaffoldConfig.instructions || {},
|
|
77
|
+
workflow: scaffoldConfig.workflow || {},
|
|
78
|
+
business: scaffoldConfig.business || {}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Generate CLAUDE.md
|
|
82
|
+
try {
|
|
83
|
+
const claudePath = path.join(projectRoot, 'CLAUDE.md');
|
|
84
|
+
const claudeContent = claudeTemplate.generate(cfg);
|
|
85
|
+
fs.writeFileSync(claudePath, claudeContent);
|
|
86
|
+
results.claudeMd = true;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
utils.print.debug(`Failed to generate CLAUDE.md: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Generate AGENTS.md (root level)
|
|
92
|
+
try {
|
|
93
|
+
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
|
94
|
+
const agentsContent = agentsTemplate.generate(cfg);
|
|
95
|
+
fs.writeFileSync(agentsPath, agentsContent);
|
|
96
|
+
results.agentsMd = true;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
utils.print.debug(`Failed to generate AGENTS.md: ${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Generate planning/AGENTS.md if planning folder exists
|
|
102
|
+
const planningDir = path.join(projectRoot, 'planning');
|
|
103
|
+
if (fs.existsSync(planningDir)) {
|
|
104
|
+
try {
|
|
105
|
+
const planningAgentsPath = path.join(planningDir, 'AGENTS.md');
|
|
106
|
+
const planningAgentsContent = agentsTemplate.generatePlanningAgents(cfg);
|
|
107
|
+
fs.writeFileSync(planningAgentsPath, planningAgentsContent);
|
|
108
|
+
results.planningAgentsMd = true;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
utils.print.debug(`Failed to generate planning/AGENTS.md: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
|
|
42
117
|
/**
|
|
43
118
|
* Seed folder structure
|
|
44
119
|
*/
|
|
@@ -382,9 +457,27 @@ ${utils.COLORS.dim}Interactive project configuration${utils.COLORS.reset}
|
|
|
382
457
|
utils.print.debug(`Auto-tagging failed: ${err.message}`);
|
|
383
458
|
}
|
|
384
459
|
|
|
460
|
+
// Generate AI context files
|
|
461
|
+
const aiSpinner = utils.createSpinner('Generating AI context files...').start();
|
|
462
|
+
const aiResults = generateAIContextFiles(projectRoot, projectConfig);
|
|
463
|
+
const aiFilesGenerated = [];
|
|
464
|
+
if (aiResults.claudeMd) aiFilesGenerated.push('CLAUDE.md');
|
|
465
|
+
if (aiResults.agentsMd) aiFilesGenerated.push('AGENTS.md');
|
|
466
|
+
if (aiFilesGenerated.length > 0) {
|
|
467
|
+
aiSpinner.succeed(`Generated ${aiFilesGenerated.join(', ')}`);
|
|
468
|
+
} else {
|
|
469
|
+
aiSpinner.warn('Could not generate AI context files');
|
|
470
|
+
}
|
|
471
|
+
|
|
385
472
|
console.log(`
|
|
386
473
|
${utils.COLORS.green}${utils.COLORS.bold}✓ Seed initialized successfully!${utils.COLORS.reset}
|
|
387
474
|
|
|
475
|
+
${utils.COLORS.bold}Generated Files:${utils.COLORS.reset}
|
|
476
|
+
${utils.COLORS.cyan}SEED.md${utils.COLORS.reset} - Project specification
|
|
477
|
+
${utils.COLORS.cyan}bootspring.config.js${utils.COLORS.reset} - Configuration
|
|
478
|
+
${utils.COLORS.cyan}CLAUDE.md${utils.COLORS.reset} - Claude Code context
|
|
479
|
+
${utils.COLORS.cyan}AGENTS.md${utils.COLORS.reset} - Codex/Cursor/Amp context
|
|
480
|
+
|
|
388
481
|
${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
|
|
389
482
|
1. Review ${utils.COLORS.cyan}SEED.md${utils.COLORS.reset} for your project configuration
|
|
390
483
|
2. Run ${utils.COLORS.cyan}bootspring seed scaffold${utils.COLORS.reset} to generate project structure
|
|
@@ -549,13 +642,33 @@ ${utils.COLORS.bold}Available presets:${utils.COLORS.reset}`);
|
|
|
549
642
|
}
|
|
550
643
|
}
|
|
551
644
|
|
|
645
|
+
// Generate AI context files (CLAUDE.md and AGENTS.md)
|
|
646
|
+
const aiSpinner = utils.createSpinner('Generating AI context files...').start();
|
|
647
|
+
const aiResults = generateAIContextFiles(projectRoot, scaffoldConfig);
|
|
648
|
+
|
|
649
|
+
const aiFilesGenerated = [];
|
|
650
|
+
if (aiResults.claudeMd) aiFilesGenerated.push('CLAUDE.md');
|
|
651
|
+
if (aiResults.agentsMd) aiFilesGenerated.push('AGENTS.md');
|
|
652
|
+
if (aiResults.planningAgentsMd) aiFilesGenerated.push('planning/AGENTS.md');
|
|
653
|
+
|
|
654
|
+
if (aiFilesGenerated.length > 0) {
|
|
655
|
+
aiSpinner.succeed(`Generated ${aiFilesGenerated.join(', ')}`);
|
|
656
|
+
} else {
|
|
657
|
+
aiSpinner.warn('Could not generate AI context files');
|
|
658
|
+
}
|
|
659
|
+
|
|
552
660
|
console.log(`
|
|
553
661
|
${utils.COLORS.green}${utils.COLORS.bold}✓ Scaffold complete!${utils.COLORS.reset}
|
|
554
662
|
|
|
663
|
+
${utils.COLORS.bold}Generated Files:${utils.COLORS.reset}
|
|
664
|
+
${utils.COLORS.cyan}CLAUDE.md${utils.COLORS.reset} - Context for Claude Code
|
|
665
|
+
${utils.COLORS.cyan}AGENTS.md${utils.COLORS.reset} - Context for Codex, Cursor, Amp, Kilo
|
|
666
|
+
|
|
555
667
|
${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
|
|
556
668
|
1. Run ${utils.COLORS.cyan}npm install${utils.COLORS.reset} to install dependencies
|
|
557
669
|
2. Copy ${utils.COLORS.cyan}.env.example${utils.COLORS.reset} to ${utils.COLORS.cyan}.env.local${utils.COLORS.reset}
|
|
558
670
|
3. Run ${utils.COLORS.cyan}npm run dev${utils.COLORS.reset} to start development
|
|
671
|
+
4. Run ${utils.COLORS.cyan}bootspring build${utils.COLORS.reset} to start building tasks
|
|
559
672
|
`);
|
|
560
673
|
} catch (error) {
|
|
561
674
|
spinner.fail(`Scaffold failed: ${error.message}`);
|
|
@@ -807,15 +920,73 @@ ${utils.COLORS.dim}Create SEED.md from your preseed documents${utils.COLORS.rese
|
|
|
807
920
|
fs.writeFileSync(seedPath, seedContent);
|
|
808
921
|
spinner.succeed('Created SEED.md');
|
|
809
922
|
|
|
923
|
+
// Extract tech stack for AI context files
|
|
924
|
+
const techStack = extractTechStack(docs);
|
|
925
|
+
const projectName = extractProjectName(docs) || preseedConfig.identity?.name || 'My Project';
|
|
926
|
+
const tagline = extractTagline(docs) || '';
|
|
927
|
+
|
|
928
|
+
const synthConfig = {
|
|
929
|
+
project: {
|
|
930
|
+
name: projectName,
|
|
931
|
+
description: tagline,
|
|
932
|
+
version: '0.1.0',
|
|
933
|
+
status: 'development'
|
|
934
|
+
},
|
|
935
|
+
stack: {
|
|
936
|
+
framework: techStack.framework,
|
|
937
|
+
language: techStack.language,
|
|
938
|
+
database: techStack.database,
|
|
939
|
+
hosting: techStack.hosting
|
|
940
|
+
},
|
|
941
|
+
frontend: {
|
|
942
|
+
uiLibrary: techStack.uiLibrary,
|
|
943
|
+
styling: techStack.styling
|
|
944
|
+
},
|
|
945
|
+
backend: {
|
|
946
|
+
orm: techStack.orm,
|
|
947
|
+
auth: techStack.auth
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
// Generate AI context files
|
|
952
|
+
const aiSpinner = utils.createSpinner('Generating AI context files...').start();
|
|
953
|
+
const aiResults = generateAIContextFiles(projectRoot, synthConfig);
|
|
954
|
+
const aiFilesGenerated = [];
|
|
955
|
+
if (aiResults.claudeMd) aiFilesGenerated.push('CLAUDE.md');
|
|
956
|
+
if (aiResults.agentsMd) aiFilesGenerated.push('AGENTS.md');
|
|
957
|
+
if (aiFilesGenerated.length > 0) {
|
|
958
|
+
aiSpinner.succeed(`Generated ${aiFilesGenerated.join(', ')}`);
|
|
959
|
+
} else {
|
|
960
|
+
aiSpinner.warn('Could not generate AI context files');
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Also create bootspring.config.js for CLI compatibility
|
|
964
|
+
const configPath = path.join(projectRoot, 'bootspring.config.js');
|
|
965
|
+
if (!fs.existsSync(configPath)) {
|
|
966
|
+
const configSpinner = utils.createSpinner('Creating bootspring.config.js...').start();
|
|
967
|
+
if (config.save(synthConfig, configPath)) {
|
|
968
|
+
configSpinner.succeed('Created bootspring.config.js');
|
|
969
|
+
} else {
|
|
970
|
+
configSpinner.warn('Could not create bootspring.config.js');
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
810
974
|
console.log(`
|
|
811
975
|
${utils.COLORS.green}${utils.COLORS.bold}✓ SEED.md synthesized successfully!${utils.COLORS.reset}
|
|
812
976
|
|
|
813
977
|
${utils.COLORS.bold}Extracted from:${utils.COLORS.reset}
|
|
814
978
|
${preseedFiles.map(f => ` • ${f}`).join('\n')}
|
|
815
979
|
|
|
980
|
+
${utils.COLORS.bold}Generated Files:${utils.COLORS.reset}
|
|
981
|
+
${utils.COLORS.cyan}SEED.md${utils.COLORS.reset} - Project specification
|
|
982
|
+
${utils.COLORS.cyan}bootspring.config.js${utils.COLORS.reset} - Configuration
|
|
983
|
+
${utils.COLORS.cyan}CLAUDE.md${utils.COLORS.reset} - Claude Code context
|
|
984
|
+
${utils.COLORS.cyan}AGENTS.md${utils.COLORS.reset} - Codex/Cursor/Amp context
|
|
985
|
+
|
|
816
986
|
${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
|
|
817
987
|
1. Review ${utils.COLORS.cyan}SEED.md${utils.COLORS.reset} - your project spec is ready
|
|
818
988
|
2. Run ${utils.COLORS.cyan}bootspring seed scaffold --preset=nextjs${utils.COLORS.reset} to generate project
|
|
989
|
+
3. Or run ${utils.COLORS.cyan}bootspring build${utils.COLORS.reset} to start building tasks
|
|
819
990
|
`);
|
|
820
991
|
} catch (error) {
|
|
821
992
|
spinner.fail(`Synthesis failed: ${error.message}`);
|
|
@@ -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.10",
|
|
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