universal-agent-memory 0.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 +321 -0
- package/dist/analyzers/index.d.ts +3 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +373 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +119 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/cli/analyze.d.ts +7 -0
- package/dist/cli/analyze.d.ts.map +1 -0
- package/dist/cli/analyze.js +103 -0
- package/dist/cli/analyze.js.map +1 -0
- package/dist/cli/droids.d.ts +9 -0
- package/dist/cli/droids.d.ts.map +1 -0
- package/dist/cli/droids.js +227 -0
- package/dist/cli/droids.js.map +1 -0
- package/dist/cli/generate.d.ts +9 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +203 -0
- package/dist/cli/generate.js.map +1 -0
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +260 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/memory.d.ts +15 -0
- package/dist/cli/memory.d.ts.map +1 -0
- package/dist/cli/memory.js +274 -0
- package/dist/cli/memory.js.map +1 -0
- package/dist/cli/sync.d.ts +7 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +26 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/cli/worktree.d.ts +9 -0
- package/dist/cli/worktree.d.ts.map +1 -0
- package/dist/cli/worktree.js +175 -0
- package/dist/cli/worktree.js.map +1 -0
- package/dist/generators/claude-md.d.ts +3 -0
- package/dist/generators/claude-md.d.ts.map +1 -0
- package/dist/generators/claude-md.js +643 -0
- package/dist/generators/claude-md.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/backends/base.d.ts +18 -0
- package/dist/memory/backends/base.d.ts.map +1 -0
- package/dist/memory/backends/base.js +2 -0
- package/dist/memory/backends/base.js.map +1 -0
- package/dist/memory/backends/factory.d.ts +4 -0
- package/dist/memory/backends/factory.d.ts.map +1 -0
- package/dist/memory/backends/factory.js +52 -0
- package/dist/memory/backends/factory.js.map +1 -0
- package/dist/memory/backends/github.d.ts +22 -0
- package/dist/memory/backends/github.d.ts.map +1 -0
- package/dist/memory/backends/github.js +118 -0
- package/dist/memory/backends/github.js.map +1 -0
- package/dist/memory/backends/qdrant-cloud.d.ts +19 -0
- package/dist/memory/backends/qdrant-cloud.d.ts.map +1 -0
- package/dist/memory/backends/qdrant-cloud.js +111 -0
- package/dist/memory/backends/qdrant-cloud.js.map +1 -0
- package/dist/memory/prepopulate.d.ts +76 -0
- package/dist/memory/prepopulate.d.ts.map +1 -0
- package/dist/memory/prepopulate.js +815 -0
- package/dist/memory/prepopulate.js.map +1 -0
- package/dist/memory/short-term/factory.d.ts +23 -0
- package/dist/memory/short-term/factory.d.ts.map +1 -0
- package/dist/memory/short-term/factory.js +28 -0
- package/dist/memory/short-term/factory.js.map +1 -0
- package/dist/memory/short-term/indexeddb.d.ts +25 -0
- package/dist/memory/short-term/indexeddb.d.ts.map +1 -0
- package/dist/memory/short-term/indexeddb.js +64 -0
- package/dist/memory/short-term/indexeddb.js.map +1 -0
- package/dist/memory/short-term/sqlite.d.ts +40 -0
- package/dist/memory/short-term/sqlite.d.ts.map +1 -0
- package/dist/memory/short-term/sqlite.js +136 -0
- package/dist/memory/short-term/sqlite.js.map +1 -0
- package/dist/types/analysis.d.ts +82 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +2 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/config.d.ts +923 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +97 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/merge-claude-md.d.ts +15 -0
- package/dist/utils/merge-claude-md.d.ts.map +1 -0
- package/dist/utils/merge-claude-md.js +149 -0
- package/dist/utils/merge-claude-md.js.map +1 -0
- package/package.json +90 -0
- package/templates/CLAUDE.template.md +632 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
import { join, relative, extname, basename } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Prepopulate memory from project documentation, git history, and skills/artifacts
|
|
6
|
+
*/
|
|
7
|
+
export async function prepopulateMemory(cwd, options = {}) {
|
|
8
|
+
const shortTermMemories = [];
|
|
9
|
+
const longTermMemories = [];
|
|
10
|
+
const discoveredSkills = [];
|
|
11
|
+
const doAll = !options.docs && !options.git && !options.skills;
|
|
12
|
+
// Parse documentation
|
|
13
|
+
if (doAll || options.docs) {
|
|
14
|
+
const docs = await parseDocumentation(cwd, options.verbose);
|
|
15
|
+
const docMemories = extractDocumentationMemories(docs);
|
|
16
|
+
// Short-term: recent/important observations
|
|
17
|
+
shortTermMemories.push(...docMemories.filter(m => m.importance && m.importance >= 7).slice(0, 20));
|
|
18
|
+
// Long-term: all documentation memories
|
|
19
|
+
longTermMemories.push(...docMemories);
|
|
20
|
+
}
|
|
21
|
+
// Extract git learnings
|
|
22
|
+
if (doAll || options.git) {
|
|
23
|
+
const commits = await extractGitHistory(cwd, options.limit || 500, options.since);
|
|
24
|
+
const gitMemories = extractGitLearnings(commits);
|
|
25
|
+
// Short-term: recent significant commits
|
|
26
|
+
shortTermMemories.push(...gitMemories.filter(m => m.importance && m.importance >= 6).slice(0, 30));
|
|
27
|
+
// Long-term: all git learnings
|
|
28
|
+
longTermMemories.push(...gitMemories);
|
|
29
|
+
}
|
|
30
|
+
// Discover skills, droids, commands, and artifacts
|
|
31
|
+
if (doAll || options.skills) {
|
|
32
|
+
const skills = await discoverSkillsAndArtifacts(cwd, options.verbose);
|
|
33
|
+
discoveredSkills.push(...skills);
|
|
34
|
+
// Create memories for discovered skills
|
|
35
|
+
const skillMemories = extractSkillMemories(skills);
|
|
36
|
+
longTermMemories.push(...skillMemories);
|
|
37
|
+
// Add important skills to short-term
|
|
38
|
+
shortTermMemories.push(...skillMemories.filter(m => m.importance && m.importance >= 7).slice(0, 10));
|
|
39
|
+
}
|
|
40
|
+
return { shortTerm: shortTermMemories, longTerm: longTermMemories, skills: discoveredSkills };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse all documentation files in the project
|
|
44
|
+
*/
|
|
45
|
+
export async function parseDocumentation(cwd, verbose = false) {
|
|
46
|
+
const docs = [];
|
|
47
|
+
// Documentation file patterns to search for
|
|
48
|
+
const docPatterns = [
|
|
49
|
+
'README.md',
|
|
50
|
+
'README.txt',
|
|
51
|
+
'README',
|
|
52
|
+
'CONTRIBUTING.md',
|
|
53
|
+
'CHANGELOG.md',
|
|
54
|
+
'ARCHITECTURE.md',
|
|
55
|
+
'DEVELOPMENT.md',
|
|
56
|
+
'SECURITY.md',
|
|
57
|
+
'.github/PULL_REQUEST_TEMPLATE.md',
|
|
58
|
+
'.github/ISSUE_TEMPLATE.md',
|
|
59
|
+
];
|
|
60
|
+
// Parse root-level docs
|
|
61
|
+
for (const pattern of docPatterns) {
|
|
62
|
+
const docPath = join(cwd, pattern);
|
|
63
|
+
if (existsSync(docPath)) {
|
|
64
|
+
const doc = parseMarkdownFile(docPath, cwd);
|
|
65
|
+
if (doc) {
|
|
66
|
+
docs.push(doc);
|
|
67
|
+
if (verbose)
|
|
68
|
+
console.log(` Parsed: ${pattern}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Parse docs/ directory
|
|
73
|
+
const docsDir = join(cwd, 'docs');
|
|
74
|
+
if (existsSync(docsDir) && statSync(docsDir).isDirectory()) {
|
|
75
|
+
const docFiles = findMarkdownFiles(docsDir);
|
|
76
|
+
for (const docFile of docFiles) {
|
|
77
|
+
const doc = parseMarkdownFile(docFile, cwd);
|
|
78
|
+
if (doc) {
|
|
79
|
+
docs.push(doc);
|
|
80
|
+
if (verbose)
|
|
81
|
+
console.log(` Parsed: ${relative(cwd, docFile)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Parse ADRs (Architecture Decision Records)
|
|
86
|
+
const adrDirs = ['docs/adr', 'docs/decisions', 'adr', 'decisions'];
|
|
87
|
+
for (const adrDir of adrDirs) {
|
|
88
|
+
const adrPath = join(cwd, adrDir);
|
|
89
|
+
if (existsSync(adrPath) && statSync(adrPath).isDirectory()) {
|
|
90
|
+
const adrFiles = findMarkdownFiles(adrPath);
|
|
91
|
+
for (const adrFile of adrFiles) {
|
|
92
|
+
const doc = parseMarkdownFile(adrFile, cwd, 'adr');
|
|
93
|
+
if (doc) {
|
|
94
|
+
docs.push(doc);
|
|
95
|
+
if (verbose)
|
|
96
|
+
console.log(` Parsed ADR: ${relative(cwd, adrFile)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return docs;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extract git commit history with details
|
|
105
|
+
*/
|
|
106
|
+
export async function extractGitHistory(cwd, limit = 500, since) {
|
|
107
|
+
const commits = [];
|
|
108
|
+
try {
|
|
109
|
+
// Check if it's a git repo
|
|
110
|
+
execSync('git rev-parse --git-dir', { cwd, encoding: 'utf-8', stdio: 'pipe' });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return commits; // Not a git repo
|
|
114
|
+
}
|
|
115
|
+
// Build git log command
|
|
116
|
+
let gitCmd = `git log --pretty=format:"%H|%aI|%an|%s|%b<<<END>>>" -n ${limit}`;
|
|
117
|
+
if (since) {
|
|
118
|
+
gitCmd += ` --since="${since}"`;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const output = execSync(gitCmd, { cwd, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 });
|
|
122
|
+
const commitStrings = output.split('<<<END>>>').filter(s => s.trim());
|
|
123
|
+
for (const commitStr of commitStrings) {
|
|
124
|
+
const parts = commitStr.trim().split('|');
|
|
125
|
+
if (parts.length < 4)
|
|
126
|
+
continue;
|
|
127
|
+
const [hash, date, author, ...rest] = parts;
|
|
128
|
+
const messageAndBody = rest.join('|');
|
|
129
|
+
const [message, ...bodyParts] = messageAndBody.split('\n');
|
|
130
|
+
const body = bodyParts.join('\n').trim();
|
|
131
|
+
// Get files changed in this commit
|
|
132
|
+
let files = [];
|
|
133
|
+
try {
|
|
134
|
+
const filesOutput = execSync(`git diff-tree --no-commit-id --name-only -r ${hash}`, {
|
|
135
|
+
cwd,
|
|
136
|
+
encoding: 'utf-8',
|
|
137
|
+
stdio: 'pipe',
|
|
138
|
+
});
|
|
139
|
+
files = filesOutput.trim().split('\n').filter(f => f);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Ignore errors getting file list
|
|
143
|
+
}
|
|
144
|
+
// Determine commit type from conventional commit format
|
|
145
|
+
const type = categorizeCommit(message);
|
|
146
|
+
commits.push({
|
|
147
|
+
hash: hash.trim(),
|
|
148
|
+
date: date.trim(),
|
|
149
|
+
author: author.trim(),
|
|
150
|
+
message: message.trim(),
|
|
151
|
+
body,
|
|
152
|
+
files,
|
|
153
|
+
type,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
// Git command failed, return empty
|
|
159
|
+
console.warn('Failed to extract git history:', error);
|
|
160
|
+
}
|
|
161
|
+
return commits;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Convert documentation into memory entries
|
|
165
|
+
*/
|
|
166
|
+
export function extractDocumentationMemories(docs) {
|
|
167
|
+
const memories = [];
|
|
168
|
+
const now = new Date().toISOString();
|
|
169
|
+
for (const doc of docs) {
|
|
170
|
+
for (const section of doc.sections) {
|
|
171
|
+
// Skip empty or very short sections
|
|
172
|
+
if (!section.content || section.content.length < 50)
|
|
173
|
+
continue;
|
|
174
|
+
// Chunk large sections
|
|
175
|
+
const chunks = chunkContent(section.content, 500);
|
|
176
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
177
|
+
const memoryType = mapSectionToMemoryType(section.type);
|
|
178
|
+
const importance = calculateDocImportance(section, doc.path);
|
|
179
|
+
memories.push({
|
|
180
|
+
id: `doc-${basename(doc.path, extname(doc.path))}-${section.heading.toLowerCase().replace(/\s+/g, '-')}-${i}`,
|
|
181
|
+
timestamp: now,
|
|
182
|
+
type: memoryType,
|
|
183
|
+
content: chunks[i],
|
|
184
|
+
tags: extractTags(section, doc.path),
|
|
185
|
+
importance,
|
|
186
|
+
metadata: {
|
|
187
|
+
source: 'documentation',
|
|
188
|
+
file: doc.path,
|
|
189
|
+
section: section.heading,
|
|
190
|
+
docTitle: doc.title,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return memories;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Convert git history into memory entries with learnings
|
|
200
|
+
*/
|
|
201
|
+
export function extractGitLearnings(commits) {
|
|
202
|
+
const memories = [];
|
|
203
|
+
// Group commits by type for analysis
|
|
204
|
+
const bugFixes = commits.filter(c => c.type === 'fix');
|
|
205
|
+
const features = commits.filter(c => c.type === 'feat');
|
|
206
|
+
const refactors = commits.filter(c => c.type === 'refactor');
|
|
207
|
+
const reverts = commits.filter(c => c.type === 'revert');
|
|
208
|
+
// Extract learnings from bug fixes
|
|
209
|
+
for (const commit of bugFixes.slice(0, 50)) {
|
|
210
|
+
const learning = extractBugFixLearning(commit);
|
|
211
|
+
if (learning) {
|
|
212
|
+
memories.push({
|
|
213
|
+
id: `git-fix-${commit.hash.substring(0, 8)}`,
|
|
214
|
+
timestamp: commit.date,
|
|
215
|
+
type: 'observation',
|
|
216
|
+
content: learning,
|
|
217
|
+
tags: ['bug-fix', ...extractFileTags(commit.files)],
|
|
218
|
+
importance: calculateCommitImportance(commit),
|
|
219
|
+
metadata: {
|
|
220
|
+
source: 'git',
|
|
221
|
+
commit: commit.hash,
|
|
222
|
+
author: commit.author,
|
|
223
|
+
files: commit.files.slice(0, 10),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Extract learnings from features
|
|
229
|
+
for (const commit of features.slice(0, 30)) {
|
|
230
|
+
memories.push({
|
|
231
|
+
id: `git-feat-${commit.hash.substring(0, 8)}`,
|
|
232
|
+
timestamp: commit.date,
|
|
233
|
+
type: 'observation',
|
|
234
|
+
content: `Feature added: ${commit.message}${commit.body ? `. ${summarizeBody(commit.body)}` : ''}`,
|
|
235
|
+
tags: ['feature', ...extractFileTags(commit.files)],
|
|
236
|
+
importance: calculateCommitImportance(commit),
|
|
237
|
+
metadata: {
|
|
238
|
+
source: 'git',
|
|
239
|
+
commit: commit.hash,
|
|
240
|
+
author: commit.author,
|
|
241
|
+
files: commit.files.slice(0, 10),
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Extract learnings from refactors (architectural decisions)
|
|
246
|
+
for (const commit of refactors.slice(0, 20)) {
|
|
247
|
+
memories.push({
|
|
248
|
+
id: `git-refactor-${commit.hash.substring(0, 8)}`,
|
|
249
|
+
timestamp: commit.date,
|
|
250
|
+
type: 'thought',
|
|
251
|
+
content: `Architectural change: ${commit.message}${commit.body ? `. Reasoning: ${summarizeBody(commit.body)}` : ''}`,
|
|
252
|
+
tags: ['refactor', 'architecture', ...extractFileTags(commit.files)],
|
|
253
|
+
importance: calculateCommitImportance(commit) + 1, // Refactors are usually important decisions
|
|
254
|
+
metadata: {
|
|
255
|
+
source: 'git',
|
|
256
|
+
commit: commit.hash,
|
|
257
|
+
author: commit.author,
|
|
258
|
+
files: commit.files.slice(0, 10),
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Extract learnings from reverts (failed approaches)
|
|
263
|
+
for (const commit of reverts) {
|
|
264
|
+
memories.push({
|
|
265
|
+
id: `git-revert-${commit.hash.substring(0, 8)}`,
|
|
266
|
+
timestamp: commit.date,
|
|
267
|
+
type: 'thought',
|
|
268
|
+
content: `Failed approach reverted: ${commit.message}. This approach did not work - avoid similar changes.`,
|
|
269
|
+
tags: ['revert', 'failed-approach', ...extractFileTags(commit.files)],
|
|
270
|
+
importance: 8, // Reverts are always important learnings
|
|
271
|
+
metadata: {
|
|
272
|
+
source: 'git',
|
|
273
|
+
commit: commit.hash,
|
|
274
|
+
author: commit.author,
|
|
275
|
+
files: commit.files.slice(0, 10),
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
// Identify hot spots (frequently modified files)
|
|
280
|
+
const fileChangeCounts = new Map();
|
|
281
|
+
for (const commit of commits) {
|
|
282
|
+
for (const file of commit.files) {
|
|
283
|
+
fileChangeCounts.set(file, (fileChangeCounts.get(file) || 0) + 1);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const hotSpots = [...fileChangeCounts.entries()]
|
|
287
|
+
.sort((a, b) => b[1] - a[1])
|
|
288
|
+
.slice(0, 10)
|
|
289
|
+
.filter(([_, count]) => count >= 5);
|
|
290
|
+
if (hotSpots.length > 0) {
|
|
291
|
+
memories.push({
|
|
292
|
+
id: 'git-hotspots',
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
type: 'observation',
|
|
295
|
+
content: `Frequently modified files (hot spots): ${hotSpots.map(([f, c]) => `${f} (${c} changes)`).join(', ')}. These files may need extra attention during changes.`,
|
|
296
|
+
tags: ['hot-spots', 'code-quality'],
|
|
297
|
+
importance: 7,
|
|
298
|
+
metadata: {
|
|
299
|
+
source: 'git-analysis',
|
|
300
|
+
hotSpots: Object.fromEntries(hotSpots),
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return memories;
|
|
305
|
+
}
|
|
306
|
+
// Helper functions
|
|
307
|
+
function findMarkdownFiles(dir) {
|
|
308
|
+
const files = [];
|
|
309
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
const fullPath = join(dir, entry.name);
|
|
312
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
313
|
+
files.push(...findMarkdownFiles(fullPath));
|
|
314
|
+
}
|
|
315
|
+
else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
|
|
316
|
+
files.push(fullPath);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return files;
|
|
320
|
+
}
|
|
321
|
+
function parseMarkdownFile(filePath, cwd, docType) {
|
|
322
|
+
try {
|
|
323
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
324
|
+
const relativePath = relative(cwd, filePath);
|
|
325
|
+
// Extract title from first heading or filename
|
|
326
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
327
|
+
const title = titleMatch ? titleMatch[1] : basename(filePath, extname(filePath));
|
|
328
|
+
// Split content by headings
|
|
329
|
+
const sections = [];
|
|
330
|
+
const headingRegex = /^(#{1,3})\s+(.+)$/gm;
|
|
331
|
+
let lastIndex = 0;
|
|
332
|
+
let lastHeading = 'Introduction';
|
|
333
|
+
let match;
|
|
334
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
335
|
+
// Save previous section
|
|
336
|
+
if (lastIndex > 0 || match.index > 0) {
|
|
337
|
+
const sectionContent = content.slice(lastIndex, match.index).trim();
|
|
338
|
+
if (sectionContent) {
|
|
339
|
+
sections.push({
|
|
340
|
+
heading: lastHeading,
|
|
341
|
+
content: cleanMarkdown(sectionContent),
|
|
342
|
+
type: categorizeSectionType(lastHeading, sectionContent, docType),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
lastHeading = match[2];
|
|
347
|
+
lastIndex = match.index + match[0].length;
|
|
348
|
+
}
|
|
349
|
+
// Add final section
|
|
350
|
+
const finalContent = content.slice(lastIndex).trim();
|
|
351
|
+
if (finalContent) {
|
|
352
|
+
sections.push({
|
|
353
|
+
heading: lastHeading,
|
|
354
|
+
content: cleanMarkdown(finalContent),
|
|
355
|
+
type: categorizeSectionType(lastHeading, finalContent, docType),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
path: relativePath,
|
|
360
|
+
title,
|
|
361
|
+
sections,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function cleanMarkdown(content) {
|
|
369
|
+
return content
|
|
370
|
+
.replace(/```[\s\S]*?```/g, '[code block]') // Replace code blocks
|
|
371
|
+
.replace(/`[^`]+`/g, (m) => m) // Keep inline code
|
|
372
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
|
|
373
|
+
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '[image: $1]') // Simplify images
|
|
374
|
+
.replace(/\n{3,}/g, '\n\n') // Normalize newlines
|
|
375
|
+
.trim();
|
|
376
|
+
}
|
|
377
|
+
function categorizeSectionType(heading, content, docType) {
|
|
378
|
+
const headingLower = heading.toLowerCase();
|
|
379
|
+
const contentLower = content.toLowerCase();
|
|
380
|
+
if (docType === 'adr')
|
|
381
|
+
return 'architecture';
|
|
382
|
+
if (headingLower.includes('install') ||
|
|
383
|
+
headingLower.includes('setup') ||
|
|
384
|
+
headingLower.includes('getting started') ||
|
|
385
|
+
headingLower.includes('quickstart') ||
|
|
386
|
+
headingLower.includes('prerequisites')) {
|
|
387
|
+
return 'setup';
|
|
388
|
+
}
|
|
389
|
+
if (headingLower.includes('architecture') ||
|
|
390
|
+
headingLower.includes('design') ||
|
|
391
|
+
headingLower.includes('structure') ||
|
|
392
|
+
headingLower.includes('overview')) {
|
|
393
|
+
return 'architecture';
|
|
394
|
+
}
|
|
395
|
+
if (headingLower.includes('api') ||
|
|
396
|
+
headingLower.includes('endpoint') ||
|
|
397
|
+
headingLower.includes('reference')) {
|
|
398
|
+
return 'api';
|
|
399
|
+
}
|
|
400
|
+
if (headingLower.includes('troubleshoot') ||
|
|
401
|
+
headingLower.includes('faq') ||
|
|
402
|
+
headingLower.includes('common issues') ||
|
|
403
|
+
headingLower.includes('known issues') ||
|
|
404
|
+
contentLower.includes('if you encounter')) {
|
|
405
|
+
return 'troubleshooting';
|
|
406
|
+
}
|
|
407
|
+
return 'general';
|
|
408
|
+
}
|
|
409
|
+
function mapSectionToMemoryType(sectionType) {
|
|
410
|
+
switch (sectionType) {
|
|
411
|
+
case 'architecture':
|
|
412
|
+
return 'thought';
|
|
413
|
+
case 'troubleshooting':
|
|
414
|
+
return 'observation';
|
|
415
|
+
default:
|
|
416
|
+
return 'observation';
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function calculateDocImportance(section, filePath) {
|
|
420
|
+
let importance = 5;
|
|
421
|
+
// Boost README
|
|
422
|
+
if (filePath.toLowerCase().includes('readme'))
|
|
423
|
+
importance += 2;
|
|
424
|
+
// Boost architecture docs
|
|
425
|
+
if (section.type === 'architecture')
|
|
426
|
+
importance += 1;
|
|
427
|
+
// Boost setup/troubleshooting
|
|
428
|
+
if (section.type === 'setup' || section.type === 'troubleshooting')
|
|
429
|
+
importance += 1;
|
|
430
|
+
// ADRs are important
|
|
431
|
+
if (filePath.includes('adr') || filePath.includes('decision'))
|
|
432
|
+
importance += 2;
|
|
433
|
+
return Math.min(importance, 10);
|
|
434
|
+
}
|
|
435
|
+
function categorizeCommit(message) {
|
|
436
|
+
const msgLower = message.toLowerCase();
|
|
437
|
+
if (msgLower.startsWith('revert'))
|
|
438
|
+
return 'revert';
|
|
439
|
+
if (msgLower.startsWith('fix:') || msgLower.startsWith('fix(') || msgLower.includes('bugfix'))
|
|
440
|
+
return 'fix';
|
|
441
|
+
if (msgLower.startsWith('feat:') || msgLower.startsWith('feat(') || msgLower.startsWith('feature'))
|
|
442
|
+
return 'feat';
|
|
443
|
+
if (msgLower.startsWith('refactor:') || msgLower.startsWith('refactor('))
|
|
444
|
+
return 'refactor';
|
|
445
|
+
if (msgLower.startsWith('docs:') || msgLower.startsWith('docs('))
|
|
446
|
+
return 'docs';
|
|
447
|
+
if (msgLower.startsWith('test:') || msgLower.startsWith('test('))
|
|
448
|
+
return 'test';
|
|
449
|
+
if (msgLower.startsWith('chore:') || msgLower.startsWith('chore('))
|
|
450
|
+
return 'chore';
|
|
451
|
+
return 'other';
|
|
452
|
+
}
|
|
453
|
+
function extractBugFixLearning(commit) {
|
|
454
|
+
const message = commit.message;
|
|
455
|
+
const body = commit.body;
|
|
456
|
+
// Try to extract what was fixed
|
|
457
|
+
let learning = `Bug fixed: ${message}`;
|
|
458
|
+
if (body) {
|
|
459
|
+
// Look for "Fixes #123" or similar
|
|
460
|
+
const issueMatch = body.match(/(?:fixes|closes|resolves)\s+#(\d+)/i);
|
|
461
|
+
if (issueMatch) {
|
|
462
|
+
learning += ` (Issue #${issueMatch[1]})`;
|
|
463
|
+
}
|
|
464
|
+
// Add summary of body if present
|
|
465
|
+
const summary = summarizeBody(body);
|
|
466
|
+
if (summary) {
|
|
467
|
+
learning += `. ${summary}`;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Add affected files context
|
|
471
|
+
if (commit.files.length > 0 && commit.files.length <= 5) {
|
|
472
|
+
learning += ` Affected files: ${commit.files.join(', ')}.`;
|
|
473
|
+
}
|
|
474
|
+
return learning;
|
|
475
|
+
}
|
|
476
|
+
function summarizeBody(body) {
|
|
477
|
+
// Take first meaningful line of body
|
|
478
|
+
const lines = body.split('\n').filter(l => l.trim() && !l.startsWith('Co-authored') && !l.startsWith('Signed-off'));
|
|
479
|
+
if (lines.length === 0)
|
|
480
|
+
return '';
|
|
481
|
+
const firstLine = lines[0].trim();
|
|
482
|
+
if (firstLine.length > 200) {
|
|
483
|
+
return firstLine.substring(0, 200) + '...';
|
|
484
|
+
}
|
|
485
|
+
return firstLine;
|
|
486
|
+
}
|
|
487
|
+
function calculateCommitImportance(commit) {
|
|
488
|
+
let importance = 5;
|
|
489
|
+
// Reverts are always important
|
|
490
|
+
if (commit.type === 'revert')
|
|
491
|
+
importance = 8;
|
|
492
|
+
// Large changes are more significant
|
|
493
|
+
if (commit.files.length > 10)
|
|
494
|
+
importance += 1;
|
|
495
|
+
// Changes to core files
|
|
496
|
+
const coreFiles = commit.files.filter(f => f.includes('config') ||
|
|
497
|
+
f.includes('package.json') ||
|
|
498
|
+
f.includes('index') ||
|
|
499
|
+
f.includes('main') ||
|
|
500
|
+
f.includes('app'));
|
|
501
|
+
if (coreFiles.length > 0)
|
|
502
|
+
importance += 1;
|
|
503
|
+
// Has detailed body
|
|
504
|
+
if (commit.body && commit.body.length > 50)
|
|
505
|
+
importance += 1;
|
|
506
|
+
return Math.min(importance, 10);
|
|
507
|
+
}
|
|
508
|
+
function extractFileTags(files) {
|
|
509
|
+
const tags = new Set();
|
|
510
|
+
for (const file of files.slice(0, 10)) {
|
|
511
|
+
// Extract directory as tag
|
|
512
|
+
const parts = file.split('/');
|
|
513
|
+
if (parts.length > 1) {
|
|
514
|
+
tags.add(parts[0]);
|
|
515
|
+
}
|
|
516
|
+
// Extract file type
|
|
517
|
+
const ext = extname(file).toLowerCase();
|
|
518
|
+
if (ext === '.ts' || ext === '.tsx')
|
|
519
|
+
tags.add('typescript');
|
|
520
|
+
if (ext === '.js' || ext === '.jsx')
|
|
521
|
+
tags.add('javascript');
|
|
522
|
+
if (ext === '.py')
|
|
523
|
+
tags.add('python');
|
|
524
|
+
if (ext === '.go')
|
|
525
|
+
tags.add('go');
|
|
526
|
+
if (ext === '.rs')
|
|
527
|
+
tags.add('rust');
|
|
528
|
+
if (ext === '.yml' || ext === '.yaml')
|
|
529
|
+
tags.add('config');
|
|
530
|
+
if (ext === '.json')
|
|
531
|
+
tags.add('config');
|
|
532
|
+
if (ext === '.md')
|
|
533
|
+
tags.add('docs');
|
|
534
|
+
// Special files
|
|
535
|
+
if (file.includes('test') || file.includes('spec'))
|
|
536
|
+
tags.add('tests');
|
|
537
|
+
if (file.includes('docker') || file.includes('Dockerfile'))
|
|
538
|
+
tags.add('docker');
|
|
539
|
+
if (file.includes('terraform') || file.endsWith('.tf'))
|
|
540
|
+
tags.add('terraform');
|
|
541
|
+
}
|
|
542
|
+
return [...tags].slice(0, 5);
|
|
543
|
+
}
|
|
544
|
+
function extractTags(section, filePath) {
|
|
545
|
+
const tags = [section.type];
|
|
546
|
+
// Add source file as tag
|
|
547
|
+
const fileName = basename(filePath, extname(filePath)).toLowerCase();
|
|
548
|
+
if (fileName !== 'readme') {
|
|
549
|
+
tags.push(fileName);
|
|
550
|
+
}
|
|
551
|
+
// Extract keywords from heading
|
|
552
|
+
const keywords = section.heading.toLowerCase()
|
|
553
|
+
.split(/[\s-_]+/)
|
|
554
|
+
.filter(w => w.length > 3 && !['the', 'and', 'for', 'with'].includes(w));
|
|
555
|
+
tags.push(...keywords.slice(0, 3));
|
|
556
|
+
return [...new Set(tags)].slice(0, 5);
|
|
557
|
+
}
|
|
558
|
+
function chunkContent(content, maxLength) {
|
|
559
|
+
if (content.length <= maxLength)
|
|
560
|
+
return [content];
|
|
561
|
+
const chunks = [];
|
|
562
|
+
const sentences = content.split(/(?<=[.!?])\s+/);
|
|
563
|
+
let currentChunk = '';
|
|
564
|
+
for (const sentence of sentences) {
|
|
565
|
+
if (currentChunk.length + sentence.length > maxLength) {
|
|
566
|
+
if (currentChunk)
|
|
567
|
+
chunks.push(currentChunk.trim());
|
|
568
|
+
currentChunk = sentence;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
currentChunk += (currentChunk ? ' ' : '') + sentence;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (currentChunk)
|
|
575
|
+
chunks.push(currentChunk.trim());
|
|
576
|
+
return chunks;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Discover skills, droids, commands, and artifacts from various platforms
|
|
580
|
+
*/
|
|
581
|
+
export async function discoverSkillsAndArtifacts(cwd, verbose = false) {
|
|
582
|
+
const skills = [];
|
|
583
|
+
// Factory.ai droids, skills, and commands
|
|
584
|
+
const factoryPaths = [
|
|
585
|
+
{ dir: '.factory/droids', type: 'droid' },
|
|
586
|
+
{ dir: '.factory/skills', type: 'skill' },
|
|
587
|
+
{ dir: '.factory/commands', type: 'command' },
|
|
588
|
+
];
|
|
589
|
+
for (const { dir, type } of factoryPaths) {
|
|
590
|
+
const fullPath = join(cwd, dir);
|
|
591
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
592
|
+
const files = readdirSync(fullPath);
|
|
593
|
+
for (const file of files) {
|
|
594
|
+
if (file.endsWith('.md') || file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
595
|
+
const filePath = join(fullPath, file);
|
|
596
|
+
const skill = parseSkillFile(filePath, cwd, type, 'factory');
|
|
597
|
+
if (skill) {
|
|
598
|
+
skills.push(skill);
|
|
599
|
+
if (verbose)
|
|
600
|
+
console.log(` Found Factory ${type}: ${skill.name}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Claude Code agents and commands
|
|
607
|
+
const claudePaths = [
|
|
608
|
+
{ dir: '.claude/agents', type: 'droid' },
|
|
609
|
+
{ dir: '.claude/commands', type: 'command' },
|
|
610
|
+
];
|
|
611
|
+
for (const { dir, type } of claudePaths) {
|
|
612
|
+
const fullPath = join(cwd, dir);
|
|
613
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
614
|
+
const files = readdirSync(fullPath);
|
|
615
|
+
for (const file of files) {
|
|
616
|
+
if (file.endsWith('.md') || file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
617
|
+
const filePath = join(fullPath, file);
|
|
618
|
+
const skill = parseSkillFile(filePath, cwd, type, 'claude');
|
|
619
|
+
if (skill) {
|
|
620
|
+
skills.push(skill);
|
|
621
|
+
if (verbose)
|
|
622
|
+
console.log(` Found Claude ${type}: ${skill.name}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// OpenCode agents and commands
|
|
629
|
+
const opencodePaths = [
|
|
630
|
+
{ dir: '.opencode/agent', type: 'droid' },
|
|
631
|
+
{ dir: '.opencode/command', type: 'command' },
|
|
632
|
+
];
|
|
633
|
+
for (const { dir, type } of opencodePaths) {
|
|
634
|
+
const fullPath = join(cwd, dir);
|
|
635
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
636
|
+
const files = readdirSync(fullPath);
|
|
637
|
+
for (const file of files) {
|
|
638
|
+
if (file.endsWith('.md') || file.endsWith('.yaml') || file.endsWith('.yml') || file.endsWith('.json')) {
|
|
639
|
+
const filePath = join(fullPath, file);
|
|
640
|
+
const skill = parseSkillFile(filePath, cwd, type, 'opencode');
|
|
641
|
+
if (skill) {
|
|
642
|
+
skills.push(skill);
|
|
643
|
+
if (verbose)
|
|
644
|
+
console.log(` Found OpenCode ${type}: ${skill.name}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// Discover artifacts (reusable code patterns, templates)
|
|
651
|
+
const artifactDirs = [
|
|
652
|
+
'templates',
|
|
653
|
+
'snippets',
|
|
654
|
+
'.github/workflows',
|
|
655
|
+
'scripts',
|
|
656
|
+
'tools',
|
|
657
|
+
];
|
|
658
|
+
for (const dir of artifactDirs) {
|
|
659
|
+
const fullPath = join(cwd, dir);
|
|
660
|
+
if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
|
|
661
|
+
const files = readdirSync(fullPath).slice(0, 20); // Limit to avoid too many
|
|
662
|
+
for (const file of files) {
|
|
663
|
+
const filePath = join(fullPath, file);
|
|
664
|
+
if (statSync(filePath).isFile()) {
|
|
665
|
+
skills.push({
|
|
666
|
+
name: basename(file, extname(file)),
|
|
667
|
+
path: relative(cwd, filePath),
|
|
668
|
+
type: 'artifact',
|
|
669
|
+
description: `Reusable ${dir} artifact`,
|
|
670
|
+
platform: 'generic',
|
|
671
|
+
});
|
|
672
|
+
if (verbose)
|
|
673
|
+
console.log(` Found artifact: ${file}`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return skills;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Parse a skill/droid/command file to extract metadata
|
|
682
|
+
*/
|
|
683
|
+
function parseSkillFile(filePath, cwd, type, platform) {
|
|
684
|
+
try {
|
|
685
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
686
|
+
const fileName = basename(filePath, extname(filePath));
|
|
687
|
+
let description = '';
|
|
688
|
+
// Try to extract description from different formats
|
|
689
|
+
if (filePath.endsWith('.md')) {
|
|
690
|
+
// Extract first paragraph or heading
|
|
691
|
+
const match = content.match(/^#\s+.+\n+(.+)/m);
|
|
692
|
+
if (match)
|
|
693
|
+
description = match[1].trim();
|
|
694
|
+
}
|
|
695
|
+
else if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
|
|
696
|
+
// Look for description field
|
|
697
|
+
const match = content.match(/description:\s*['"]?([^'"\n]+)/i);
|
|
698
|
+
if (match)
|
|
699
|
+
description = match[1].trim();
|
|
700
|
+
}
|
|
701
|
+
else if (filePath.endsWith('.json')) {
|
|
702
|
+
try {
|
|
703
|
+
const json = JSON.parse(content);
|
|
704
|
+
description = json.description || '';
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
// Ignore JSON parse errors
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
name: fileName,
|
|
712
|
+
path: relative(cwd, filePath),
|
|
713
|
+
type,
|
|
714
|
+
description: description || `${platform} ${type}`,
|
|
715
|
+
platform,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Convert discovered skills into memory entries
|
|
724
|
+
*/
|
|
725
|
+
export function extractSkillMemories(skills) {
|
|
726
|
+
const memories = [];
|
|
727
|
+
const now = new Date().toISOString();
|
|
728
|
+
// Group by type
|
|
729
|
+
const droids = skills.filter(s => s.type === 'droid');
|
|
730
|
+
const skillItems = skills.filter(s => s.type === 'skill');
|
|
731
|
+
const commands = skills.filter(s => s.type === 'command');
|
|
732
|
+
const artifacts = skills.filter(s => s.type === 'artifact');
|
|
733
|
+
// Create memory for available droids/agents
|
|
734
|
+
if (droids.length > 0) {
|
|
735
|
+
memories.push({
|
|
736
|
+
id: 'skills-droids-available',
|
|
737
|
+
timestamp: now,
|
|
738
|
+
type: 'observation',
|
|
739
|
+
content: `Available AI agents/droids: ${droids.map(d => `${d.name} (${d.platform})`).join(', ')}. These can be invoked for specialized tasks.`,
|
|
740
|
+
tags: ['droids', 'agents', 'capabilities'],
|
|
741
|
+
importance: 8,
|
|
742
|
+
metadata: {
|
|
743
|
+
source: 'skills-discovery',
|
|
744
|
+
droids: droids.map(d => ({ name: d.name, platform: d.platform, path: d.path })),
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
// Create memory for available skills
|
|
749
|
+
if (skillItems.length > 0) {
|
|
750
|
+
memories.push({
|
|
751
|
+
id: 'skills-skills-available',
|
|
752
|
+
timestamp: now,
|
|
753
|
+
type: 'observation',
|
|
754
|
+
content: `Available skills: ${skillItems.map(s => s.name).join(', ')}. These provide specialized capabilities.`,
|
|
755
|
+
tags: ['skills', 'capabilities'],
|
|
756
|
+
importance: 7,
|
|
757
|
+
metadata: {
|
|
758
|
+
source: 'skills-discovery',
|
|
759
|
+
skills: skillItems.map(s => ({ name: s.name, platform: s.platform, path: s.path })),
|
|
760
|
+
},
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
// Create memory for available commands
|
|
764
|
+
if (commands.length > 0) {
|
|
765
|
+
memories.push({
|
|
766
|
+
id: 'skills-commands-available',
|
|
767
|
+
timestamp: now,
|
|
768
|
+
type: 'observation',
|
|
769
|
+
content: `Available slash commands: ${commands.map(c => `/${c.name}`).join(', ')}. Use these for quick actions.`,
|
|
770
|
+
tags: ['commands', 'shortcuts'],
|
|
771
|
+
importance: 7,
|
|
772
|
+
metadata: {
|
|
773
|
+
source: 'skills-discovery',
|
|
774
|
+
commands: commands.map(c => ({ name: c.name, platform: c.platform, path: c.path })),
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
// Create memories for individual droids with descriptions
|
|
779
|
+
for (const droid of droids) {
|
|
780
|
+
if (droid.description && droid.description.length > 20) {
|
|
781
|
+
memories.push({
|
|
782
|
+
id: `skill-droid-${droid.name}`,
|
|
783
|
+
timestamp: now,
|
|
784
|
+
type: 'thought',
|
|
785
|
+
content: `Droid "${droid.name}" (${droid.platform}): ${droid.description}. Located at ${droid.path}.`,
|
|
786
|
+
tags: ['droid', droid.platform, droid.name],
|
|
787
|
+
importance: 6,
|
|
788
|
+
metadata: {
|
|
789
|
+
source: 'skills-discovery',
|
|
790
|
+
...droid,
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Create memory for useful artifacts/templates
|
|
796
|
+
const usefulArtifacts = artifacts.filter(a => a.path.includes('workflow') ||
|
|
797
|
+
a.path.includes('template') ||
|
|
798
|
+
a.path.includes('script'));
|
|
799
|
+
if (usefulArtifacts.length > 0) {
|
|
800
|
+
memories.push({
|
|
801
|
+
id: 'skills-artifacts-available',
|
|
802
|
+
timestamp: now,
|
|
803
|
+
type: 'observation',
|
|
804
|
+
content: `Useful project artifacts: ${usefulArtifacts.slice(0, 10).map(a => a.path).join(', ')}. These can be referenced or reused.`,
|
|
805
|
+
tags: ['artifacts', 'templates', 'reusable'],
|
|
806
|
+
importance: 5,
|
|
807
|
+
metadata: {
|
|
808
|
+
source: 'skills-discovery',
|
|
809
|
+
artifacts: usefulArtifacts.map(a => a.path),
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
return memories;
|
|
814
|
+
}
|
|
815
|
+
//# sourceMappingURL=prepopulate.js.map
|