@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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/agents/README.md +93 -0
  4. package/agents/api-expert/context.md +416 -0
  5. package/agents/architecture-expert/context.md +454 -0
  6. package/agents/backend-expert/context.md +483 -0
  7. package/agents/code-review-expert/context.md +365 -0
  8. package/agents/database-expert/context.md +250 -0
  9. package/agents/devops-expert/context.md +446 -0
  10. package/agents/frontend-expert/context.md +364 -0
  11. package/agents/index.js +140 -0
  12. package/agents/performance-expert/context.md +377 -0
  13. package/agents/security-expert/context.md +343 -0
  14. package/agents/testing-expert/context.md +414 -0
  15. package/agents/ui-ux-expert/context.md +448 -0
  16. package/agents/vercel-expert/context.md +426 -0
  17. package/bin/bootspring.js +310 -0
  18. package/cli/agent.js +337 -0
  19. package/cli/context.js +194 -0
  20. package/cli/dashboard.js +150 -0
  21. package/cli/generate.js +294 -0
  22. package/cli/init.js +410 -0
  23. package/cli/loop.js +421 -0
  24. package/cli/mcp.js +241 -0
  25. package/cli/memory.js +303 -0
  26. package/cli/orchestrator.js +400 -0
  27. package/cli/plugin.js +451 -0
  28. package/cli/quality.js +332 -0
  29. package/cli/skill.js +369 -0
  30. package/cli/task.js +628 -0
  31. package/cli/telemetry.js +114 -0
  32. package/cli/todo.js +614 -0
  33. package/cli/update.js +312 -0
  34. package/core/config.js +245 -0
  35. package/core/context.js +329 -0
  36. package/core/entitlements.js +209 -0
  37. package/core/index.js +43 -0
  38. package/core/policies.js +68 -0
  39. package/core/telemetry.js +247 -0
  40. package/core/utils.js +380 -0
  41. package/dashboard/server.js +818 -0
  42. package/docs/integrations/claude-code.md +42 -0
  43. package/docs/integrations/codex.md +42 -0
  44. package/docs/mcp-api-platform.md +102 -0
  45. package/generators/generate.js +598 -0
  46. package/generators/index.js +18 -0
  47. package/hooks/context-detector.js +177 -0
  48. package/hooks/index.js +35 -0
  49. package/hooks/prompt-enhancer.js +289 -0
  50. package/intelligence/git-memory.js +551 -0
  51. package/intelligence/index.js +59 -0
  52. package/intelligence/orchestrator.js +964 -0
  53. package/intelligence/prd.js +447 -0
  54. package/intelligence/recommendation-weights.json +18 -0
  55. package/intelligence/recommendations.js +234 -0
  56. package/mcp/capabilities.js +71 -0
  57. package/mcp/contracts/mcp-contract.v1.json +497 -0
  58. package/mcp/registry.js +213 -0
  59. package/mcp/response-formatter.js +462 -0
  60. package/mcp/server.js +99 -0
  61. package/mcp/tools/agent-tool.js +137 -0
  62. package/mcp/tools/capabilities-tool.js +54 -0
  63. package/mcp/tools/context-tool.js +49 -0
  64. package/mcp/tools/dashboard-tool.js +58 -0
  65. package/mcp/tools/generate-tool.js +46 -0
  66. package/mcp/tools/loop-tool.js +134 -0
  67. package/mcp/tools/memory-tool.js +180 -0
  68. package/mcp/tools/orchestrator-tool.js +232 -0
  69. package/mcp/tools/plugin-tool.js +76 -0
  70. package/mcp/tools/quality-tool.js +47 -0
  71. package/mcp/tools/skill-tool.js +233 -0
  72. package/mcp/tools/telemetry-tool.js +95 -0
  73. package/mcp/tools/todo-tool.js +133 -0
  74. package/package.json +98 -0
  75. package/plugins/index.js +141 -0
  76. package/quality/index.js +380 -0
  77. package/quality/lint-budgets.json +19 -0
  78. package/skills/index.js +787 -0
  79. package/skills/patterns/README.md +163 -0
  80. package/skills/patterns/api/route-handler.md +217 -0
  81. package/skills/patterns/api/server-action.md +249 -0
  82. package/skills/patterns/auth/clerk.md +132 -0
  83. package/skills/patterns/database/prisma.md +180 -0
  84. package/skills/patterns/payments/stripe.md +272 -0
  85. package/skills/patterns/security/validation.md +268 -0
  86. package/skills/patterns/testing/vitest.md +307 -0
  87. package/templates/bootspring.config.js +83 -0
  88. package/templates/mcp.json +9 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * MCP memory tool module
3
+ */
4
+
5
+ function getToolDefinition() {
6
+ return {
7
+ name: 'bootspring_memory',
8
+ description: 'Access git-based project memory. Extract learnings, search history, get file-specific context.',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ action: {
13
+ type: 'string',
14
+ enum: ['summary', 'search', 'files', 'stats'],
15
+ description: 'Action: summary (categorized learnings), search (by keyword), files (for specific files), stats (repo overview)'
16
+ },
17
+ query: {
18
+ type: 'string',
19
+ description: 'Search query (for search action)'
20
+ },
21
+ files: {
22
+ type: 'array',
23
+ items: { type: 'string' },
24
+ description: 'File paths to get learnings for (for files action)'
25
+ },
26
+ limit: {
27
+ type: 'number',
28
+ description: 'Max learnings to return (default: 20)'
29
+ }
30
+ },
31
+ required: ['action']
32
+ }
33
+ };
34
+ }
35
+
36
+ function createHandler({ gitMemory }) {
37
+ return async (args) => {
38
+ const { action, query, files, limit = 20 } = args;
39
+
40
+ if (!gitMemory.isGitRepo()) {
41
+ return {
42
+ content: [{
43
+ type: 'text',
44
+ text: JSON.stringify({
45
+ error: 'Not a git repository',
46
+ hint: 'Initialize git with: git init'
47
+ }, null, 2)
48
+ }]
49
+ };
50
+ }
51
+
52
+ switch (action) {
53
+ case 'summary': {
54
+ const result = gitMemory.extractLearnings({ limit });
55
+ if (result.error) {
56
+ return {
57
+ content: [{ type: 'text', text: `Error: ${result.error}` }]
58
+ };
59
+ }
60
+ const grouped = gitMemory.groupByCategory(result.learnings);
61
+ const summary = {};
62
+
63
+ for (const [category, items] of Object.entries(grouped)) {
64
+ const config = gitMemory.MEMORY_CATEGORIES[category];
65
+ summary[category] = {
66
+ icon: config?.icon || '📝',
67
+ label: config?.label || category,
68
+ count: items.length,
69
+ top: items.slice(0, 3).map(i => ({
70
+ hash: i.hash,
71
+ summary: i.summary
72
+ }))
73
+ };
74
+ }
75
+
76
+ return {
77
+ content: [{
78
+ type: 'text',
79
+ text: JSON.stringify({
80
+ totalLearnings: result.learnings.length,
81
+ categories: summary
82
+ }, null, 2)
83
+ }]
84
+ };
85
+ }
86
+
87
+ case 'search': {
88
+ if (!query) {
89
+ return {
90
+ content: [{
91
+ type: 'text',
92
+ text: JSON.stringify({ error: 'query required for search' }, null, 2)
93
+ }]
94
+ };
95
+ }
96
+ const result = gitMemory.extractLearnings({ limit: 100 });
97
+ const matches = gitMemory.searchLearnings(result.learnings, query);
98
+
99
+ return {
100
+ content: [{
101
+ type: 'text',
102
+ text: JSON.stringify({
103
+ query,
104
+ matches: matches.slice(0, limit).map(m => ({
105
+ hash: m.hash,
106
+ summary: m.summary,
107
+ categories: m.categories,
108
+ details: m.details?.slice(0, 200)
109
+ }))
110
+ }, null, 2)
111
+ }]
112
+ };
113
+ }
114
+
115
+ case 'files': {
116
+ if (!files || files.length === 0) {
117
+ return {
118
+ content: [{
119
+ type: 'text',
120
+ text: JSON.stringify({ error: 'files array required' }, null, 2)
121
+ }]
122
+ };
123
+ }
124
+ const learnings = gitMemory.getLearningsForFiles(files, { limit });
125
+
126
+ return {
127
+ content: [{
128
+ type: 'text',
129
+ text: JSON.stringify({
130
+ files,
131
+ learnings: learnings.map(l => ({
132
+ hash: l.hash,
133
+ file: l.file,
134
+ summary: l.summary,
135
+ categories: l.categories
136
+ }))
137
+ }, null, 2)
138
+ }]
139
+ };
140
+ }
141
+
142
+ case 'stats': {
143
+ const stats = gitMemory.getRepoStats();
144
+ const result = gitMemory.extractLearnings({ limit: 100 });
145
+ const grouped = gitMemory.groupByCategory(result.learnings);
146
+
147
+ const categoryCounts = {};
148
+ for (const [cat, items] of Object.entries(grouped)) {
149
+ categoryCounts[cat] = items.length;
150
+ }
151
+
152
+ return {
153
+ content: [{
154
+ type: 'text',
155
+ text: JSON.stringify({
156
+ ...stats,
157
+ categoryCounts
158
+ }, null, 2)
159
+ }]
160
+ };
161
+ }
162
+
163
+ default:
164
+ return {
165
+ content: [{
166
+ type: 'text',
167
+ text: JSON.stringify({
168
+ error: `Unknown action: ${action}`,
169
+ validActions: ['summary', 'search', 'files', 'stats']
170
+ }, null, 2)
171
+ }]
172
+ };
173
+ }
174
+ };
175
+ }
176
+
177
+ module.exports = {
178
+ getToolDefinition,
179
+ createHandler
180
+ };
@@ -0,0 +1,232 @@
1
+ /**
2
+ * MCP orchestrator tool module
3
+ */
4
+
5
+ const MAX_RECOMMENDATION_LIMIT = 50;
6
+ const DEFAULT_RECOMMENDATION_LIMIT = 5;
7
+
8
+ function getToolDefinition() {
9
+ return {
10
+ name: 'bootspring_orchestrator',
11
+ description: 'Intelligent agent coordination. Analyze context, suggest agents, manage workflows.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ action: {
16
+ type: 'string',
17
+ enum: ['analyze', 'suggest', 'recommend', 'workflows', 'workflow', 'start', 'next', 'checkpoint', 'status'],
18
+ description: 'Action: analyze context, suggest agents, recommend workflows/skills, list workflows, show workflow, start/advance workflow, or status'
19
+ },
20
+ context: {
21
+ type: 'string',
22
+ description: 'Context text to analyze (for analyze/suggest actions)'
23
+ },
24
+ workflow: {
25
+ type: 'string',
26
+ description: 'Workflow name (for workflow action)'
27
+ },
28
+ signal: {
29
+ type: 'string',
30
+ description: 'Completion signal ref for action=checkpoint (exact text, partial text, or 1-based index)'
31
+ },
32
+ limit: {
33
+ type: 'number',
34
+ description: 'Max recommendations to return for action=recommend (default: 5)'
35
+ },
36
+ mode: {
37
+ type: 'string',
38
+ enum: ['local', 'server'],
39
+ description: 'Optional entitlement mode override'
40
+ },
41
+ entitled: {
42
+ type: 'boolean',
43
+ description: 'Optional entitlement override for premium workflows'
44
+ },
45
+ tier: {
46
+ type: 'string',
47
+ description: 'Optional tier override: free, pro, team, enterprise'
48
+ }
49
+ },
50
+ required: ['action']
51
+ }
52
+ };
53
+ }
54
+
55
+ function createHandler({ intelligence, entitlements, recommendations, trackTelemetry, format }) {
56
+ return async (args) => {
57
+ const {
58
+ action,
59
+ context: contextText,
60
+ workflow: workflowName,
61
+ signal,
62
+ mode,
63
+ entitled,
64
+ tier
65
+ } = args;
66
+ const accessOptions = { mode, entitled, tier };
67
+
68
+ switch (action) {
69
+ case 'analyze':
70
+ case 'suggest': {
71
+ const analysis = intelligence.analyzeContext(contextText || '');
72
+ return {
73
+ content: [{
74
+ type: 'text',
75
+ text: JSON.stringify({
76
+ phase: analysis.phase,
77
+ phaseName: analysis.phaseConfig?.name,
78
+ suggestions: analysis.suggestions,
79
+ skills: analysis.skills
80
+ }, null, 2)
81
+ }]
82
+ };
83
+ }
84
+ case 'recommend': {
85
+ const requestedLimit = Number(args.limit);
86
+ const validLimit = Number.isFinite(requestedLimit) && requestedLimit > 0
87
+ ? Math.min(requestedLimit, MAX_RECOMMENDATION_LIMIT)
88
+ : DEFAULT_RECOMMENDATION_LIMIT;
89
+ const result = recommendations.recommend({
90
+ contextText: contextText || '',
91
+ limit: validLimit,
92
+ accessOptions
93
+ });
94
+ return {
95
+ content: [{
96
+ type: 'text',
97
+ text: JSON.stringify(result, null, 2)
98
+ }]
99
+ };
100
+ }
101
+ case 'workflows': {
102
+ const workflows = intelligence.listWorkflows();
103
+ const filtered = entitlements.filterAccessibleWorkflows(workflows, accessOptions);
104
+ return {
105
+ content: [{
106
+ type: 'text',
107
+ text: JSON.stringify({
108
+ workflows: filtered.allowed,
109
+ hiddenCount: filtered.denied.length
110
+ }, null, 2)
111
+ }]
112
+ };
113
+ }
114
+ case 'workflow': {
115
+ if (!workflowName) throw new Error('Workflow name required');
116
+ const workflow = intelligence.getWorkflow(workflowName);
117
+ if (!workflow) throw new Error(`Unknown workflow: ${workflowName}`);
118
+ const decision = entitlements.checkWorkflowAccess(workflow, accessOptions);
119
+ if (!decision.allowed) {
120
+ trackTelemetry('premium_prompted', {
121
+ capability: 'workflow_pack',
122
+ workflow: workflowName,
123
+ tier: workflow.tier || 'free',
124
+ mode: decision.context?.mode,
125
+ userTier: decision.context?.tier,
126
+ reason: decision.code
127
+ });
128
+ throw new Error(decision.reason);
129
+ }
130
+ return {
131
+ content: [{
132
+ type: 'text',
133
+ text: JSON.stringify(workflow, null, 2)
134
+ }]
135
+ };
136
+ }
137
+ case 'start': {
138
+ if (!workflowName) throw new Error('Workflow name required');
139
+ const workflow = intelligence.getWorkflow(workflowName);
140
+ if (!workflow) throw new Error(`Unknown workflow: ${workflowName}`);
141
+ const decision = entitlements.checkWorkflowAccess(workflow, accessOptions);
142
+ if (!decision.allowed) {
143
+ trackTelemetry('premium_prompted', {
144
+ capability: 'workflow_pack',
145
+ workflow: workflowName,
146
+ tier: workflow.tier || 'free',
147
+ mode: decision.context?.mode,
148
+ userTier: decision.context?.tier,
149
+ reason: decision.code
150
+ });
151
+ throw new Error(decision.reason);
152
+ }
153
+ const result = intelligence.startWorkflow(workflowName);
154
+ if (!result.success) throw new Error(result.error || 'Failed to start workflow');
155
+ return {
156
+ content: [{
157
+ type: 'text',
158
+ text: JSON.stringify({
159
+ ...result,
160
+ signalProgress: intelligence.getWorkflowSignalProgress(workflowName)
161
+ }, null, 2)
162
+ }]
163
+ };
164
+ }
165
+ case 'next': {
166
+ const result = intelligence.advanceWorkflow();
167
+ if (!result.success) throw new Error(result.error || 'Failed to advance workflow');
168
+ const activeName = intelligence.loadState().activeWorkflow;
169
+ return {
170
+ content: [{
171
+ type: 'text',
172
+ text: JSON.stringify({
173
+ ...result,
174
+ signalProgress: activeName ? intelligence.getWorkflowSignalProgress(activeName) : null
175
+ }, null, 2)
176
+ }]
177
+ };
178
+ }
179
+ case 'checkpoint': {
180
+ const targetWorkflowName = workflowName || intelligence.loadState().activeWorkflow;
181
+ if (targetWorkflowName) {
182
+ const workflow = intelligence.getWorkflow(targetWorkflowName);
183
+ if (!workflow) throw new Error(`Unknown workflow: ${targetWorkflowName}`);
184
+ const decision = entitlements.checkWorkflowAccess(workflow, accessOptions);
185
+ if (!decision.allowed) {
186
+ trackTelemetry('premium_prompted', {
187
+ capability: 'workflow_pack',
188
+ workflow: targetWorkflowName,
189
+ tier: workflow.tier || 'free',
190
+ mode: decision.context?.mode,
191
+ userTier: decision.context?.tier,
192
+ reason: decision.code
193
+ });
194
+ throw new Error(decision.reason);
195
+ }
196
+ }
197
+ const result = intelligence.markWorkflowCheckpoint(workflowName, signal);
198
+ if (!result.success) throw new Error(result.error || 'Failed to mark workflow checkpoint');
199
+ return {
200
+ content: [{
201
+ type: 'text',
202
+ text: JSON.stringify(result, null, 2)
203
+ }]
204
+ };
205
+ }
206
+ case 'status': {
207
+ const state = intelligence.loadState();
208
+ const phase = intelligence.getCurrentPhase();
209
+ const agents = intelligence.getAvailableAgents();
210
+ return format.orchestratorStatus({
211
+ currentPhase: phase,
212
+ lastAnalysis: state.lastAnalysis,
213
+ activeWorkflow: state.activeWorkflow,
214
+ availableAgents: agents.length,
215
+ recentSuggestions: state.suggestions?.slice(0, 5),
216
+ activeWorkflowSignalProgress: state.activeWorkflow
217
+ ? intelligence.getWorkflowSignalProgress(state.activeWorkflow, state)
218
+ : null
219
+ });
220
+ }
221
+ default:
222
+ return format.error(`Unknown action: ${action}`, [
223
+ 'Valid actions: analyze, suggest, recommend, workflows, workflow, start, next, checkpoint, status'
224
+ ]);
225
+ }
226
+ };
227
+ }
228
+
229
+ module.exports = {
230
+ getToolDefinition,
231
+ createHandler
232
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * MCP plugin tool module
3
+ */
4
+
5
+ function getToolDefinition() {
6
+ return {
7
+ name: 'bootspring_plugin',
8
+ description: 'Manage plugins. List, enable, disable, or show plugin details.',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ action: {
13
+ type: 'string',
14
+ enum: ['list', 'show', 'enable', 'disable'],
15
+ description: 'Action: list, show, enable, or disable'
16
+ },
17
+ name: {
18
+ type: 'string',
19
+ description: 'Plugin name (e.g., auth, payments, database)'
20
+ }
21
+ },
22
+ required: ['action']
23
+ }
24
+ };
25
+ }
26
+
27
+ function createHandler({ config }) {
28
+ return async (args) => {
29
+ const { action, name } = args;
30
+ const cfg = config.load();
31
+
32
+ switch (action) {
33
+ case 'list': {
34
+ const plugins = Object.entries(cfg.plugins || {}).map(([id, plugin]) => ({
35
+ id,
36
+ enabled: plugin.enabled !== false,
37
+ provider: plugin.provider || 'default'
38
+ }));
39
+ return {
40
+ content: [{
41
+ type: 'text',
42
+ text: JSON.stringify(plugins, null, 2)
43
+ }]
44
+ };
45
+ }
46
+ case 'show': {
47
+ if (!name) throw new Error('Plugin name required');
48
+ const plugin = cfg.plugins?.[name];
49
+ if (!plugin) throw new Error(`Plugin not found: ${name}`);
50
+ return {
51
+ content: [{
52
+ type: 'text',
53
+ text: JSON.stringify({ id: name, ...plugin }, null, 2)
54
+ }]
55
+ };
56
+ }
57
+ case 'enable':
58
+ case 'disable': {
59
+ if (!name) throw new Error('Plugin name required');
60
+ return {
61
+ content: [{
62
+ type: 'text',
63
+ text: `Plugin ${name} ${action}d. Update bootspring.config.js to persist.`
64
+ }]
65
+ };
66
+ }
67
+ default:
68
+ throw new Error(`Unknown action: ${action}`);
69
+ }
70
+ };
71
+ }
72
+
73
+ module.exports = {
74
+ getToolDefinition,
75
+ createHandler
76
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * MCP quality tool module
3
+ */
4
+
5
+ function getToolDefinition() {
6
+ return {
7
+ name: 'bootspring_quality',
8
+ description: 'Run quality gates to check code quality and security.',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ gate: {
13
+ type: 'string',
14
+ enum: ['pre-commit', 'pre-push', 'pre-deploy'],
15
+ description: 'Quality gate to run'
16
+ }
17
+ },
18
+ required: ['gate']
19
+ }
20
+ };
21
+ }
22
+
23
+ function createHandler({ config, quality, format }) {
24
+ return async (args) => {
25
+ const { gate } = args;
26
+ const cfg = config.load();
27
+ const projectRoot = cfg._projectRoot;
28
+
29
+ const availableGates = quality.listGates();
30
+ if (!availableGates.includes(gate)) {
31
+ return format.error(`Unknown gate: ${gate}`, [
32
+ `Available gates: ${availableGates.join(', ')}`
33
+ ]);
34
+ }
35
+
36
+ const results = quality.runGate(gate, projectRoot, {
37
+ verbose: false
38
+ });
39
+
40
+ return format.qualityResults(results);
41
+ };
42
+ }
43
+
44
+ module.exports = {
45
+ getToolDefinition,
46
+ createHandler
47
+ };