@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,551 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bootspring Git Memory
|
|
5
|
+
*
|
|
6
|
+
* Extracts learnings, patterns, and decisions from git history.
|
|
7
|
+
* Replaces Ralph's progress.txt with structured git-based memory.
|
|
8
|
+
*
|
|
9
|
+
* @package bootspring
|
|
10
|
+
* @module intelligence/git-memory
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Memory categories extracted from commits
|
|
18
|
+
*/
|
|
19
|
+
const MEMORY_CATEGORIES = {
|
|
20
|
+
DECISION: {
|
|
21
|
+
patterns: [
|
|
22
|
+
/decided to/i, /chose to/i, /switched to/i, /migrated to/i,
|
|
23
|
+
/now using/i, /replaced .+ with/i, /prefer/i
|
|
24
|
+
],
|
|
25
|
+
icon: '🎯',
|
|
26
|
+
label: 'Decisions'
|
|
27
|
+
},
|
|
28
|
+
BUGFIX: {
|
|
29
|
+
patterns: [
|
|
30
|
+
/fix(ed|es)?:/i, /bug(fix)?:/i, /resolved/i, /patched/i,
|
|
31
|
+
/was causing/i, /issue with/i, /broke/i, /fixed by/i
|
|
32
|
+
],
|
|
33
|
+
icon: '🐛',
|
|
34
|
+
label: 'Bug Fixes & Gotchas'
|
|
35
|
+
},
|
|
36
|
+
PATTERN: {
|
|
37
|
+
patterns: [
|
|
38
|
+
/pattern/i, /refactor/i, /abstracted/i, /extracted/i,
|
|
39
|
+
/standardized/i, /convention/i, /approach/i
|
|
40
|
+
],
|
|
41
|
+
icon: '🔧',
|
|
42
|
+
label: 'Patterns & Conventions'
|
|
43
|
+
},
|
|
44
|
+
ARCHITECTURE: {
|
|
45
|
+
patterns: [
|
|
46
|
+
/architect/i, /structure/i, /reorganiz/i, /module/i,
|
|
47
|
+
/split/i, /merged/i, /moved .+ to/i, /renamed/i
|
|
48
|
+
],
|
|
49
|
+
icon: '🏗️',
|
|
50
|
+
label: 'Architecture'
|
|
51
|
+
},
|
|
52
|
+
DEPENDENCY: {
|
|
53
|
+
patterns: [
|
|
54
|
+
/added .+ dependency/i, /upgraded/i, /downgraded/i,
|
|
55
|
+
/pinned/i, /removed .+ package/i, /npm/i, /yarn/i
|
|
56
|
+
],
|
|
57
|
+
icon: '📦',
|
|
58
|
+
label: 'Dependencies'
|
|
59
|
+
},
|
|
60
|
+
PERFORMANCE: {
|
|
61
|
+
patterns: [
|
|
62
|
+
/performance/i, /optimiz/i, /faster/i, /slower/i,
|
|
63
|
+
/memory/i, /cache/i, /lazy/i, /async/i
|
|
64
|
+
],
|
|
65
|
+
icon: '⚡',
|
|
66
|
+
label: 'Performance'
|
|
67
|
+
},
|
|
68
|
+
SECURITY: {
|
|
69
|
+
patterns: [
|
|
70
|
+
/security/i, /vulnerab/i, /auth/i, /permission/i,
|
|
71
|
+
/sanitiz/i, /validat/i, /escape/i, /inject/i
|
|
72
|
+
],
|
|
73
|
+
icon: '🔒',
|
|
74
|
+
label: 'Security'
|
|
75
|
+
},
|
|
76
|
+
CONFIG: {
|
|
77
|
+
patterns: [
|
|
78
|
+
/config/i, /environment/i, /\.env/i, /settings/i,
|
|
79
|
+
/setup/i, /initialize/i
|
|
80
|
+
],
|
|
81
|
+
icon: '⚙️',
|
|
82
|
+
label: 'Configuration'
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Execute git command and return output
|
|
88
|
+
*/
|
|
89
|
+
function git(command, options = {}) {
|
|
90
|
+
try {
|
|
91
|
+
const cwd = options.cwd || process.cwd();
|
|
92
|
+
return execSync(`git ${command}`, {
|
|
93
|
+
cwd,
|
|
94
|
+
encoding: 'utf-8',
|
|
95
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
96
|
+
}).trim();
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (options.throwOnError) throw e;
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if current directory is a git repo
|
|
105
|
+
*/
|
|
106
|
+
function isGitRepo(cwd = process.cwd()) {
|
|
107
|
+
try {
|
|
108
|
+
git('rev-parse --git-dir', { cwd, throwOnError: true });
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get recent commits with full messages
|
|
117
|
+
*/
|
|
118
|
+
function getCommits(options = {}) {
|
|
119
|
+
const {
|
|
120
|
+
limit = 100,
|
|
121
|
+
since = null,
|
|
122
|
+
until = null,
|
|
123
|
+
author = null,
|
|
124
|
+
path: filePath = null,
|
|
125
|
+
cwd = process.cwd()
|
|
126
|
+
} = options;
|
|
127
|
+
|
|
128
|
+
// Use %x00 (null char) as record separator and ||| as field separator
|
|
129
|
+
// This handles multi-line commit bodies correctly
|
|
130
|
+
let command = `log --pretty=format:"%H|||%ai|||%an|||%s%x00" -n ${limit}`;
|
|
131
|
+
|
|
132
|
+
if (since) command += ` --since="${since}"`;
|
|
133
|
+
if (until) command += ` --until="${until}"`;
|
|
134
|
+
if (author) command += ` --author="${author}"`;
|
|
135
|
+
if (filePath) command += ` -- "${filePath}"`;
|
|
136
|
+
|
|
137
|
+
const output = git(command, { cwd });
|
|
138
|
+
if (!output) return [];
|
|
139
|
+
|
|
140
|
+
// Split by null character to get individual commits
|
|
141
|
+
return output.split('\x00')
|
|
142
|
+
.filter(record => record.trim())
|
|
143
|
+
.map(record => {
|
|
144
|
+
const [hash, date, author, subject] = record.trim().split('|||');
|
|
145
|
+
return {
|
|
146
|
+
hash: hash?.slice(0, 8),
|
|
147
|
+
date,
|
|
148
|
+
author,
|
|
149
|
+
subject,
|
|
150
|
+
body: '', // Body extraction removed for simplicity - subject is enough for categorization
|
|
151
|
+
fullMessage: subject || ''
|
|
152
|
+
};
|
|
153
|
+
})
|
|
154
|
+
.filter(c => c.hash && c.hash.length === 8);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Categorize a commit based on its message
|
|
159
|
+
*/
|
|
160
|
+
function categorizeCommit(commit) {
|
|
161
|
+
const message = commit.fullMessage.toLowerCase();
|
|
162
|
+
const categories = [];
|
|
163
|
+
|
|
164
|
+
for (const [category, config] of Object.entries(MEMORY_CATEGORIES)) {
|
|
165
|
+
for (const pattern of config.patterns) {
|
|
166
|
+
if (pattern.test(commit.fullMessage)) {
|
|
167
|
+
categories.push(category);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Default to general if no category matched
|
|
174
|
+
if (categories.length === 0) {
|
|
175
|
+
categories.push('GENERAL');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return categories;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract files changed in a commit
|
|
183
|
+
*/
|
|
184
|
+
function getCommitFiles(hash, cwd = process.cwd()) {
|
|
185
|
+
const output = git(`show --name-only --pretty=format:"" ${hash}`, { cwd });
|
|
186
|
+
return output.split('\n').filter(f => f.trim());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Extract learnings from commit history
|
|
191
|
+
*/
|
|
192
|
+
function extractLearnings(options = {}) {
|
|
193
|
+
const {
|
|
194
|
+
limit = 50,
|
|
195
|
+
since = '3 months ago',
|
|
196
|
+
cwd = process.cwd(),
|
|
197
|
+
includeFiles = false
|
|
198
|
+
} = options;
|
|
199
|
+
|
|
200
|
+
if (!isGitRepo(cwd)) {
|
|
201
|
+
return { error: 'Not a git repository', learnings: [] };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const commits = getCommits({ limit, since, cwd });
|
|
205
|
+
const learnings = [];
|
|
206
|
+
|
|
207
|
+
for (const commit of commits) {
|
|
208
|
+
const categories = categorizeCommit(commit);
|
|
209
|
+
|
|
210
|
+
// Skip mundane commits
|
|
211
|
+
if (isMundaneCommit(commit)) continue;
|
|
212
|
+
|
|
213
|
+
const learning = {
|
|
214
|
+
hash: commit.hash,
|
|
215
|
+
date: commit.date,
|
|
216
|
+
categories,
|
|
217
|
+
summary: commit.subject,
|
|
218
|
+
details: commit.body || null,
|
|
219
|
+
importance: calculateImportance(commit, categories)
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (includeFiles) {
|
|
223
|
+
learning.files = getCommitFiles(commit.hash, cwd);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
learnings.push(learning);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Sort by importance
|
|
230
|
+
learnings.sort((a, b) => b.importance - a.importance);
|
|
231
|
+
|
|
232
|
+
return { learnings, total: commits.length };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if commit is mundane (low-value for memory)
|
|
237
|
+
*/
|
|
238
|
+
function isMundaneCommit(commit) {
|
|
239
|
+
const mundanePatterns = [
|
|
240
|
+
/^merge/i,
|
|
241
|
+
/^wip/i,
|
|
242
|
+
/^temp/i,
|
|
243
|
+
/^bump version/i,
|
|
244
|
+
/^update changelog/i,
|
|
245
|
+
/^lint/i,
|
|
246
|
+
/^format/i,
|
|
247
|
+
/^typo/i,
|
|
248
|
+
/^minor/i,
|
|
249
|
+
/^small/i
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
return mundanePatterns.some(p => p.test(commit.subject));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Calculate importance score for a learning
|
|
257
|
+
*/
|
|
258
|
+
function calculateImportance(commit, categories) {
|
|
259
|
+
let score = 0;
|
|
260
|
+
|
|
261
|
+
// Category weights
|
|
262
|
+
const weights = {
|
|
263
|
+
DECISION: 10,
|
|
264
|
+
ARCHITECTURE: 9,
|
|
265
|
+
SECURITY: 8,
|
|
266
|
+
BUGFIX: 7,
|
|
267
|
+
PATTERN: 6,
|
|
268
|
+
PERFORMANCE: 5,
|
|
269
|
+
DEPENDENCY: 4,
|
|
270
|
+
CONFIG: 3,
|
|
271
|
+
GENERAL: 1
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
for (const cat of categories) {
|
|
275
|
+
score += weights[cat] || 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Boost for longer messages (more context)
|
|
279
|
+
if (commit.body && commit.body.length > 50) {
|
|
280
|
+
score += 2;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Boost for conventional commit prefixes
|
|
284
|
+
if (/^(feat|fix|refactor|perf|security):/i.test(commit.subject)) {
|
|
285
|
+
score += 2;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return score;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Group learnings by category
|
|
293
|
+
*/
|
|
294
|
+
function groupByCategory(learnings) {
|
|
295
|
+
const grouped = {};
|
|
296
|
+
|
|
297
|
+
for (const learning of learnings) {
|
|
298
|
+
for (const category of learning.categories) {
|
|
299
|
+
if (!grouped[category]) {
|
|
300
|
+
grouped[category] = [];
|
|
301
|
+
}
|
|
302
|
+
grouped[category].push(learning);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return grouped;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Generate markdown summary of learnings
|
|
311
|
+
*/
|
|
312
|
+
function toMarkdown(learnings, options = {}) {
|
|
313
|
+
const { title = 'Project Learnings', maxPerCategory = 5 } = options;
|
|
314
|
+
const grouped = groupByCategory(learnings);
|
|
315
|
+
|
|
316
|
+
let md = `## ${title}\n\n`;
|
|
317
|
+
md += `> Extracted from ${learnings.length} significant commits\n\n`;
|
|
318
|
+
|
|
319
|
+
for (const [category, items] of Object.entries(grouped)) {
|
|
320
|
+
const config = MEMORY_CATEGORIES[category] || { icon: '📝', label: category };
|
|
321
|
+
const topItems = items.slice(0, maxPerCategory);
|
|
322
|
+
|
|
323
|
+
md += `### ${config.icon} ${config.label}\n\n`;
|
|
324
|
+
|
|
325
|
+
for (const item of topItems) {
|
|
326
|
+
md += `- **${item.summary}**`;
|
|
327
|
+
if (item.details) {
|
|
328
|
+
// First line of details only
|
|
329
|
+
const firstLine = item.details.split('\n')[0].trim();
|
|
330
|
+
if (firstLine && firstLine.length < 100) {
|
|
331
|
+
md += ` - ${firstLine}`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
md += ` _(${item.hash})_\n`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
md += '\n';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return md;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Generate compact summary for CLAUDE.md injection
|
|
345
|
+
*/
|
|
346
|
+
function toCompactSummary(learnings, options = {}) {
|
|
347
|
+
const { maxItems = 15 } = options;
|
|
348
|
+
const topLearnings = learnings.slice(0, maxItems);
|
|
349
|
+
|
|
350
|
+
let summary = `## Recent Learnings\n\n`;
|
|
351
|
+
summary += `Key decisions and patterns from recent development:\n\n`;
|
|
352
|
+
|
|
353
|
+
for (const learning of topLearnings) {
|
|
354
|
+
const categoryIcons = learning.categories
|
|
355
|
+
.map(c => MEMORY_CATEGORIES[c]?.icon || '📝')
|
|
356
|
+
.join('');
|
|
357
|
+
|
|
358
|
+
summary += `- ${categoryIcons} ${learning.summary}\n`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return summary;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Search learnings by keyword
|
|
366
|
+
*/
|
|
367
|
+
function searchLearnings(learnings, query) {
|
|
368
|
+
const queryLower = query.toLowerCase();
|
|
369
|
+
|
|
370
|
+
return learnings.filter(l => {
|
|
371
|
+
return l.summary.toLowerCase().includes(queryLower) ||
|
|
372
|
+
(l.details && l.details.toLowerCase().includes(queryLower)) ||
|
|
373
|
+
l.categories.some(c => c.toLowerCase().includes(queryLower));
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Get learnings relevant to specific files
|
|
379
|
+
*/
|
|
380
|
+
function getLearningsForFiles(files, options = {}) {
|
|
381
|
+
const { cwd = process.cwd(), limit = 20 } = options;
|
|
382
|
+
const learnings = [];
|
|
383
|
+
|
|
384
|
+
for (const file of files) {
|
|
385
|
+
const commits = getCommits({ limit: 10, path: file, cwd });
|
|
386
|
+
|
|
387
|
+
for (const commit of commits) {
|
|
388
|
+
if (!isMundaneCommit(commit)) {
|
|
389
|
+
const categories = categorizeCommit(commit);
|
|
390
|
+
learnings.push({
|
|
391
|
+
hash: commit.hash,
|
|
392
|
+
date: commit.date,
|
|
393
|
+
categories,
|
|
394
|
+
summary: commit.subject,
|
|
395
|
+
details: commit.body,
|
|
396
|
+
file,
|
|
397
|
+
importance: calculateImportance(commit, categories)
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Dedupe by hash and sort
|
|
404
|
+
const seen = new Set();
|
|
405
|
+
return learnings
|
|
406
|
+
.filter(l => {
|
|
407
|
+
if (seen.has(l.hash)) return false;
|
|
408
|
+
seen.add(l.hash);
|
|
409
|
+
return true;
|
|
410
|
+
})
|
|
411
|
+
.sort((a, b) => b.importance - a.importance)
|
|
412
|
+
.slice(0, limit);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get summary stats about the repo
|
|
417
|
+
*/
|
|
418
|
+
function getRepoStats(cwd = process.cwd()) {
|
|
419
|
+
if (!isGitRepo(cwd)) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const totalCommits = parseInt(git('rev-list --count HEAD', { cwd }) || '0');
|
|
424
|
+
const firstCommit = git('log --reverse --pretty=format:"%ai" | head -1', { cwd });
|
|
425
|
+
const contributors = git('shortlog -sn --no-merges', { cwd })
|
|
426
|
+
.split('\n')
|
|
427
|
+
.filter(l => l.trim())
|
|
428
|
+
.length;
|
|
429
|
+
|
|
430
|
+
const recentActivity = git('log --since="1 week ago" --oneline', { cwd })
|
|
431
|
+
.split('\n')
|
|
432
|
+
.filter(l => l.trim())
|
|
433
|
+
.length;
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
totalCommits,
|
|
437
|
+
firstCommit,
|
|
438
|
+
contributors,
|
|
439
|
+
recentActivity,
|
|
440
|
+
weeklyPace: recentActivity
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// CLI
|
|
445
|
+
if (require.main === module) {
|
|
446
|
+
const args = process.argv.slice(2);
|
|
447
|
+
const command = args[0] || 'summary';
|
|
448
|
+
|
|
449
|
+
switch (command) {
|
|
450
|
+
case 'extract': {
|
|
451
|
+
const result = extractLearnings({
|
|
452
|
+
limit: parseInt(args[1]) || 50,
|
|
453
|
+
includeFiles: args.includes('--files')
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
if (result.error) {
|
|
457
|
+
console.error(result.error);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
console.log(JSON.stringify(result.learnings, null, 2));
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
case 'summary': {
|
|
466
|
+
const result = extractLearnings({ limit: 50 });
|
|
467
|
+
|
|
468
|
+
if (result.error) {
|
|
469
|
+
console.error(result.error);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
console.log(toMarkdown(result.learnings));
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
case 'compact': {
|
|
478
|
+
const result = extractLearnings({ limit: 30 });
|
|
479
|
+
|
|
480
|
+
if (result.error) {
|
|
481
|
+
console.error(result.error);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.log(toCompactSummary(result.learnings));
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
case 'search': {
|
|
490
|
+
const query = args.slice(1).join(' ');
|
|
491
|
+
if (!query) {
|
|
492
|
+
console.error('Usage: git-memory.js search <query>');
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const result = extractLearnings({ limit: 100 });
|
|
497
|
+
const matches = searchLearnings(result.learnings, query);
|
|
498
|
+
|
|
499
|
+
console.log(`Found ${matches.length} matches for "${query}":\n`);
|
|
500
|
+
for (const match of matches.slice(0, 10)) {
|
|
501
|
+
console.log(` [${match.hash}] ${match.summary}`);
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
case 'stats': {
|
|
507
|
+
const stats = getRepoStats();
|
|
508
|
+
if (!stats) {
|
|
509
|
+
console.error('Not a git repository');
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
case 'categories': {
|
|
517
|
+
console.log('\nAvailable categories:\n');
|
|
518
|
+
for (const [key, config] of Object.entries(MEMORY_CATEGORIES)) {
|
|
519
|
+
console.log(` ${config.icon} ${key.padEnd(15)} ${config.label}`);
|
|
520
|
+
}
|
|
521
|
+
console.log();
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
default:
|
|
526
|
+
console.log(`
|
|
527
|
+
Bootspring Git Memory
|
|
528
|
+
|
|
529
|
+
Usage:
|
|
530
|
+
git-memory.js extract [limit] [--files] Extract learnings as JSON
|
|
531
|
+
git-memory.js summary Generate markdown summary
|
|
532
|
+
git-memory.js compact Compact summary for CLAUDE.md
|
|
533
|
+
git-memory.js search <query> Search learnings
|
|
534
|
+
git-memory.js stats Repository statistics
|
|
535
|
+
git-memory.js categories List memory categories
|
|
536
|
+
`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
module.exports = {
|
|
541
|
+
isGitRepo,
|
|
542
|
+
getCommits,
|
|
543
|
+
extractLearnings,
|
|
544
|
+
groupByCategory,
|
|
545
|
+
toMarkdown,
|
|
546
|
+
toCompactSummary,
|
|
547
|
+
searchLearnings,
|
|
548
|
+
getLearningsForFiles,
|
|
549
|
+
getRepoStats,
|
|
550
|
+
MEMORY_CATEGORIES
|
|
551
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Intelligence Module
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent agent orchestration, context analysis,
|
|
5
|
+
* workflow management, and autonomous loop execution.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module intelligence
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const orchestrator = require('./orchestrator');
|
|
12
|
+
const prd = require('./prd');
|
|
13
|
+
const gitMemory = require('./git-memory');
|
|
14
|
+
const recommendations = require('./recommendations');
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
// Orchestrator functions
|
|
18
|
+
analyzeContext: orchestrator.analyzeContext,
|
|
19
|
+
getWorkflow: orchestrator.getWorkflow,
|
|
20
|
+
listWorkflows: orchestrator.listWorkflows,
|
|
21
|
+
startWorkflow: orchestrator.startWorkflow,
|
|
22
|
+
advanceWorkflow: orchestrator.advanceWorkflow,
|
|
23
|
+
markWorkflowCheckpoint: orchestrator.markWorkflowCheckpoint,
|
|
24
|
+
getWorkflowSignalProgress: orchestrator.getWorkflowSignalProgress,
|
|
25
|
+
loadState: orchestrator.loadState,
|
|
26
|
+
saveState: orchestrator.saveState,
|
|
27
|
+
getCurrentPhase: orchestrator.getCurrentPhase,
|
|
28
|
+
agentExists: orchestrator.agentExists,
|
|
29
|
+
getAvailableAgents: orchestrator.getAvailableAgents,
|
|
30
|
+
|
|
31
|
+
// PRD functions
|
|
32
|
+
createPRD: prd.createPRD,
|
|
33
|
+
loadPRD: prd.loadPRD,
|
|
34
|
+
savePRD: prd.savePRD,
|
|
35
|
+
getNextStory: prd.getNextStory,
|
|
36
|
+
updateStoryStatus: prd.updateStoryStatus,
|
|
37
|
+
getPRDProgress: prd.getProgress,
|
|
38
|
+
isPRDComplete: prd.isComplete,
|
|
39
|
+
parseMarkdownPRD: prd.parseMarkdownPRD,
|
|
40
|
+
prdToMarkdown: prd.toMarkdown,
|
|
41
|
+
|
|
42
|
+
// Git Memory functions
|
|
43
|
+
extractLearnings: gitMemory.extractLearnings,
|
|
44
|
+
getLearningsForFiles: gitMemory.getLearningsForFiles,
|
|
45
|
+
searchLearnings: gitMemory.searchLearnings,
|
|
46
|
+
learningsToMarkdown: gitMemory.toMarkdown,
|
|
47
|
+
learningsToCompact: gitMemory.toCompactSummary,
|
|
48
|
+
getRepoStats: gitMemory.getRepoStats,
|
|
49
|
+
isGitRepo: gitMemory.isGitRepo,
|
|
50
|
+
MEMORY_CATEGORIES: gitMemory.MEMORY_CATEGORIES,
|
|
51
|
+
|
|
52
|
+
// Recommendation engine
|
|
53
|
+
createRecommendationsEngine: recommendations.createRecommendationsEngine,
|
|
54
|
+
|
|
55
|
+
// Constants
|
|
56
|
+
PHASE_AGENTS: orchestrator.PHASE_AGENTS,
|
|
57
|
+
TECHNICAL_TRIGGERS: orchestrator.TECHNICAL_TRIGGERS,
|
|
58
|
+
WORKFLOWS: orchestrator.WORKFLOWS
|
|
59
|
+
};
|