@girardmedia/bootspring 2.0.21 → 2.0.23

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 (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
package/src/cli/mcp.ts ADDED
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Bootspring MCP Command
3
+ * Control the MCP server
4
+ *
5
+ * @package bootspring
6
+ * @module cli/mcp
7
+ */
8
+
9
+ import * as path from 'path';
10
+
11
+ // Import JS modules with type interfaces
12
+ interface ConfigModule {
13
+ load(): {
14
+ _projectRoot: string;
15
+ _configPath?: string;
16
+ project?: { name: string };
17
+ };
18
+ }
19
+
20
+ interface ContextModule {
21
+ get(options: { config: unknown }): {
22
+ state: { phase: string; health: string };
23
+ };
24
+ }
25
+
26
+ interface ContextDetectorModule {
27
+ detectContext(prompt: string): {
28
+ matches: Array<unknown>;
29
+ suggestedAgents: string[];
30
+ };
31
+ }
32
+
33
+ interface PromptEnhancerModule {
34
+ enhance(prompt: string, detection: unknown, options: unknown): {
35
+ category: string;
36
+ } | null;
37
+ }
38
+
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
40
+ const config = require('../../core/config') as ConfigModule;
41
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
42
+ const utils = require('../../core/utils') as typeof import('../core/utils');
43
+
44
+ /**
45
+ * Start MCP server (for direct invocation by Claude Code)
46
+ */
47
+ function startServer(): void {
48
+ // When called directly by MCP, just require the server module
49
+ // This allows Claude Code to invoke it via stdio
50
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
51
+ require('../../mcp/server.js');
52
+ }
53
+
54
+ /**
55
+ * Show MCP status and configuration info
56
+ */
57
+ function showStatus(): void {
58
+ const cfg = config.load();
59
+
60
+ console.log(`
61
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring MCP Server${utils.COLORS.reset}
62
+ ${utils.COLORS.dim}Model Context Protocol integration${utils.COLORS.reset}
63
+
64
+ ${utils.COLORS.bold}Status${utils.COLORS.reset}
65
+ The MCP server runs on-demand when Claude Code invokes it.
66
+ It does not run as a persistent background service.
67
+
68
+ ${utils.COLORS.bold}Configuration${utils.COLORS.reset}
69
+ Project Root: ${cfg._projectRoot}
70
+ Config File: ${cfg._configPath || 'not found'}
71
+
72
+ ${utils.COLORS.bold}Available Tools${utils.COLORS.reset}
73
+ ${utils.COLORS.cyan}bootspring_context${utils.COLORS.reset} - Show/validate project context
74
+ ${utils.COLORS.cyan}bootspring_agent${utils.COLORS.reset} - Work with specialized agents
75
+ ${utils.COLORS.cyan}bootspring_todo${utils.COLORS.reset} - Manage todo items
76
+ ${utils.COLORS.cyan}bootspring_skill${utils.COLORS.reset} - Search code patterns
77
+ ${utils.COLORS.cyan}bootspring_quality${utils.COLORS.reset} - Run quality gates
78
+ ${utils.COLORS.cyan}bootspring_generate${utils.COLORS.reset} - Regenerate context
79
+ ${utils.COLORS.cyan}bootspring_plugin${utils.COLORS.reset} - Manage plugins
80
+ ${utils.COLORS.cyan}bootspring_dashboard${utils.COLORS.reset} - Dashboard info
81
+
82
+ ${utils.COLORS.bold}Resources${utils.COLORS.reset}
83
+ ${utils.COLORS.dim}bootspring://context/project${utils.COLORS.reset} - Project context JSON
84
+ ${utils.COLORS.dim}bootspring://context/claude.md${utils.COLORS.reset} - Generated AI context
85
+ ${utils.COLORS.dim}bootspring://context/todo.md${utils.COLORS.reset} - Todo list
86
+ ${utils.COLORS.dim}bootspring://agents/list${utils.COLORS.reset} - Available agents
87
+
88
+ ${utils.COLORS.bold}Setup${utils.COLORS.reset}
89
+ Ensure .mcp.json exists with:
90
+ ${utils.COLORS.dim}{
91
+ "mcpServers": {
92
+ "bootspring": {
93
+ "command": "npx",
94
+ "args": ["bootspring", "mcp"],
95
+ "env": {}
96
+ }
97
+ }
98
+ }${utils.COLORS.reset}
99
+
100
+ ${utils.COLORS.bold}Test${utils.COLORS.reset}
101
+ Run: ${utils.COLORS.cyan}bootspring mcp test${utils.COLORS.reset}
102
+ `);
103
+ }
104
+
105
+ /**
106
+ * Test MCP tools
107
+ */
108
+ async function testMcp(): Promise<void> {
109
+ console.log(`
110
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring MCP Test${utils.COLORS.reset}
111
+ `);
112
+
113
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
114
+ const context = require('../../core/context') as ContextModule;
115
+
116
+ // Test config loading
117
+ const spinner1 = utils.createSpinner('Testing config loader').start();
118
+ try {
119
+ const cfg = config.load();
120
+ if (cfg.project) {
121
+ spinner1.succeed(`Config loaded: ${cfg.project.name}`);
122
+ } else {
123
+ spinner1.warn('Config loaded but missing project info');
124
+ }
125
+ } catch (error) {
126
+ spinner1.fail(`Config error: ${(error as Error).message}`);
127
+ }
128
+
129
+ // Test context
130
+ const spinner2 = utils.createSpinner('Testing context detector').start();
131
+ try {
132
+ const cfg = config.load();
133
+ const ctx = context.get({ config: cfg });
134
+ spinner2.succeed(`Context loaded: phase=${ctx.state.phase}, health=${ctx.state.health}`);
135
+ } catch (error) {
136
+ spinner2.fail(`Context error: ${(error as Error).message}`);
137
+ }
138
+
139
+ // Test hooks
140
+ const spinner3 = utils.createSpinner('Testing context detection hooks').start();
141
+ try {
142
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
143
+ const detector = require('../../hooks/context-detector') as ContextDetectorModule;
144
+ const result = detector.detectContext('implement user authentication with Clerk');
145
+ if (result.matches.length > 0) {
146
+ spinner3.succeed(`Detection works: found ${result.matches.length} matches (${result.suggestedAgents.join(', ')})`);
147
+ } else {
148
+ spinner3.warn('Detection works but found no matches');
149
+ }
150
+ } catch (error) {
151
+ spinner3.fail(`Detection error: ${(error as Error).message}`);
152
+ }
153
+
154
+ // Test prompt enhancer
155
+ const spinner4 = utils.createSpinner('Testing prompt enhancer').start();
156
+ try {
157
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
158
+ const detector = require('../../hooks/context-detector') as ContextDetectorModule;
159
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
160
+ const enhancer = require('../../hooks/prompt-enhancer') as PromptEnhancerModule;
161
+ const detection = detector.detectContext('create a database schema for users');
162
+ const enhancement = enhancer.enhance('create a database schema', detection, {});
163
+ if (enhancement) {
164
+ spinner4.succeed(`Enhancer works: category=${enhancement.category}`);
165
+ } else {
166
+ spinner4.warn('Enhancer works but produced no enhancement');
167
+ }
168
+ } catch (error) {
169
+ spinner4.fail(`Enhancer error: ${(error as Error).message}`);
170
+ }
171
+
172
+ console.log(`
173
+ ${utils.COLORS.bold}Summary${utils.COLORS.reset}
174
+ All core MCP components are functional.
175
+ The server will work when invoked by Claude Code.
176
+
177
+ ${utils.COLORS.dim}Tip: Use the tools via Claude Code for full integration testing${utils.COLORS.reset}
178
+ `);
179
+ }
180
+
181
+ /**
182
+ * Generate .mcp.json configuration
183
+ */
184
+ function generateConfig(): void {
185
+ const cfg = config.load();
186
+ const mcpPath = path.join(cfg._projectRoot, '.mcp.json');
187
+
188
+ const mcpConfig = {
189
+ mcpServers: {
190
+ bootspring: {
191
+ command: 'npx',
192
+ args: ['bootspring', 'mcp'],
193
+ env: {}
194
+ }
195
+ }
196
+ };
197
+
198
+ const spinner = utils.createSpinner('Generating .mcp.json').start();
199
+
200
+ if (utils.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2))) {
201
+ spinner.succeed(`Created ${mcpPath}`);
202
+ console.log(`
203
+ ${utils.COLORS.dim}Contents:${utils.COLORS.reset}
204
+ ${JSON.stringify(mcpConfig, null, 2)}
205
+ `);
206
+ } else {
207
+ spinner.fail('Failed to create .mcp.json');
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Show MCP help
213
+ */
214
+ function showHelp(): void {
215
+ console.log(`
216
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring MCP${utils.COLORS.reset}
217
+ ${utils.COLORS.dim}Model Context Protocol server control${utils.COLORS.reset}
218
+
219
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
220
+ bootspring mcp [command]
221
+
222
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
223
+ ${utils.COLORS.cyan}(default)${utils.COLORS.reset} Start MCP server (used by Claude Code)
224
+ ${utils.COLORS.cyan}status${utils.COLORS.reset} Show MCP configuration and tools
225
+ ${utils.COLORS.cyan}test${utils.COLORS.reset} Test MCP components
226
+ ${utils.COLORS.cyan}config${utils.COLORS.reset} Generate .mcp.json file
227
+ ${utils.COLORS.cyan}help${utils.COLORS.reset} Show this help
228
+
229
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
230
+ bootspring mcp status # Check configuration
231
+ bootspring mcp test # Test components
232
+ bootspring mcp config # Generate .mcp.json
233
+ `);
234
+ }
235
+
236
+ /**
237
+ * Run MCP command
238
+ */
239
+ export async function run(args: string[]): Promise<void> {
240
+ const subcommand = args[0];
241
+
242
+ // Default: start the MCP server (for Claude Code invocation)
243
+ if (!subcommand) {
244
+ startServer();
245
+ return;
246
+ }
247
+
248
+ switch (subcommand) {
249
+ case 'status':
250
+ showStatus();
251
+ break;
252
+
253
+ case 'test':
254
+ await testMcp();
255
+ break;
256
+
257
+ case 'config':
258
+ case 'init':
259
+ case 'setup':
260
+ generateConfig();
261
+ break;
262
+
263
+ case 'help':
264
+ case '-h':
265
+ case '--help':
266
+ showHelp();
267
+ break;
268
+
269
+ default:
270
+ utils.print.error(`Unknown subcommand: ${subcommand}`);
271
+ showHelp();
272
+ }
273
+ }
274
+
275
+ export { startServer, showStatus, testMcp };
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Bootspring Memory Command
5
+ * View and manage git-based project memory
6
+ *
7
+ * @package bootspring
8
+ * @module cli/memory
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+
13
+ // Colors
14
+ const c = {
15
+ reset: '\x1b[0m',
16
+ bold: '\x1b[1m',
17
+ dim: '\x1b[2m',
18
+ green: '\x1b[32m',
19
+ blue: '\x1b[34m',
20
+ yellow: '\x1b[33m',
21
+ cyan: '\x1b[36m',
22
+ red: '\x1b[31m',
23
+ magenta: '\x1b[35m'
24
+ };
25
+
26
+ // Type definitions for git-memory module
27
+ interface Learning {
28
+ hash: string;
29
+ summary: string;
30
+ date: string;
31
+ details?: string;
32
+ categories: string[];
33
+ file?: string;
34
+ }
35
+
36
+ interface ExtractResult {
37
+ learnings: Learning[];
38
+ total: number;
39
+ error?: string;
40
+ }
41
+
42
+ interface CategoryConfig {
43
+ icon: string;
44
+ label: string;
45
+ patterns: RegExp[];
46
+ }
47
+
48
+ interface RepoStats {
49
+ totalCommits: number;
50
+ contributors: number;
51
+ weeklyPace: number;
52
+ firstCommit: string | null;
53
+ }
54
+
55
+ interface GitMemoryModule {
56
+ isGitRepo(): boolean;
57
+ extractLearnings(options?: { limit?: number; since?: string }): ExtractResult;
58
+ groupByCategory(learnings: Learning[]): Record<string, Learning[]>;
59
+ searchLearnings(learnings: Learning[], query: string): Learning[];
60
+ getLearningsForFiles(files: string[]): Learning[];
61
+ toMarkdown(learnings: Learning[], options?: { title?: string; maxPerCategory?: number }): string;
62
+ getRepoStats(): RepoStats | null;
63
+ MEMORY_CATEGORIES: Record<string, CategoryConfig>;
64
+ }
65
+
66
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
67
+ const gitMemory = require('../../intelligence/git-memory') as GitMemoryModule;
68
+
69
+ interface MemoryOptions {
70
+ limit?: number;
71
+ since?: string;
72
+ }
73
+
74
+ /**
75
+ * Display learnings summary
76
+ */
77
+ function showSummary(options: MemoryOptions = {}): void {
78
+ const { limit = 30, since = '3 months ago' } = options;
79
+
80
+ console.log(`\n${c.cyan}${c.bold}⚡ Project Memory${c.reset}\n`);
81
+
82
+ if (!gitMemory.isGitRepo()) {
83
+ console.log(`${c.red}Not a git repository${c.reset}`);
84
+ return;
85
+ }
86
+
87
+ const result = gitMemory.extractLearnings({ limit, since });
88
+
89
+ if (result.error) {
90
+ console.log(`${c.red}Error: ${result.error}${c.reset}`);
91
+ return;
92
+ }
93
+
94
+ if (result.learnings.length === 0) {
95
+ console.log(`${c.yellow}No significant learnings found in recent commits${c.reset}`);
96
+ console.log(`${c.dim}Try using more descriptive commit messages${c.reset}`);
97
+ return;
98
+ }
99
+
100
+ // Group by category
101
+ const grouped = gitMemory.groupByCategory(result.learnings);
102
+
103
+ console.log(`${c.dim}Extracted ${result.learnings.length} learnings from ${result.total} commits${c.reset}\n`);
104
+
105
+ for (const [category, items] of Object.entries(grouped)) {
106
+ const config = gitMemory.MEMORY_CATEGORIES[category] || { icon: '📝', label: category };
107
+
108
+ console.log(`${c.bold}${config.icon} ${config.label}${c.reset} (${items.length})`);
109
+
110
+ for (const item of items.slice(0, 3)) {
111
+ console.log(` ${c.dim}[${item.hash}]${c.reset} ${item.summary}`);
112
+ }
113
+
114
+ if (items.length > 3) {
115
+ console.log(` ${c.dim}... and ${items.length - 3} more${c.reset}`);
116
+ }
117
+
118
+ console.log('');
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Search learnings
124
+ */
125
+ function searchMemory(query: string): void {
126
+ console.log(`\n${c.cyan}${c.bold}⚡ Search Memory${c.reset}\n`);
127
+
128
+ if (!query) {
129
+ console.log(`${c.red}Usage: bootspring memory search <query>${c.reset}`);
130
+ return;
131
+ }
132
+
133
+ const result = gitMemory.extractLearnings({ limit: 100 });
134
+ const matches = gitMemory.searchLearnings(result.learnings, query);
135
+
136
+ console.log(`${c.dim}Found ${matches.length} matches for "${query}"${c.reset}\n`);
137
+
138
+ for (const match of matches.slice(0, 15)) {
139
+ const icons = match.categories
140
+ .map(cat => gitMemory.MEMORY_CATEGORIES[cat]?.icon || '📝')
141
+ .join('');
142
+
143
+ console.log(`${icons} ${c.bold}${match.summary}${c.reset}`);
144
+ console.log(` ${c.dim}[${match.hash}] ${match.date}${c.reset}`);
145
+
146
+ if (match.details) {
147
+ const firstLine = match.details.split('\n')[0];
148
+ if (firstLine) {
149
+ const preview = firstLine.slice(0, 80);
150
+ console.log(` ${c.dim}${preview}${c.reset}`);
151
+ }
152
+ }
153
+
154
+ console.log('');
155
+ }
156
+
157
+ if (matches.length > 15) {
158
+ console.log(`${c.dim}... and ${matches.length - 15} more matches${c.reset}`);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Show learnings for specific files
164
+ */
165
+ function showForFiles(files: string[]): void {
166
+ console.log(`\n${c.cyan}${c.bold}⚡ File Memory${c.reset}\n`);
167
+
168
+ if (!files || files.length === 0) {
169
+ console.log(`${c.red}Usage: bootspring memory files <file1> [file2...]${c.reset}`);
170
+ return;
171
+ }
172
+
173
+ const learnings = gitMemory.getLearningsForFiles(files);
174
+
175
+ if (learnings.length === 0) {
176
+ console.log(`${c.yellow}No learnings found for these files${c.reset}`);
177
+ return;
178
+ }
179
+
180
+ console.log(`${c.dim}Learnings related to: ${files.join(', ')}${c.reset}\n`);
181
+
182
+ for (const learning of learnings) {
183
+ const icons = learning.categories
184
+ .map(cat => gitMemory.MEMORY_CATEGORIES[cat]?.icon || '📝')
185
+ .join('');
186
+
187
+ console.log(`${icons} ${c.bold}${learning.summary}${c.reset}`);
188
+ console.log(` ${c.dim}[${learning.hash}] File: ${learning.file}${c.reset}`);
189
+ console.log('');
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Export learnings to markdown
195
+ */
196
+ function exportMarkdown(outputPath?: string): void {
197
+ const result = gitMemory.extractLearnings({ limit: 50 });
198
+
199
+ if (result.error) {
200
+ console.log(`${c.red}Error: ${result.error}${c.reset}`);
201
+ return;
202
+ }
203
+
204
+ const markdown = gitMemory.toMarkdown(result.learnings, {
205
+ title: 'Project Learnings',
206
+ maxPerCategory: 10
207
+ });
208
+
209
+ if (outputPath) {
210
+ fs.writeFileSync(outputPath, markdown);
211
+ console.log(`${c.green}Exported to ${outputPath}${c.reset}`);
212
+ } else {
213
+ console.log(markdown);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Show repo stats
219
+ */
220
+ function showStats(): void {
221
+ console.log(`\n${c.cyan}${c.bold}⚡ Repository Stats${c.reset}\n`);
222
+
223
+ const stats = gitMemory.getRepoStats();
224
+
225
+ if (!stats) {
226
+ console.log(`${c.red}Not a git repository${c.reset}`);
227
+ return;
228
+ }
229
+
230
+ console.log(` ${c.bold}Total Commits:${c.reset} ${stats.totalCommits}`);
231
+ console.log(` ${c.bold}Contributors:${c.reset} ${stats.contributors}`);
232
+ console.log(` ${c.bold}Weekly Activity:${c.reset} ${stats.weeklyPace} commits`);
233
+ console.log(` ${c.bold}First Commit:${c.reset} ${stats.firstCommit || 'unknown'}`);
234
+ console.log('');
235
+
236
+ // Show category breakdown
237
+ const result = gitMemory.extractLearnings({ limit: 100 });
238
+ const grouped = gitMemory.groupByCategory(result.learnings);
239
+
240
+ console.log(`${c.bold}Learning Categories:${c.reset}\n`);
241
+
242
+ for (const [category, items] of Object.entries(grouped).sort((a, b) => b[1].length - a[1].length)) {
243
+ const config = gitMemory.MEMORY_CATEGORIES[category] || { icon: '📝', label: category };
244
+ const bar = '█'.repeat(Math.min(items.length, 20));
245
+ console.log(` ${config.icon} ${config.label.padEnd(20)} ${c.cyan}${bar}${c.reset} ${items.length}`);
246
+ }
247
+
248
+ console.log('');
249
+ }
250
+
251
+ /**
252
+ * Show help
253
+ */
254
+ function showHelp(): void {
255
+ console.log(`
256
+ ${c.bold}Bootspring Memory${c.reset}
257
+
258
+ Extract and search project learnings from git history.
259
+
260
+ ${c.cyan}Usage:${c.reset}
261
+ bootspring memory <command> [options]
262
+
263
+ ${c.cyan}Commands:${c.reset}
264
+ summary Show categorized learning summary (default)
265
+ search <query> Search learnings by keyword
266
+ files <file...> Show learnings for specific files
267
+ export [path] Export learnings to markdown
268
+ stats Show repository statistics
269
+ categories List memory categories
270
+ help Show this help
271
+
272
+ ${c.cyan}Options:${c.reset}
273
+ --limit <n> Number of commits to analyze (default: 30)
274
+ --since <date> How far back to look (default: "3 months ago")
275
+
276
+ ${c.cyan}Examples:${c.reset}
277
+ bootspring memory summary
278
+ bootspring memory search "authentication"
279
+ bootspring memory files src/api/auth.ts
280
+ bootspring memory export LEARNINGS.md
281
+ bootspring memory stats
282
+
283
+ ${c.cyan}Categories:${c.reset}
284
+ ${Object.entries(gitMemory.MEMORY_CATEGORIES)
285
+ .map(([key, cfg]) => ` ${cfg.icon} ${key.padEnd(15)} ${cfg.label}`)
286
+ .join('\n')}
287
+ `);
288
+ }
289
+
290
+ /**
291
+ * Main CLI handler
292
+ */
293
+ export async function run(args: string[]): Promise<void> {
294
+ const command = args[0] || 'summary';
295
+
296
+ // Parse options
297
+ const options: MemoryOptions = {};
298
+ for (let i = 0; i < args.length; i++) {
299
+ const nextArg = args[i + 1];
300
+ if (args[i] === '--limit' && nextArg) {
301
+ options.limit = parseInt(nextArg, 10);
302
+ }
303
+ if (args[i] === '--since' && nextArg) {
304
+ options.since = nextArg;
305
+ }
306
+ }
307
+
308
+ switch (command) {
309
+ case 'summary':
310
+ case 'show':
311
+ showSummary(options);
312
+ break;
313
+
314
+ case 'search':
315
+ searchMemory(args.slice(1).filter(a => !a.startsWith('--')).join(' '));
316
+ break;
317
+
318
+ case 'files':
319
+ case 'file':
320
+ showForFiles(args.slice(1).filter(a => !a.startsWith('--')));
321
+ break;
322
+
323
+ case 'export':
324
+ exportMarkdown(args[1]);
325
+ break;
326
+
327
+ case 'stats':
328
+ showStats();
329
+ break;
330
+
331
+ case 'categories':
332
+ console.log(`\n${c.bold}Memory Categories:${c.reset}\n`);
333
+ for (const [key, cfg] of Object.entries(gitMemory.MEMORY_CATEGORIES)) {
334
+ console.log(` ${cfg.icon} ${key.padEnd(15)} ${c.dim}${cfg.label}${c.reset}`);
335
+ console.log(` ${c.dim}Patterns: ${cfg.patterns.slice(0, 3).map(p => p.source).join(', ')}...${c.reset}`);
336
+ }
337
+ console.log('');
338
+ break;
339
+
340
+ case 'help':
341
+ case '--help':
342
+ case '-h':
343
+ default:
344
+ showHelp();
345
+ }
346
+ }