@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,213 @@
1
+ /**
2
+ * MCP registry composition
3
+ * Central place for tool/resource definitions and handler wiring.
4
+ */
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ const config = require('../core/config');
10
+ const context = require('../core/context');
11
+ const todoModule = require('../cli/todo');
12
+ const agentModule = require('../cli/agent');
13
+ const generateModule = require('../cli/generate');
14
+ const intelligence = require('../intelligence');
15
+ const gitMemory = require('../intelligence/git-memory');
16
+ const prdModule = require('../intelligence/prd');
17
+ const quality = require('../quality');
18
+ const skills = require('../skills');
19
+ const entitlements = require('../core/entitlements');
20
+ const telemetry = require('../core/telemetry');
21
+ const capabilities = require('./capabilities');
22
+ const format = require('./response-formatter');
23
+
24
+ const agentTool = require('./tools/agent-tool');
25
+ const capabilitiesTool = require('./tools/capabilities-tool');
26
+ const contextTool = require('./tools/context-tool');
27
+ const dashboardTool = require('./tools/dashboard-tool');
28
+ const generateTool = require('./tools/generate-tool');
29
+ const loopTool = require('./tools/loop-tool');
30
+ const memoryTool = require('./tools/memory-tool');
31
+ const orchestratorTool = require('./tools/orchestrator-tool');
32
+ const pluginTool = require('./tools/plugin-tool');
33
+ const qualityTool = require('./tools/quality-tool');
34
+ const skillTool = require('./tools/skill-tool');
35
+ const telemetryTool = require('./tools/telemetry-tool');
36
+ const todoTool = require('./tools/todo-tool');
37
+
38
+ const recommendations = intelligence.createRecommendationsEngine({
39
+ intelligence,
40
+ telemetry,
41
+ skills,
42
+ entitlements
43
+ });
44
+
45
+ function trackTelemetry(event, payload) {
46
+ try {
47
+ telemetry.emitEvent(event, payload);
48
+ } catch {
49
+ // Do not break MCP responses due to telemetry failures.
50
+ }
51
+ }
52
+
53
+ const TOOLS = [
54
+ contextTool.getToolDefinition(),
55
+ agentTool.getToolDefinition(),
56
+ todoTool.getToolDefinition(),
57
+ generateTool.getToolDefinition(),
58
+ skillTool.getToolDefinition(),
59
+ qualityTool.getToolDefinition(),
60
+ dashboardTool.getToolDefinition(),
61
+ pluginTool.getToolDefinition(),
62
+ capabilitiesTool.getToolDefinition(),
63
+ telemetryTool.getToolDefinition(),
64
+ orchestratorTool.getToolDefinition(),
65
+ loopTool.getToolDefinition(),
66
+ memoryTool.getToolDefinition()
67
+ ];
68
+
69
+ const RESOURCES = [
70
+ {
71
+ uri: 'bootspring://context/project',
72
+ name: 'Project Context',
73
+ description: 'Current project context and configuration',
74
+ mimeType: 'application/json'
75
+ },
76
+ {
77
+ uri: 'bootspring://context/claude.md',
78
+ name: 'CLAUDE.md Content',
79
+ description: 'Generated AI context file',
80
+ mimeType: 'text/markdown'
81
+ },
82
+ {
83
+ uri: 'bootspring://context/todo.md',
84
+ name: 'Todo List',
85
+ description: 'Project todo list',
86
+ mimeType: 'text/markdown'
87
+ },
88
+ {
89
+ uri: 'bootspring://agents/list',
90
+ name: 'Agent List',
91
+ description: 'Available specialized agents',
92
+ mimeType: 'application/json'
93
+ }
94
+ ];
95
+
96
+ const toolHandlers = {
97
+ bootspring_capabilities: capabilitiesTool.createHandler({ capabilities }),
98
+ bootspring_orchestrator: orchestratorTool.createHandler({
99
+ intelligence,
100
+ entitlements,
101
+ recommendations,
102
+ trackTelemetry,
103
+ format
104
+ }),
105
+ bootspring_context: contextTool.createHandler({
106
+ config,
107
+ context,
108
+ format
109
+ }),
110
+ bootspring_skill: skillTool.createHandler({
111
+ skills,
112
+ entitlements,
113
+ trackTelemetry
114
+ }),
115
+ bootspring_agent: agentTool.createHandler({
116
+ agentModule,
117
+ format
118
+ }),
119
+ bootspring_todo: todoTool.createHandler({
120
+ config,
121
+ todoModule,
122
+ format
123
+ }),
124
+ bootspring_quality: qualityTool.createHandler({
125
+ config,
126
+ quality,
127
+ format
128
+ }),
129
+ bootspring_generate: generateTool.createHandler({
130
+ config,
131
+ context,
132
+ generateModule
133
+ }),
134
+ bootspring_dashboard: dashboardTool.createHandler({ config }),
135
+ bootspring_plugin: pluginTool.createHandler({ config }),
136
+ bootspring_loop: loopTool.createHandler({
137
+ prdModule,
138
+ format
139
+ }),
140
+ bootspring_memory: memoryTool.createHandler({ gitMemory }),
141
+ bootspring_telemetry: telemetryTool.createHandler({ telemetry }),
142
+ };
143
+
144
+ const resourceHandlers = {
145
+ 'bootspring://context/project': async () => {
146
+ const cfg = config.load();
147
+ const ctx = context.get({ config: cfg });
148
+ return {
149
+ contents: [{
150
+ uri: 'bootspring://context/project',
151
+ mimeType: 'application/json',
152
+ text: JSON.stringify(ctx, null, 2)
153
+ }]
154
+ };
155
+ },
156
+
157
+ 'bootspring://context/claude.md': async () => {
158
+ const cfg = config.load();
159
+ const claudePath = path.join(cfg._projectRoot, cfg.paths?.context || 'CLAUDE.md');
160
+ let content = '';
161
+ if (fs.existsSync(claudePath)) {
162
+ content = fs.readFileSync(claudePath, 'utf-8');
163
+ }
164
+ return {
165
+ contents: [{
166
+ uri: 'bootspring://context/claude.md',
167
+ mimeType: 'text/markdown',
168
+ text: content
169
+ }]
170
+ };
171
+ },
172
+
173
+ 'bootspring://context/todo.md': async () => {
174
+ const cfg = config.load();
175
+ const todoPath = path.join(cfg._projectRoot, cfg.paths?.todo || 'todo.md');
176
+ let content = '';
177
+ if (fs.existsSync(todoPath)) {
178
+ content = fs.readFileSync(todoPath, 'utf-8');
179
+ }
180
+ return {
181
+ contents: [{
182
+ uri: 'bootspring://context/todo.md',
183
+ mimeType: 'text/markdown',
184
+ text: content
185
+ }]
186
+ };
187
+ },
188
+
189
+ 'bootspring://agents/list': async () => {
190
+ const agents = Object.entries(agentModule.AGENTS).map(([id, agent]) => ({
191
+ id,
192
+ name: agent.name,
193
+ category: agent.category,
194
+ description: agent.description,
195
+ expertise: agent.expertise
196
+ }));
197
+ return {
198
+ contents: [{
199
+ uri: 'bootspring://agents/list',
200
+ mimeType: 'application/json',
201
+ text: JSON.stringify(agents, null, 2)
202
+ }]
203
+ };
204
+ }
205
+ };
206
+
207
+ module.exports = {
208
+ TOOLS,
209
+ RESOURCES,
210
+ toolHandlers,
211
+ resourceHandlers,
212
+ trackTelemetry
213
+ };
@@ -0,0 +1,462 @@
1
+ /**
2
+ * Bootspring MCP Response Formatter
3
+ * Creates well-structured, human-friendly tool responses
4
+ *
5
+ * @package bootspring
6
+ * @module mcp
7
+ */
8
+
9
+ /**
10
+ * Format a success response with structured output
11
+ * @param {object} options - Response options
12
+ * @param {string} options.summary - Brief summary of what happened
13
+ * @param {any} options.data - Main data payload
14
+ * @param {string[]} [options.hints] - Optional hints for next actions
15
+ * @param {object} [options.meta] - Optional metadata
16
+ * @returns {object} MCP-formatted response
17
+ */
18
+ function success({ summary, data, hints = [], meta = {} }) {
19
+ const sections = [];
20
+
21
+ // Summary section
22
+ if (summary) {
23
+ sections.push(`✓ ${summary}`);
24
+ }
25
+
26
+ // Data section
27
+ if (data !== undefined && data !== null) {
28
+ if (typeof data === 'string') {
29
+ sections.push(data);
30
+ } else {
31
+ sections.push(JSON.stringify(data, null, 2));
32
+ }
33
+ }
34
+
35
+ // Hints section
36
+ if (hints.length > 0) {
37
+ sections.push('\n💡 Hints:');
38
+ hints.forEach(hint => sections.push(` • ${hint}`));
39
+ }
40
+
41
+ return {
42
+ content: [{
43
+ type: 'text',
44
+ text: sections.join('\n')
45
+ }],
46
+ _meta: meta
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Format an error response
52
+ * @param {string} message - Error message
53
+ * @param {string[]} [suggestions] - Helpful suggestions
54
+ * @returns {object} MCP-formatted error response
55
+ */
56
+ function error(message, suggestions = []) {
57
+ const sections = [`✗ Error: ${message}`];
58
+
59
+ if (suggestions.length > 0) {
60
+ sections.push('\n💡 Suggestions:');
61
+ suggestions.forEach(s => sections.push(` • ${s}`));
62
+ }
63
+
64
+ return {
65
+ content: [{
66
+ type: 'text',
67
+ text: sections.join('\n')
68
+ }],
69
+ isError: true
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Format a list response with items
75
+ * @param {object} options - List options
76
+ * @param {string} options.title - List title
77
+ * @param {any[]} options.items - Items to list
78
+ * @param {function} options.formatter - Function to format each item
79
+ * @param {string} [options.emptyMessage] - Message when list is empty
80
+ * @param {string[]} [options.hints] - Optional hints
81
+ * @returns {object} MCP-formatted response
82
+ */
83
+ function list({ title, items, formatter, emptyMessage = 'No items found', hints = [] }) {
84
+ const sections = [];
85
+
86
+ sections.push(`📋 ${title}`);
87
+ sections.push(` ${items.length} item(s)\n`);
88
+
89
+ if (items.length === 0) {
90
+ sections.push(` ${emptyMessage}`);
91
+ } else {
92
+ items.forEach((item, index) => {
93
+ const formatted = formatter ? formatter(item, index) : String(item);
94
+ sections.push(formatted);
95
+ });
96
+ }
97
+
98
+ if (hints.length > 0) {
99
+ sections.push('\n💡 Hints:');
100
+ hints.forEach(hint => sections.push(` • ${hint}`));
101
+ }
102
+
103
+ return {
104
+ content: [{
105
+ type: 'text',
106
+ text: sections.join('\n')
107
+ }]
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Format agent details for display
113
+ * @param {string} id - Agent ID
114
+ * @param {object} agent - Agent data
115
+ * @returns {object} MCP-formatted response
116
+ */
117
+ function agentDetails(id, agent) {
118
+ const sections = [
119
+ `🤖 Agent: ${agent.name}`,
120
+ ` ID: ${id}`,
121
+ ` Category: ${agent.category || 'General'}`,
122
+ '',
123
+ agent.description,
124
+ '',
125
+ '📚 Expertise:',
126
+ ...agent.expertise.map(e => ` • ${e}`),
127
+ ''
128
+ ];
129
+
130
+ if (agent.systemPrompt) {
131
+ sections.push('📝 System Prompt Available: Yes');
132
+ }
133
+
134
+ sections.push(
135
+ '',
136
+ '💡 Usage:',
137
+ ' Invoke this agent with a topic to get specialized assistance'
138
+ );
139
+
140
+ return {
141
+ content: [{
142
+ type: 'text',
143
+ text: sections.join('\n')
144
+ }]
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Format skill details for display
150
+ * @param {string} id - Skill ID
151
+ * @param {object} skill - Skill data
152
+ * @param {string} [content] - Skill content (markdown)
153
+ * @returns {object} MCP-formatted response
154
+ */
155
+ function skillDetails(id, skill, content = null) {
156
+ const sections = [
157
+ `📖 Skill: ${skill.name}`,
158
+ ` ID: ${id}`,
159
+ '',
160
+ skill.description,
161
+ ''
162
+ ];
163
+
164
+ if (skill.tags && skill.tags.length > 0) {
165
+ sections.push(`🏷️ Tags: ${skill.tags.join(', ')}`);
166
+ sections.push('');
167
+ }
168
+
169
+ if (content) {
170
+ sections.push('---', '', content);
171
+ }
172
+
173
+ return {
174
+ content: [{
175
+ type: 'text',
176
+ text: sections.join('\n')
177
+ }]
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Format todo list for display
183
+ * @param {object[]} pending - Pending todos
184
+ * @param {object[]} completed - Completed todos
185
+ * @returns {object} MCP-formatted response
186
+ */
187
+ function todoList(pending, completed) {
188
+ const sections = [
189
+ '📋 Project Todo List',
190
+ ''
191
+ ];
192
+
193
+ sections.push(`⏳ Pending (${pending.length}):`);
194
+ if (pending.length === 0) {
195
+ sections.push(' No pending tasks! 🎉');
196
+ } else {
197
+ pending.forEach((t, i) => {
198
+ sections.push(` ${i}. [ ] ${t.text}`);
199
+ });
200
+ }
201
+
202
+ sections.push('');
203
+ sections.push(`✅ Completed (${completed.length}):`);
204
+ if (completed.length === 0) {
205
+ sections.push(' No completed tasks yet');
206
+ } else {
207
+ completed.slice(0, 5).forEach(t => {
208
+ sections.push(` [x] ${t.text}`);
209
+ });
210
+ if (completed.length > 5) {
211
+ sections.push(` ... and ${completed.length - 5} more`);
212
+ }
213
+ }
214
+
215
+ sections.push(
216
+ '',
217
+ '💡 Actions:',
218
+ ' • Add: bootspring_todo action=add text="your task"',
219
+ ' • Complete: bootspring_todo action=done index=0',
220
+ ' • Remove: bootspring_todo action=remove index=0'
221
+ );
222
+
223
+ return {
224
+ content: [{
225
+ type: 'text',
226
+ text: sections.join('\n')
227
+ }]
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Format quality gate results
233
+ * @param {object} results - Gate execution results
234
+ * @returns {object} MCP-formatted response
235
+ */
236
+ function qualityResults(results) {
237
+ const statusIcon = results.status === 'pass' ? '✅' : '❌';
238
+ const sections = [
239
+ `${statusIcon} Quality Gate: ${results.name || results.gate}`,
240
+ ` Status: ${results.status.toUpperCase()}`,
241
+ ` Passed: ${results.passed} | Failed: ${results.failed} | Skipped: ${results.skipped || 0}`,
242
+ ''
243
+ ];
244
+
245
+ sections.push('📊 Check Results:');
246
+ for (const check of results.results) {
247
+ const icon = check.status === 'pass'
248
+ ? '✓'
249
+ : check.status === 'fail'
250
+ ? '✗'
251
+ : '○';
252
+ sections.push(` ${icon} ${check.name}: ${check.status}`);
253
+ if (check.status === 'fail' && check.message) {
254
+ const shortMessage = check.message.split('\n')[0].slice(0, 60);
255
+ sections.push(` ${shortMessage}...`);
256
+ }
257
+ }
258
+
259
+ if (results.status === 'fail') {
260
+ sections.push(
261
+ '',
262
+ '💡 Next Steps:',
263
+ ' • Fix the failing checks and re-run the gate',
264
+ ' • Use --skip <check> to skip specific checks if needed'
265
+ );
266
+ }
267
+
268
+ return {
269
+ content: [{
270
+ type: 'text',
271
+ text: sections.join('\n')
272
+ }]
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Format context validation results
278
+ * @param {object} validation - Validation results
279
+ * @returns {object} MCP-formatted response
280
+ */
281
+ function contextValidation(validation) {
282
+ const statusIcon = validation.valid ? '✅' : '⚠️';
283
+ const sections = [
284
+ `${statusIcon} Project Validation`,
285
+ ` Valid: ${validation.valid}`,
286
+ ''
287
+ ];
288
+
289
+ if (validation.errors && validation.errors.length > 0) {
290
+ sections.push('❌ Errors:');
291
+ validation.errors.forEach(e => sections.push(` • ${e}`));
292
+ sections.push('');
293
+ }
294
+
295
+ if (validation.warnings && validation.warnings.length > 0) {
296
+ sections.push('⚠️ Warnings:');
297
+ validation.warnings.forEach(w => sections.push(` • ${w}`));
298
+ sections.push('');
299
+ }
300
+
301
+ if (validation.suggestions && validation.suggestions.length > 0) {
302
+ sections.push('💡 Suggestions:');
303
+ validation.suggestions.forEach(s => sections.push(` • ${s}`));
304
+ }
305
+
306
+ return {
307
+ content: [{
308
+ type: 'text',
309
+ text: sections.join('\n')
310
+ }]
311
+ };
312
+ }
313
+
314
+ /**
315
+ * Format project context summary
316
+ * @param {object} ctx - Project context
317
+ * @returns {object} MCP-formatted response
318
+ */
319
+ function contextSummary(ctx) {
320
+ const sections = [
321
+ `📁 Project: ${ctx.project?.name || 'Unknown'}`,
322
+ ` Version: ${ctx.project?.version || 'N/A'}`,
323
+ ''
324
+ ];
325
+
326
+ if (ctx.stack) {
327
+ sections.push('🛠️ Tech Stack:');
328
+ if (ctx.stack.framework) sections.push(` Framework: ${ctx.stack.framework}`);
329
+ if (ctx.stack.language) sections.push(` Language: ${ctx.stack.language}`);
330
+ if (ctx.stack.database) sections.push(` Database: ${ctx.stack.database}`);
331
+ sections.push('');
332
+ }
333
+
334
+ if (ctx.plugins && Object.keys(ctx.plugins).length > 0) {
335
+ sections.push('🔌 Active Plugins:');
336
+ Object.keys(ctx.plugins).forEach(p => {
337
+ sections.push(` • ${p}`);
338
+ });
339
+ sections.push('');
340
+ }
341
+
342
+ sections.push(
343
+ '💡 Commands:',
344
+ ' • bootspring_context action=validate - Check project health',
345
+ ' • bootspring_generate - Regenerate CLAUDE.md',
346
+ ' • bootspring_agent action=list - See available agents'
347
+ );
348
+
349
+ return {
350
+ content: [{
351
+ type: 'text',
352
+ text: sections.join('\n')
353
+ }]
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Format orchestrator status
359
+ * @param {object} status - Orchestrator status
360
+ * @returns {object} MCP-formatted response
361
+ */
362
+ function orchestratorStatus(status) {
363
+ const sections = [
364
+ '🎯 Orchestrator Status',
365
+ '',
366
+ ` Phase: ${status.currentPhase || 'Unknown'}`,
367
+ ` Available Agents: ${status.availableAgents || 0}`,
368
+ ` Active Workflow: ${status.activeWorkflow || 'None'}`,
369
+ ''
370
+ ];
371
+
372
+ if (status.activeWorkflowSignalProgress) {
373
+ sections.push(
374
+ ` Checkpoint Signals: ${status.activeWorkflowSignalProgress.completedSignals.length}/${status.activeWorkflowSignalProgress.totalSignals}`,
375
+ ''
376
+ );
377
+ }
378
+
379
+ if (status.recentSuggestions && status.recentSuggestions.length > 0) {
380
+ sections.push('💡 Recent Suggestions:');
381
+ status.recentSuggestions.forEach(s => {
382
+ sections.push(` • ${s.text || s}`);
383
+ });
384
+ }
385
+
386
+ return {
387
+ content: [{
388
+ type: 'text',
389
+ text: sections.join('\n')
390
+ }]
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Format PRD/loop status
396
+ * @param {object} prd - PRD data
397
+ * @param {object} progress - Progress info
398
+ * @returns {object} MCP-formatted response
399
+ */
400
+ function loopStatus(prd, progress) {
401
+ const sections = [
402
+ `📋 PRD: ${prd.name}`,
403
+ '',
404
+ ` Progress: ${progress.completed}/${progress.total} stories (${Math.round(progress.percentage)}%)`,
405
+ ''
406
+ ];
407
+
408
+ const progressBar = generateProgressBar(progress.percentage);
409
+ sections.push(` ${progressBar}`);
410
+ sections.push('');
411
+
412
+ sections.push('📖 Stories:');
413
+ prd.stories.forEach(s => {
414
+ const icon = s.status === 'complete'
415
+ ? '✅'
416
+ : s.status === 'in-progress'
417
+ ? '🔄'
418
+ : '⏳';
419
+ sections.push(` ${icon} ${s.title} (${s.status})`);
420
+ });
421
+
422
+ sections.push(
423
+ '',
424
+ '💡 Actions:',
425
+ ' • bootspring_loop action=next - Get next story to work on',
426
+ ' • bootspring_loop action=complete storyId=X - Mark story complete'
427
+ );
428
+
429
+ return {
430
+ content: [{
431
+ type: 'text',
432
+ text: sections.join('\n')
433
+ }]
434
+ };
435
+ }
436
+
437
+ /**
438
+ * Generate a simple progress bar
439
+ * @param {number} percentage - Progress percentage (0-100)
440
+ * @returns {string}
441
+ */
442
+ function generateProgressBar(percentage) {
443
+ const width = 20;
444
+ const filled = Math.round((percentage / 100) * width);
445
+ const empty = width - filled;
446
+ return '[' + '█'.repeat(filled) + '░'.repeat(empty) + '] ' + Math.round(percentage) + '%';
447
+ }
448
+
449
+ module.exports = {
450
+ success,
451
+ error,
452
+ list,
453
+ agentDetails,
454
+ skillDetails,
455
+ todoList,
456
+ qualityResults,
457
+ contextValidation,
458
+ contextSummary,
459
+ orchestratorStatus,
460
+ loopStatus,
461
+ generateProgressBar
462
+ };