@girardmedia/bootspring 1.1.0
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/LICENSE +21 -0
- package/README.md +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP skill tool module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function getToolDefinition() {
|
|
6
|
+
return {
|
|
7
|
+
name: 'bootspring_skill',
|
|
8
|
+
description: 'Search and retrieve code patterns/skills. Find battle-tested implementations.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['list', 'show', 'search', 'sync'],
|
|
15
|
+
description: 'Action: list all skills, show a skill, search, or sync remote external catalog'
|
|
16
|
+
},
|
|
17
|
+
name: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Skill name (e.g., auth/clerk, database/prisma)'
|
|
20
|
+
},
|
|
21
|
+
query: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Search query'
|
|
24
|
+
},
|
|
25
|
+
includeExternal: {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
description: 'Include external skills from skills/external'
|
|
28
|
+
},
|
|
29
|
+
limit: {
|
|
30
|
+
type: 'number',
|
|
31
|
+
description: 'Limit list/search result count (default: 50)'
|
|
32
|
+
},
|
|
33
|
+
summary: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
description: 'For action=show, return concise summary instead of full content'
|
|
36
|
+
},
|
|
37
|
+
sections: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'For action=show, comma-separated section keywords to include'
|
|
40
|
+
},
|
|
41
|
+
maxChars: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
description: 'For action=show, cap returned content length'
|
|
44
|
+
},
|
|
45
|
+
mode: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
enum: ['local', 'server'],
|
|
48
|
+
description: 'Optional entitlement mode override (default: local)'
|
|
49
|
+
},
|
|
50
|
+
entitled: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description: 'Optional entitlement override for external skills'
|
|
53
|
+
},
|
|
54
|
+
tier: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Optional tier override (free, pro, team, enterprise)'
|
|
57
|
+
},
|
|
58
|
+
manifestUrl: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'For action=sync, remote manifest URL'
|
|
61
|
+
},
|
|
62
|
+
contentBaseUrl: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'For action=sync, fallback base URL for SKILL.md files'
|
|
65
|
+
},
|
|
66
|
+
token: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'For action=sync, optional bearer token'
|
|
69
|
+
},
|
|
70
|
+
cacheDir: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'For action=sync, local cache directory override'
|
|
73
|
+
},
|
|
74
|
+
manifestPublicKey: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'For action=sync, PEM public key for manifest signature verification'
|
|
77
|
+
},
|
|
78
|
+
requireManifestSignature: {
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
description: 'For action=sync, require signature validation'
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
required: ['action']
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createHandler({ skills, entitlements, trackTelemetry }) {
|
|
89
|
+
return async (args) => {
|
|
90
|
+
const {
|
|
91
|
+
action,
|
|
92
|
+
name,
|
|
93
|
+
query,
|
|
94
|
+
includeExternal = false,
|
|
95
|
+
summary = false,
|
|
96
|
+
sections,
|
|
97
|
+
maxChars,
|
|
98
|
+
mode,
|
|
99
|
+
entitled,
|
|
100
|
+
tier,
|
|
101
|
+
manifestUrl,
|
|
102
|
+
contentBaseUrl,
|
|
103
|
+
token,
|
|
104
|
+
cacheDir,
|
|
105
|
+
manifestPublicKey,
|
|
106
|
+
requireManifestSignature
|
|
107
|
+
} = args;
|
|
108
|
+
|
|
109
|
+
const parsedLimit = Number(args.limit);
|
|
110
|
+
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
|
|
111
|
+
const accessOptions = { mode, entitled, tier };
|
|
112
|
+
|
|
113
|
+
switch (action) {
|
|
114
|
+
case 'list': {
|
|
115
|
+
let skillIds = skills.listSkills({ includeExternal });
|
|
116
|
+
if (includeExternal) {
|
|
117
|
+
skillIds = entitlements.filterAccessibleSkills(skillIds, accessOptions).allowed;
|
|
118
|
+
}
|
|
119
|
+
const items = skillIds
|
|
120
|
+
.slice(0, limit)
|
|
121
|
+
.map(id => {
|
|
122
|
+
const metadata = skills.getSkillMetadata(id) || {};
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
name: metadata.name || id,
|
|
126
|
+
description: metadata.description || ''
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
content: [{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: JSON.stringify(items, null, 2)
|
|
134
|
+
}]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
case 'show': {
|
|
138
|
+
if (!name) throw new Error('Skill name required');
|
|
139
|
+
const metadata = skills.getSkillMetadata(name);
|
|
140
|
+
const targetSkillId = metadata?.id || name;
|
|
141
|
+
const access = entitlements.checkSkillAccess(targetSkillId, accessOptions);
|
|
142
|
+
if (!access.allowed) {
|
|
143
|
+
trackTelemetry('premium_prompted', {
|
|
144
|
+
capability: 'external_skill',
|
|
145
|
+
skillId: targetSkillId,
|
|
146
|
+
mode: access.context?.mode,
|
|
147
|
+
tier: access.context?.tier,
|
|
148
|
+
reason: access.code
|
|
149
|
+
});
|
|
150
|
+
throw new Error(access.reason);
|
|
151
|
+
}
|
|
152
|
+
const rawContent = skills.loadSkill(name, {
|
|
153
|
+
includeExternal: includeExternal || entitlements.isExternalSkill(targetSkillId)
|
|
154
|
+
});
|
|
155
|
+
if (!rawContent) throw new Error(`Skill not found: ${name}`);
|
|
156
|
+
const content = skills.formatSkillContent(rawContent, {
|
|
157
|
+
summary,
|
|
158
|
+
sections,
|
|
159
|
+
maxChars
|
|
160
|
+
});
|
|
161
|
+
if (entitlements.isExternalSkill(targetSkillId)) {
|
|
162
|
+
trackTelemetry('premium_unlocked', {
|
|
163
|
+
capability: 'external_skill',
|
|
164
|
+
skillId: targetSkillId,
|
|
165
|
+
mode: access.context?.mode,
|
|
166
|
+
tier: access.context?.tier,
|
|
167
|
+
reason: access.code
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
content: [{
|
|
173
|
+
type: 'text',
|
|
174
|
+
text: JSON.stringify({
|
|
175
|
+
id: metadata?.id || name,
|
|
176
|
+
name: metadata?.name || name,
|
|
177
|
+
description: metadata?.description || '',
|
|
178
|
+
source: metadata?.source || 'built-in',
|
|
179
|
+
content
|
|
180
|
+
}, null, 2)
|
|
181
|
+
}]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
case 'search': {
|
|
185
|
+
if (!query) throw new Error('Search query required');
|
|
186
|
+
let matches = skills.searchSkills(query, { includeExternal, limit });
|
|
187
|
+
if (includeExternal) {
|
|
188
|
+
matches = entitlements.filterAccessibleSkills(matches, accessOptions).allowed;
|
|
189
|
+
}
|
|
190
|
+
const items = matches
|
|
191
|
+
.map(id => {
|
|
192
|
+
const metadata = skills.getSkillMetadata(id) || {};
|
|
193
|
+
return {
|
|
194
|
+
id,
|
|
195
|
+
name: metadata.name || id,
|
|
196
|
+
description: metadata.description || '',
|
|
197
|
+
source: metadata.source || 'built-in'
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
content: [{
|
|
203
|
+
type: 'text',
|
|
204
|
+
text: JSON.stringify(items, null, 2)
|
|
205
|
+
}]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
case 'sync': {
|
|
209
|
+
const result = await skills.syncExternalCatalog({
|
|
210
|
+
manifestUrl,
|
|
211
|
+
contentBaseUrl,
|
|
212
|
+
token,
|
|
213
|
+
cacheDir,
|
|
214
|
+
manifestPublicKey,
|
|
215
|
+
requireManifestSignature
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
content: [{
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: JSON.stringify(result, null, 2)
|
|
221
|
+
}]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
default:
|
|
225
|
+
throw new Error(`Unknown action: ${action}`);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
getToolDefinition,
|
|
232
|
+
createHandler
|
|
233
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP telemetry tool module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function getToolDefinition() {
|
|
6
|
+
return {
|
|
7
|
+
name: 'bootspring_telemetry',
|
|
8
|
+
description: 'Inspect and upload Bootspring telemetry events.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['status', 'list', 'upload', 'clear'],
|
|
15
|
+
description: 'Action: status, list, upload, or clear'
|
|
16
|
+
},
|
|
17
|
+
event: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Optional event filter'
|
|
20
|
+
},
|
|
21
|
+
limit: {
|
|
22
|
+
type: 'number',
|
|
23
|
+
description: 'Optional event limit for list/upload'
|
|
24
|
+
},
|
|
25
|
+
endpoint: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Optional upload endpoint override'
|
|
28
|
+
},
|
|
29
|
+
token: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Optional upload bearer token override'
|
|
32
|
+
},
|
|
33
|
+
clearOnSuccess: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
description: 'For action=upload, clear local events on success'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: ['action']
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createHandler({ telemetry }) {
|
|
44
|
+
return async (args) => {
|
|
45
|
+
const { action, event, endpoint, token, clearOnSuccess } = args;
|
|
46
|
+
const limit = Number(args.limit);
|
|
47
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : undefined;
|
|
48
|
+
|
|
49
|
+
switch (action) {
|
|
50
|
+
case 'status':
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: JSON.stringify(telemetry.getStatus(), null, 2)
|
|
55
|
+
}]
|
|
56
|
+
};
|
|
57
|
+
case 'list':
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: 'text',
|
|
61
|
+
text: JSON.stringify(telemetry.listEvents({ event, limit: safeLimit }), null, 2)
|
|
62
|
+
}]
|
|
63
|
+
};
|
|
64
|
+
case 'upload': {
|
|
65
|
+
const result = await telemetry.uploadEvents({
|
|
66
|
+
event,
|
|
67
|
+
limit: safeLimit,
|
|
68
|
+
endpoint,
|
|
69
|
+
token,
|
|
70
|
+
clearOnSuccess: Boolean(clearOnSuccess)
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: JSON.stringify(result, null, 2)
|
|
76
|
+
}]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case 'clear':
|
|
80
|
+
return {
|
|
81
|
+
content: [{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: JSON.stringify(telemetry.clearEvents(), null, 2)
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Unknown action: ${action}`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
getToolDefinition,
|
|
94
|
+
createHandler
|
|
95
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP todo tool module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
function getToolDefinition() {
|
|
9
|
+
return {
|
|
10
|
+
name: 'bootspring_todo',
|
|
11
|
+
description: 'Manage project todo items. Add, list, complete, or remove tasks.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
action: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
enum: ['list', 'add', 'done', 'remove', 'clear'],
|
|
18
|
+
description: 'Action: list, add, done (complete), remove, or clear'
|
|
19
|
+
},
|
|
20
|
+
text: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Todo text (when adding)'
|
|
23
|
+
},
|
|
24
|
+
index: {
|
|
25
|
+
type: 'number',
|
|
26
|
+
description: 'Todo index number (when completing or removing)'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
required: ['action']
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createHandler({ config, todoModule, format }) {
|
|
35
|
+
return async (args) => {
|
|
36
|
+
const { action, text, index } = args;
|
|
37
|
+
const cfg = config.load();
|
|
38
|
+
const todoPath = path.join(cfg._projectRoot, cfg.paths?.todo || 'todo.md');
|
|
39
|
+
|
|
40
|
+
let content = '';
|
|
41
|
+
if (fs.existsSync(todoPath)) {
|
|
42
|
+
content = fs.readFileSync(todoPath, 'utf-8');
|
|
43
|
+
} else {
|
|
44
|
+
content = '# Todo List\n\n## Pending\n\n## Completed\n';
|
|
45
|
+
fs.writeFileSync(todoPath, content);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const todos = todoModule.parseTodos(content);
|
|
49
|
+
|
|
50
|
+
switch (action) {
|
|
51
|
+
case 'list': {
|
|
52
|
+
const pending = todos.filter(t => !t.completed);
|
|
53
|
+
const completed = todos.filter(t => t.completed);
|
|
54
|
+
return format.todoList(pending, completed);
|
|
55
|
+
}
|
|
56
|
+
case 'add': {
|
|
57
|
+
if (!text) return format.error('Todo text required', ['Provide text="your task"']);
|
|
58
|
+
const lines = content.split('\n');
|
|
59
|
+
|
|
60
|
+
let insertIndex = lines.length;
|
|
61
|
+
for (let i = 0; i < lines.length; i++) {
|
|
62
|
+
if (lines[i].match(/^##?\s*(Pending|In Progress|Todo)/i)) {
|
|
63
|
+
insertIndex = i + 1;
|
|
64
|
+
while (insertIndex < lines.length && lines[insertIndex].trim() === '') {
|
|
65
|
+
insertIndex++;
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
lines.splice(insertIndex, 0, `- [ ] ${text}`);
|
|
72
|
+
fs.writeFileSync(todoPath, lines.join('\n'));
|
|
73
|
+
|
|
74
|
+
return format.success({
|
|
75
|
+
summary: `Added todo: "${text}"`,
|
|
76
|
+
hints: ['Use action=list to see all todos']
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
case 'done': {
|
|
80
|
+
if (index === undefined) return format.error('Todo index required', ['Provide index=0']);
|
|
81
|
+
const todo = todos.find(t => t.index === index);
|
|
82
|
+
if (!todo) return format.error(`Todo #${index} not found`, ['Use action=list to see valid indices']);
|
|
83
|
+
|
|
84
|
+
const lines = content.split('\n');
|
|
85
|
+
lines[todo.line] = lines[todo.line].replace('[ ]', '[x]');
|
|
86
|
+
fs.writeFileSync(todoPath, lines.join('\n'));
|
|
87
|
+
|
|
88
|
+
return format.success({
|
|
89
|
+
summary: `Completed: "${todo.text}" 🎉`,
|
|
90
|
+
hints: ['Use action=list to see remaining todos']
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
case 'remove': {
|
|
94
|
+
if (index === undefined) return format.error('Todo index required', ['Provide index=0']);
|
|
95
|
+
const todo = todos.find(t => t.index === index);
|
|
96
|
+
if (!todo) return format.error(`Todo #${index} not found`, ['Use action=list to see valid indices']);
|
|
97
|
+
|
|
98
|
+
const lines = content.split('\n');
|
|
99
|
+
lines.splice(todo.line, 1);
|
|
100
|
+
fs.writeFileSync(todoPath, lines.join('\n'));
|
|
101
|
+
|
|
102
|
+
return format.success({
|
|
103
|
+
summary: `Removed: "${todo.text}"`,
|
|
104
|
+
hints: ['Use action=list to see remaining todos']
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
case 'clear': {
|
|
108
|
+
const completed = todos.filter(t => t.completed);
|
|
109
|
+
const lines = content.split('\n');
|
|
110
|
+
const linesToRemove = completed.map(t => t.line).sort((a, b) => b - a);
|
|
111
|
+
|
|
112
|
+
for (const lineIndex of linesToRemove) {
|
|
113
|
+
lines.splice(lineIndex, 1);
|
|
114
|
+
}
|
|
115
|
+
fs.writeFileSync(todoPath, lines.join('\n'));
|
|
116
|
+
|
|
117
|
+
return format.success({
|
|
118
|
+
summary: `Cleared ${completed.length} completed todos`,
|
|
119
|
+
hints: completed.length > 0 ? ['Todo list is now clean!'] : ['No completed todos to clear']
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
return format.error(`Unknown action: ${action}`, [
|
|
124
|
+
'Valid actions: list, add, done, remove, clear'
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
getToolDefinition,
|
|
132
|
+
createHandler
|
|
133
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@girardmedia/bootspring",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Development scaffolding with intelligence - AI-powered context, agents, and workflows for any MCP-compatible assistant",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"development",
|
|
8
|
+
"scaffolding",
|
|
9
|
+
"mcp",
|
|
10
|
+
"claude",
|
|
11
|
+
"agents",
|
|
12
|
+
"context",
|
|
13
|
+
"workflow",
|
|
14
|
+
"devtools"
|
|
15
|
+
],
|
|
16
|
+
"author": "Bootspring",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/bootspring/bootspring.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://bootspring.com",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/bootspring/bootspring/issues"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"bootspring": "./bin/bootspring.js"
|
|
28
|
+
},
|
|
29
|
+
"main": "./core/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./core/index.js",
|
|
32
|
+
"./mcp": "./mcp/server.js",
|
|
33
|
+
"./hooks": "./hooks/index.js",
|
|
34
|
+
"./agents": "./agents/index.js",
|
|
35
|
+
"./skills": "./skills/index.js",
|
|
36
|
+
"./plugins": "./plugins/index.js",
|
|
37
|
+
"./intelligence": "./intelligence/index.js",
|
|
38
|
+
"./generators": "./generators/index.js",
|
|
39
|
+
"./quality": "./quality/index.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"bin/",
|
|
43
|
+
"core/",
|
|
44
|
+
"cli/",
|
|
45
|
+
"mcp/",
|
|
46
|
+
"hooks/",
|
|
47
|
+
"agents/",
|
|
48
|
+
"skills/index.js",
|
|
49
|
+
"skills/patterns/",
|
|
50
|
+
"plugins/",
|
|
51
|
+
"dashboard/",
|
|
52
|
+
"quality/",
|
|
53
|
+
"templates/",
|
|
54
|
+
"docs/",
|
|
55
|
+
"intelligence/",
|
|
56
|
+
"generators/"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"start": "node bin/bootspring.js",
|
|
60
|
+
"dashboard": "node bin/bootspring.js dashboard",
|
|
61
|
+
"mcp": "node mcp/server.js",
|
|
62
|
+
"test": "jest",
|
|
63
|
+
"test:watch": "jest --watch",
|
|
64
|
+
"test:coverage": "jest --coverage",
|
|
65
|
+
"lint": "eslint .",
|
|
66
|
+
"lint:fix": "eslint . --fix",
|
|
67
|
+
"verify:lint-budget": "node scripts/check-lint-budgets.js",
|
|
68
|
+
"build:mcp-contract": "node scripts/export-mcp-contract.js",
|
|
69
|
+
"verify:mcp-contract": "node scripts/export-mcp-contract.js --check",
|
|
70
|
+
"verify:package": "node scripts/check-package-boundaries.js",
|
|
71
|
+
"prepublishOnly": "npm test && npm run lint --if-present && npm run verify:package && npm run verify:mcp-contract"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@eslint/js": "^9.39.2",
|
|
75
|
+
"eslint": "^9.39.2",
|
|
76
|
+
"globals": "^17.3.0",
|
|
77
|
+
"jest": "^29.7.0"
|
|
78
|
+
},
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"ws": "^8.18.0",
|
|
81
|
+
"yaml": "^2.8.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
85
|
+
"zod": "^3.25.0"
|
|
86
|
+
},
|
|
87
|
+
"peerDependenciesMeta": {
|
|
88
|
+
"@modelcontextprotocol/sdk": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"zod": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"engines": {
|
|
96
|
+
"node": ">=18.0.0"
|
|
97
|
+
}
|
|
98
|
+
}
|
package/plugins/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Plugins Module
|
|
3
|
+
* Plugin management and configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Available plugins and their providers
|
|
7
|
+
const PLUGINS = {
|
|
8
|
+
auth: {
|
|
9
|
+
name: 'Authentication',
|
|
10
|
+
description: 'User authentication and session management',
|
|
11
|
+
providers: ['clerk', 'nextauth', 'auth0', 'supabase'],
|
|
12
|
+
defaultProvider: 'clerk',
|
|
13
|
+
features: ['social_login', 'email_password', 'organizations', 'mfa']
|
|
14
|
+
},
|
|
15
|
+
payments: {
|
|
16
|
+
name: 'Payments',
|
|
17
|
+
description: 'Payment processing and subscriptions',
|
|
18
|
+
providers: ['stripe', 'paddle', 'lemonsqueezy'],
|
|
19
|
+
defaultProvider: 'stripe',
|
|
20
|
+
features: ['checkout', 'subscriptions', 'webhooks', 'customer_portal']
|
|
21
|
+
},
|
|
22
|
+
database: {
|
|
23
|
+
name: 'Database',
|
|
24
|
+
description: 'Database ORM and management',
|
|
25
|
+
providers: ['prisma', 'drizzle', 'typeorm', 'mongoose'],
|
|
26
|
+
defaultProvider: 'prisma',
|
|
27
|
+
features: ['migrations', 'transactions', 'soft_deletes', 'seeding']
|
|
28
|
+
},
|
|
29
|
+
testing: {
|
|
30
|
+
name: 'Testing',
|
|
31
|
+
description: 'Testing framework and utilities',
|
|
32
|
+
providers: ['vitest', 'jest', 'playwright'],
|
|
33
|
+
defaultProvider: 'vitest',
|
|
34
|
+
features: ['unit', 'integration', 'e2e', 'coverage']
|
|
35
|
+
},
|
|
36
|
+
security: {
|
|
37
|
+
name: 'Security',
|
|
38
|
+
description: 'Security features and validation',
|
|
39
|
+
providers: ['default'],
|
|
40
|
+
defaultProvider: 'default',
|
|
41
|
+
features: ['input_validation', 'rate_limiting', 'csrf', 'headers']
|
|
42
|
+
},
|
|
43
|
+
ai: {
|
|
44
|
+
name: 'AI',
|
|
45
|
+
description: 'AI/LLM integration',
|
|
46
|
+
providers: ['anthropic', 'openai', 'google'],
|
|
47
|
+
defaultProvider: 'anthropic',
|
|
48
|
+
features: ['streaming', 'tool_use', 'structured_output', 'embeddings']
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get all available plugins
|
|
54
|
+
*/
|
|
55
|
+
function getPlugins() {
|
|
56
|
+
return PLUGINS;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get a specific plugin
|
|
61
|
+
*/
|
|
62
|
+
function getPlugin(name) {
|
|
63
|
+
return PLUGINS[name] || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List plugin names
|
|
68
|
+
*/
|
|
69
|
+
function listPlugins() {
|
|
70
|
+
return Object.keys(PLUGINS);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get providers for a plugin
|
|
75
|
+
*/
|
|
76
|
+
function getProviders(pluginName) {
|
|
77
|
+
const plugin = PLUGINS[pluginName];
|
|
78
|
+
return plugin ? plugin.providers : [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a provider is valid for a plugin
|
|
83
|
+
*/
|
|
84
|
+
function isValidProvider(pluginName, providerName) {
|
|
85
|
+
const providers = getProviders(pluginName);
|
|
86
|
+
return providers.includes(providerName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get plugin configuration template
|
|
91
|
+
*/
|
|
92
|
+
function getPluginConfig(pluginName, provider = null) {
|
|
93
|
+
const plugin = PLUGINS[pluginName];
|
|
94
|
+
if (!plugin) return null;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
enabled: true,
|
|
98
|
+
provider: provider || plugin.defaultProvider,
|
|
99
|
+
features: plugin.features
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate plugin configuration
|
|
105
|
+
*/
|
|
106
|
+
function validateConfig(config) {
|
|
107
|
+
const errors = [];
|
|
108
|
+
|
|
109
|
+
if (!config || typeof config !== 'object') {
|
|
110
|
+
return { valid: false, errors: ['Configuration must be an object'] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const [pluginName, pluginConfig] of Object.entries(config)) {
|
|
114
|
+
if (!PLUGINS[pluginName]) {
|
|
115
|
+
errors.push(`Unknown plugin: ${pluginName}`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (pluginConfig.enabled && pluginConfig.provider) {
|
|
120
|
+
if (!isValidProvider(pluginName, pluginConfig.provider)) {
|
|
121
|
+
errors.push(`Invalid provider '${pluginConfig.provider}' for plugin '${pluginName}'`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
valid: errors.length === 0,
|
|
128
|
+
errors
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
PLUGINS,
|
|
134
|
+
getPlugins,
|
|
135
|
+
getPlugin,
|
|
136
|
+
listPlugins,
|
|
137
|
+
getProviders,
|
|
138
|
+
isValidProvider,
|
|
139
|
+
getPluginConfig,
|
|
140
|
+
validateConfig
|
|
141
|
+
};
|