ai-dev-analytics 1.1.1 → 1.1.3

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 (86) hide show
  1. package/README.md +26 -11
  2. package/README.zh-CN.md +26 -11
  3. package/dist/cli/commands/build.d.ts.map +1 -1
  4. package/dist/cli/commands/build.js +1 -4
  5. package/dist/cli/commands/build.js.map +1 -1
  6. package/dist/cli/commands/import.d.ts.map +1 -1
  7. package/dist/cli/commands/import.js +12 -4
  8. package/dist/cli/commands/import.js.map +1 -1
  9. package/dist/cli/commands/init.d.ts.map +1 -1
  10. package/dist/cli/commands/init.js +6 -2
  11. package/dist/cli/commands/init.js.map +1 -1
  12. package/dist/cli/commands/log.d.ts.map +1 -1
  13. package/dist/cli/commands/log.js +4 -3
  14. package/dist/cli/commands/log.js.map +1 -1
  15. package/dist/cli/commands/memory.d.ts +2 -0
  16. package/dist/cli/commands/memory.d.ts.map +1 -0
  17. package/dist/cli/commands/memory.js +197 -0
  18. package/dist/cli/commands/memory.js.map +1 -0
  19. package/dist/cli/commands/merge.d.ts.map +1 -1
  20. package/dist/cli/commands/merge.js +2 -0
  21. package/dist/cli/commands/merge.js.map +1 -1
  22. package/dist/cli/commands/migrate-dir.d.ts.map +1 -1
  23. package/dist/cli/commands/migrate-dir.js +30 -3
  24. package/dist/cli/commands/migrate-dir.js.map +1 -1
  25. package/dist/cli/commands/migrate-legacy.d.ts.map +1 -1
  26. package/dist/cli/commands/migrate-legacy.js +65 -14
  27. package/dist/cli/commands/migrate-legacy.js.map +1 -1
  28. package/dist/cli/commands/rules.d.ts.map +1 -1
  29. package/dist/cli/commands/rules.js +12 -15
  30. package/dist/cli/commands/rules.js.map +1 -1
  31. package/dist/cli/commands/skills.d.ts.map +1 -1
  32. package/dist/cli/commands/skills.js +7 -7
  33. package/dist/cli/commands/skills.js.map +1 -1
  34. package/dist/cli/commands/start.d.ts.map +1 -1
  35. package/dist/cli/commands/start.js +9 -23
  36. package/dist/cli/commands/start.js.map +1 -1
  37. package/dist/cli/commands/update.d.ts.map +1 -1
  38. package/dist/cli/commands/update.js +15 -109
  39. package/dist/cli/commands/update.js.map +1 -1
  40. package/dist/cli/index.js +6 -0
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/mcp/server.d.ts.map +1 -1
  43. package/dist/mcp/server.js +266 -3
  44. package/dist/mcp/server.js.map +1 -1
  45. package/dist/schemas/aida-project.d.ts +73 -0
  46. package/dist/schemas/aida-project.d.ts.map +1 -0
  47. package/dist/schemas/aida-project.js +2 -0
  48. package/dist/schemas/aida-project.js.map +1 -0
  49. package/dist/utils/ai-build.d.ts +7 -3
  50. package/dist/utils/ai-build.d.ts.map +1 -1
  51. package/dist/utils/ai-build.js +154 -49
  52. package/dist/utils/ai-build.js.map +1 -1
  53. package/dist/utils/guide.d.ts.map +1 -1
  54. package/dist/utils/guide.js +15 -9
  55. package/dist/utils/guide.js.map +1 -1
  56. package/dist/utils/import.d.ts +11 -9
  57. package/dist/utils/import.d.ts.map +1 -1
  58. package/dist/utils/import.js +94 -30
  59. package/dist/utils/import.js.map +1 -1
  60. package/dist/utils/memory.d.ts +49 -0
  61. package/dist/utils/memory.d.ts.map +1 -0
  62. package/dist/utils/memory.js +657 -0
  63. package/dist/utils/memory.js.map +1 -0
  64. package/dist/utils/paths.d.ts +22 -0
  65. package/dist/utils/paths.d.ts.map +1 -1
  66. package/dist/utils/paths.js +48 -1
  67. package/dist/utils/paths.js.map +1 -1
  68. package/dist/utils/rules.d.ts +5 -2
  69. package/dist/utils/rules.d.ts.map +1 -1
  70. package/dist/utils/rules.js +54 -48
  71. package/dist/utils/rules.js.map +1 -1
  72. package/dist/utils/skills.d.ts +6 -0
  73. package/dist/utils/skills.d.ts.map +1 -1
  74. package/dist/utils/skills.js +10 -3
  75. package/dist/utils/skills.js.map +1 -1
  76. package/package.json +1 -1
  77. package/src/assets/skills/audit.md +6 -6
  78. package/src/assets/skills/bug-fixer.md +3 -3
  79. package/src/assets/skills/code-generator.md +3 -3
  80. package/src/assets/skills/dev-flower.md +2 -2
  81. package/src/assets/skills/deviation-recorder.md +5 -5
  82. package/src/assets/skills/requirement-analyzer.md +2 -2
  83. package/src/assets/skills/rules-evolver.md +5 -5
  84. package/src/assets/skills/self-reviewer.md +3 -3
  85. package/src/assets/skills/task-splitter.md +2 -2
  86. package/src/assets/skills/workflow-orchestrator.md +9 -9
@@ -0,0 +1,657 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { basename, resolve } from 'node:path';
3
+ import { ensureDir, fileExists, readJson, readText, writeJson, writeText } from './fs.js';
4
+ import { branchDir, memoriesDir, memoryIndexPath, moduleMemoriesDir, moduleMemoryPath, moduleMemoryViewPath, requirementPath, runContextPath, runMemoryPackViewPath, runContextViewPath, runsDir, } from './paths.js';
5
+ function uniqueStrings(values) {
6
+ const result = [];
7
+ const seen = new Set();
8
+ for (const value of values) {
9
+ const normalized = `${value || ''}`.trim();
10
+ if (!normalized || seen.has(normalized))
11
+ continue;
12
+ seen.add(normalized);
13
+ result.push(normalized);
14
+ }
15
+ return result;
16
+ }
17
+ function topItems(values, limit = 8) {
18
+ return uniqueStrings(values).slice(0, limit);
19
+ }
20
+ function walkJsonFiles(rootDir) {
21
+ if (!fileExists(rootDir))
22
+ return [];
23
+ const result = [];
24
+ const stack = [rootDir];
25
+ while (stack.length > 0) {
26
+ const current = stack.pop();
27
+ for (const name of readdirSync(current)) {
28
+ const full = resolve(current, name);
29
+ const stat = statSync(full);
30
+ if (stat.isDirectory()) {
31
+ stack.push(full);
32
+ }
33
+ else if (name.endsWith('.json')) {
34
+ result.push(full);
35
+ }
36
+ }
37
+ }
38
+ return result.sort();
39
+ }
40
+ function walkRunJsonFiles(rootDir) {
41
+ if (!fileExists(rootDir))
42
+ return [];
43
+ const result = [];
44
+ for (const branchName of readdirSync(rootDir)) {
45
+ const branchPath = resolve(rootDir, branchName);
46
+ if (!statSync(branchPath).isDirectory())
47
+ continue;
48
+ for (const child of readdirSync(branchPath)) {
49
+ const runPath = resolve(branchPath, child, 'run.json');
50
+ if (fileExists(runPath))
51
+ result.push(runPath);
52
+ }
53
+ }
54
+ return result.sort();
55
+ }
56
+ function extractTicket(value) {
57
+ const match = value.match(/\b([A-Z]{2,}-\d+)\b/);
58
+ return match?.[1];
59
+ }
60
+ function extractMarkdownSections(raw) {
61
+ const sections = new Map();
62
+ let current = '__lead__';
63
+ let buffer = [];
64
+ const flush = () => {
65
+ const text = buffer.join('\n').trim();
66
+ if (text)
67
+ sections.set(current.toLowerCase(), text);
68
+ buffer = [];
69
+ };
70
+ for (const line of raw.split('\n')) {
71
+ const heading = line.match(/^#{1,6}\s+(.+)$/);
72
+ if (heading) {
73
+ flush();
74
+ current = heading[1].trim();
75
+ }
76
+ else {
77
+ buffer.push(line);
78
+ }
79
+ }
80
+ flush();
81
+ return sections;
82
+ }
83
+ function extractBulletItems(raw) {
84
+ const items = [];
85
+ for (const line of raw.split('\n')) {
86
+ const trimmed = line.trim();
87
+ if (!trimmed)
88
+ continue;
89
+ const bullet = trimmed.match(/^[-*]\s+(.+)$/);
90
+ const ordered = trimmed.match(/^\d+\.\s+(.+)$/);
91
+ if (bullet)
92
+ items.push(bullet[1].trim());
93
+ else if (ordered)
94
+ items.push(ordered[1].trim());
95
+ }
96
+ return uniqueStrings(items);
97
+ }
98
+ function findSectionItems(raw, keys) {
99
+ const sections = extractMarkdownSections(raw);
100
+ for (const [heading, content] of sections) {
101
+ if (keys.some((key) => heading.includes(key))) {
102
+ const items = extractBulletItems(content);
103
+ if (items.length > 0)
104
+ return items;
105
+ }
106
+ }
107
+ return [];
108
+ }
109
+ function extractSummaryFromAnalysis(raw) {
110
+ const sections = extractMarkdownSections(raw);
111
+ for (const [heading, content] of sections) {
112
+ if (heading.includes('概述') || heading.includes('摘要') || heading.includes('summary') || heading.includes('overview')) {
113
+ const paragraph = content
114
+ .split('\n')
115
+ .map((line) => line.trim())
116
+ .find((line) => line && !line.startsWith('- ') && !line.startsWith('* ') && !line.startsWith('>'));
117
+ if (paragraph)
118
+ return paragraph;
119
+ }
120
+ }
121
+ const lead = sections.get('__lead__');
122
+ if (!lead)
123
+ return '';
124
+ return lead
125
+ .split('\n')
126
+ .map((line) => line.trim())
127
+ .find((line) => line && !line.startsWith('#') && !line.startsWith('- ') && !line.startsWith('* '))
128
+ || '';
129
+ }
130
+ function loadRequirement(projectRoot, branchName) {
131
+ const path = requirementPath(projectRoot, branchName);
132
+ if (!fileExists(path))
133
+ return null;
134
+ return readJson(path);
135
+ }
136
+ function loadAnalysis(projectRoot, branchName) {
137
+ const path = resolve(branchDir(projectRoot, branchName), 'analysis.md');
138
+ if (!fileExists(path))
139
+ return '';
140
+ return readText(path);
141
+ }
142
+ function loadBranchRuns(projectRoot, branchName) {
143
+ const dir = branchDir(projectRoot, branchName);
144
+ if (!fileExists(dir))
145
+ return [];
146
+ const runs = [];
147
+ for (const child of readdirSync(dir)) {
148
+ const runPath = resolve(dir, child, 'run.json');
149
+ if (fileExists(runPath)) {
150
+ try {
151
+ runs.push(readJson(runPath));
152
+ }
153
+ catch {
154
+ // Ignore invalid legacy run files during migration.
155
+ }
156
+ }
157
+ }
158
+ return runs;
159
+ }
160
+ function pickKeyFiles(runs) {
161
+ const score = new Map();
162
+ for (const run of runs) {
163
+ for (const file of run.files || []) {
164
+ const weight = (file.linesAdded || 0) + (file.linesRemoved || 0) + ((file.changeCount || 1) * 5);
165
+ score.set(file.path, (score.get(file.path) || 0) + weight);
166
+ }
167
+ }
168
+ return [...score.entries()]
169
+ .sort((a, b) => b[1] - a[1])
170
+ .slice(0, 8)
171
+ .map(([path]) => path);
172
+ }
173
+ function deriveCurrentPhase(runs) {
174
+ const tasks = runs.flatMap((run) => run.tasks || []);
175
+ if (tasks.some((task) => task.status === 'in-progress'))
176
+ return 'In Progress';
177
+ if (tasks.some((task) => task.status === 'pending'))
178
+ return 'Planned';
179
+ if (tasks.some((task) => task.status === 'done'))
180
+ return 'Completed';
181
+ return 'Not Started';
182
+ }
183
+ function inferModules(requirement, runs) {
184
+ if (requirement?.modules?.length) {
185
+ return uniqueStrings(requirement.modules.map((module) => module.name).filter(Boolean));
186
+ }
187
+ return uniqueStrings(runs
188
+ .flatMap((run) => run.tasks || [])
189
+ .map((task) => task.stageName)
190
+ .filter((stage) => stage && stage !== 'default'));
191
+ }
192
+ function renderListSection(title, values) {
193
+ if (values.length === 0)
194
+ return `## ${title}\n\n- None\n`;
195
+ return `## ${title}\n\n${values.map((value) => `- ${value}`).join('\n')}\n`;
196
+ }
197
+ function renderModuleMemoryCompact(record) {
198
+ const lines = [
199
+ `## Module: ${record.title}`,
200
+ '',
201
+ `- Key: ${record.moduleKey}`,
202
+ `- Updated At: ${record.updatedAt}`,
203
+ '',
204
+ record.summary || 'No summary yet.',
205
+ '',
206
+ renderListSection('Entry Files', record.entryFiles.slice(0, 6)).trimEnd(),
207
+ '',
208
+ renderListSection('Decisions', record.decisions.slice(0, 6)).trimEnd(),
209
+ '',
210
+ renderListSection('Constraints', record.constraints.slice(0, 6)).trimEnd(),
211
+ '',
212
+ renderListSection('Pitfalls', record.pitfalls.slice(0, 6)).trimEnd(),
213
+ '',
214
+ ];
215
+ return lines.join('\n').trimEnd();
216
+ }
217
+ function splitQueryTokens(query) {
218
+ return uniqueStrings(query
219
+ .toLowerCase()
220
+ .split(/[\s,/._-]+/)
221
+ .filter((item) => item.length > 1));
222
+ }
223
+ function scoreText(query, tokens, candidate) {
224
+ const text = candidate.toLowerCase();
225
+ let score = 0;
226
+ if (!candidate)
227
+ return score;
228
+ if (text.includes(query))
229
+ score += 8;
230
+ for (const token of tokens) {
231
+ if (text.includes(token))
232
+ score += 3;
233
+ }
234
+ return score;
235
+ }
236
+ export function normalizeModuleKey(value) {
237
+ return value
238
+ .trim()
239
+ .toLowerCase()
240
+ .replace(/[^a-z0-9/_\u4e00-\u9fa5-]+/g, '-')
241
+ .replace(/\/+/g, '/')
242
+ .replace(/-+/g, '-')
243
+ .replace(/^-|-$/g, '');
244
+ }
245
+ export function loadMemoryIndex(projectRoot) {
246
+ const path = memoryIndexPath(projectRoot);
247
+ if (!fileExists(path)) {
248
+ return {
249
+ updatedAt: new Date().toISOString(),
250
+ modules: [],
251
+ };
252
+ }
253
+ return readJson(path);
254
+ }
255
+ export function saveMemoryIndex(projectRoot, index) {
256
+ ensureDir(memoriesDir(projectRoot));
257
+ writeJson(memoryIndexPath(projectRoot), index);
258
+ }
259
+ export function loadModuleMemory(projectRoot, moduleKey) {
260
+ const normalized = normalizeModuleKey(moduleKey);
261
+ const path = moduleMemoryPath(projectRoot, normalized);
262
+ if (!fileExists(path))
263
+ return null;
264
+ return readJson(path);
265
+ }
266
+ function upsertMemoryIndexEntry(projectRoot, record) {
267
+ const index = loadMemoryIndex(projectRoot);
268
+ const entry = {
269
+ key: record.moduleKey,
270
+ title: record.title,
271
+ summary: record.summary,
272
+ keywords: topItems([record.moduleKey, record.title, ...record.keywords], 12),
273
+ paths: topItems([...record.entryFiles, ...record.relatedPaths], 12),
274
+ updatedAt: record.updatedAt,
275
+ };
276
+ const next = index.modules.filter((item) => item.key !== record.moduleKey);
277
+ next.push(entry);
278
+ next.sort((a, b) => a.key.localeCompare(b.key));
279
+ saveMemoryIndex(projectRoot, {
280
+ updatedAt: new Date().toISOString(),
281
+ modules: next,
282
+ });
283
+ }
284
+ export function saveModuleMemory(projectRoot, record) {
285
+ ensureDir(moduleMemoriesDir(projectRoot));
286
+ writeJson(moduleMemoryPath(projectRoot, record.moduleKey), record);
287
+ upsertMemoryIndexEntry(projectRoot, record);
288
+ }
289
+ export function loadRunContext(projectRoot, branchName) {
290
+ const path = runContextPath(projectRoot, branchName);
291
+ if (!fileExists(path))
292
+ return null;
293
+ return readJson(path);
294
+ }
295
+ export function saveRunContext(projectRoot, branchName, record) {
296
+ ensureDir(branchDir(projectRoot, branchName));
297
+ writeJson(runContextPath(projectRoot, branchName), record);
298
+ }
299
+ export function renderModuleMemoryMarkdown(record) {
300
+ const lines = [
301
+ '# Module Memory',
302
+ '',
303
+ `- Module Key: ${record.moduleKey}`,
304
+ `- Title: ${record.title}`,
305
+ `- Updated At: ${record.updatedAt}`,
306
+ '',
307
+ '## Summary',
308
+ '',
309
+ record.summary || 'No summary yet.',
310
+ '',
311
+ renderListSection('Keywords', record.keywords).trimEnd(),
312
+ '',
313
+ renderListSection('Entry Files', record.entryFiles).trimEnd(),
314
+ '',
315
+ renderListSection('Related Paths', record.relatedPaths).trimEnd(),
316
+ '',
317
+ renderListSection('Data Flow', record.dataFlow).trimEnd(),
318
+ '',
319
+ renderListSection('Decisions', record.decisions).trimEnd(),
320
+ '',
321
+ renderListSection('Constraints', record.constraints).trimEnd(),
322
+ '',
323
+ renderListSection('Pitfalls', record.pitfalls).trimEnd(),
324
+ '',
325
+ renderListSection('Related Rules', record.relatedRules).trimEnd(),
326
+ '',
327
+ '## Related Tickets',
328
+ '',
329
+ ...(record.tickets.length > 0
330
+ ? record.tickets.map((ticket) => `- ${[ticket.ticket, ticket.branch, ticket.summary].filter(Boolean).join(' | ')}`)
331
+ : ['- None']),
332
+ '',
333
+ ];
334
+ return `${lines.join('\n').trimEnd()}\n`;
335
+ }
336
+ export function renderRunContextMarkdown(record) {
337
+ const lines = [
338
+ '# Run Context',
339
+ '',
340
+ `- Branch: ${record.branch}`,
341
+ `- Title: ${record.title}`,
342
+ `- Ticket: ${record.ticket || '-'}`,
343
+ `- Current Phase: ${record.currentPhase}`,
344
+ `- Updated At: ${record.updatedAt}`,
345
+ '',
346
+ '## Summary',
347
+ '',
348
+ record.summary || 'No summary yet.',
349
+ '',
350
+ renderListSection('Modules', record.modules).trimEnd(),
351
+ '',
352
+ renderListSection('Completed', record.completed).trimEnd(),
353
+ '',
354
+ renderListSection('In Progress', record.inProgress).trimEnd(),
355
+ '',
356
+ renderListSection('Next', record.next).trimEnd(),
357
+ '',
358
+ renderListSection('Decisions', record.decisions).trimEnd(),
359
+ '',
360
+ renderListSection('Constraints', record.constraints).trimEnd(),
361
+ '',
362
+ renderListSection('Key Files', record.keyFiles).trimEnd(),
363
+ '',
364
+ renderListSection('Risks', record.risks).trimEnd(),
365
+ '',
366
+ ];
367
+ return `${lines.join('\n').trimEnd()}\n`;
368
+ }
369
+ export function renderRunMemoryPackMarkdown(context, modules) {
370
+ const lines = [
371
+ '# Runtime Memory Pack',
372
+ '',
373
+ '> This file is auto-generated from branch context and related module memories.',
374
+ '',
375
+ renderRunContextMarkdown(context).trimEnd(),
376
+ ];
377
+ if (modules.length > 0) {
378
+ lines.push('', '# Related Module Memories', '');
379
+ for (const module of modules) {
380
+ lines.push(renderModuleMemoryCompact(module), '');
381
+ }
382
+ }
383
+ return `${lines.join('\n').trimEnd()}\n`;
384
+ }
385
+ export function buildMemoryViews(projectRoot) {
386
+ ensureDir(moduleMemoriesDir(projectRoot));
387
+ let moduleViews = 0;
388
+ for (const file of walkJsonFiles(moduleMemoriesDir(projectRoot))) {
389
+ if (basename(file) === 'index.json')
390
+ continue;
391
+ const record = readJson(file);
392
+ writeText(moduleMemoryViewPath(projectRoot, record.moduleKey), renderModuleMemoryMarkdown(record));
393
+ moduleViews++;
394
+ }
395
+ let contextViews = 0;
396
+ let packViews = 0;
397
+ const root = runsDir(projectRoot);
398
+ if (!fileExists(root))
399
+ return { moduleViews, contextViews, packViews };
400
+ for (const branchName of readdirSync(root)) {
401
+ const safeBranchDir = resolve(root, branchName);
402
+ if (!fileExists(safeBranchDir) || !statSync(safeBranchDir).isDirectory())
403
+ continue;
404
+ const contextPath = resolve(safeBranchDir, 'context.json');
405
+ if (!fileExists(contextPath))
406
+ continue;
407
+ const record = readJson(contextPath);
408
+ writeText(runContextViewPath(projectRoot, record.branch), renderRunContextMarkdown(record));
409
+ contextViews++;
410
+ const modules = record.modules
411
+ .map((moduleName) => loadModuleMemory(projectRoot, moduleName))
412
+ .filter((item) => item !== null);
413
+ writeText(runMemoryPackViewPath(projectRoot, record.branch), renderRunMemoryPackMarkdown(record, modules));
414
+ packViews++;
415
+ }
416
+ return { moduleViews, contextViews, packViews };
417
+ }
418
+ export function buildRunContextFromBranch(projectRoot, branchName) {
419
+ const requirement = loadRequirement(projectRoot, branchName);
420
+ const analysis = loadAnalysis(projectRoot, branchName);
421
+ const runs = loadBranchRuns(projectRoot, branchName);
422
+ if (!requirement && !analysis && runs.length === 0) {
423
+ return null;
424
+ }
425
+ const modules = inferModules(requirement, runs);
426
+ const completed = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'done').map((task) => task.title));
427
+ const inProgress = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'in-progress').map((task) => task.title));
428
+ const next = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'pending').map((task) => task.title));
429
+ const decisions = topItems(findSectionItems(analysis, ['决策', 'decision', '方案']));
430
+ const constraints = topItems(findSectionItems(analysis, ['约束', 'constraint', '限制']));
431
+ const analysisRisks = findSectionItems(analysis, ['风险', 'risk', '注意']);
432
+ const runRisks = [
433
+ ...runs.flatMap((run) => run.bugs || []).filter((bug) => bug.status !== 'fixed').map((bug) => bug.title),
434
+ ...runs.flatMap((run) => run.deviations || []).map((deviation) => deviation.title),
435
+ ];
436
+ const title = requirement?.title || extractTicket(branchName) || branchName;
437
+ return {
438
+ branch: branchName,
439
+ ticket: extractTicket(requirement?.title || '') || extractTicket(branchName),
440
+ title,
441
+ summary: requirement?.summary || extractSummaryFromAnalysis(analysis) || title,
442
+ currentPhase: deriveCurrentPhase(runs),
443
+ modules,
444
+ completed,
445
+ inProgress,
446
+ next,
447
+ decisions,
448
+ constraints,
449
+ keyFiles: pickKeyFiles(runs),
450
+ risks: topItems([...analysisRisks, ...runRisks]),
451
+ updatedAt: new Date().toISOString(),
452
+ };
453
+ }
454
+ function collectRelatedPaths(moduleName, branchContext, runs) {
455
+ const query = moduleName.toLowerCase();
456
+ const tokens = splitQueryTokens(query);
457
+ const scored = new Map();
458
+ for (const path of branchContext.keyFiles) {
459
+ const score = scoreText(query, tokens, path);
460
+ if (score > 0)
461
+ scored.set(path, score);
462
+ }
463
+ for (const run of runs) {
464
+ for (const task of run.tasks || []) {
465
+ if (task.stageName !== moduleName)
466
+ continue;
467
+ for (const file of run.files || []) {
468
+ const bonus = (file.linesAdded || 0) + (file.linesRemoved || 0) + 10;
469
+ scored.set(file.path, (scored.get(file.path) || 0) + bonus);
470
+ }
471
+ }
472
+ }
473
+ const ranked = [...scored.entries()].sort((a, b) => b[1] - a[1]).map(([path]) => path);
474
+ return ranked.length > 0 ? ranked.slice(0, 8) : branchContext.keyFiles.slice(0, 5);
475
+ }
476
+ export function upsertModuleMemory(projectRoot, input) {
477
+ const moduleKey = normalizeModuleKey(input.moduleKey);
478
+ const existing = loadModuleMemory(projectRoot, moduleKey);
479
+ const record = {
480
+ moduleKey,
481
+ title: input.title || existing?.title || moduleKey,
482
+ summary: input.summary || existing?.summary || '',
483
+ keywords: topItems([...(existing?.keywords || []), ...(input.keywords || []), moduleKey, input.title || ''], 16),
484
+ entryFiles: topItems([...(existing?.entryFiles || []), ...(input.entryFiles || [])]),
485
+ relatedPaths: topItems([...(existing?.relatedPaths || []), ...(input.relatedPaths || [])], 12),
486
+ dataFlow: topItems([...(existing?.dataFlow || []), ...(input.dataFlow || [])]),
487
+ decisions: topItems([...(existing?.decisions || []), ...(input.decisions || [])]),
488
+ constraints: topItems([...(existing?.constraints || []), ...(input.constraints || [])]),
489
+ pitfalls: topItems([...(existing?.pitfalls || []), ...(input.pitfalls || [])]),
490
+ relatedRules: topItems([...(existing?.relatedRules || []), ...(input.relatedRules || [])], 12),
491
+ tickets: [
492
+ ...(existing?.tickets || []),
493
+ ...((input.tickets || []).filter((ticket) => ticket.summary.trim().length > 0)),
494
+ ].filter((ticket, index, array) => array.findIndex((item) => item.ticket === ticket.ticket
495
+ && item.branch === ticket.branch
496
+ && item.summary === ticket.summary) === index),
497
+ updatedAt: new Date().toISOString(),
498
+ };
499
+ saveModuleMemory(projectRoot, record);
500
+ return record;
501
+ }
502
+ export function updateRunContext(projectRoot, input) {
503
+ const existing = loadRunContext(projectRoot, input.branch);
504
+ const record = {
505
+ branch: input.branch,
506
+ ticket: input.ticket ?? existing?.ticket,
507
+ title: input.title || existing?.title || input.branch,
508
+ summary: input.summary || existing?.summary || '',
509
+ currentPhase: input.currentPhase || existing?.currentPhase || 'Not Started',
510
+ modules: topItems([...(existing?.modules || []), ...(input.modules || [])], 12),
511
+ completed: topItems([...(existing?.completed || []), ...(input.completed || [])], 12),
512
+ inProgress: topItems([...(existing?.inProgress || []), ...(input.inProgress || [])], 12),
513
+ next: topItems([...(existing?.next || []), ...(input.next || [])], 12),
514
+ decisions: topItems([...(existing?.decisions || []), ...(input.decisions || [])], 12),
515
+ constraints: topItems([...(existing?.constraints || []), ...(input.constraints || [])], 12),
516
+ keyFiles: topItems([...(existing?.keyFiles || []), ...(input.keyFiles || [])], 12),
517
+ risks: topItems([...(existing?.risks || []), ...(input.risks || [])], 12),
518
+ updatedAt: new Date().toISOString(),
519
+ };
520
+ saveRunContext(projectRoot, input.branch, record);
521
+ return record;
522
+ }
523
+ export function searchModuleMemories(projectRoot, query, pathHints = []) {
524
+ const normalizedQuery = query.trim().toLowerCase();
525
+ if (!normalizedQuery)
526
+ return [];
527
+ const tokens = splitQueryTokens(normalizedQuery);
528
+ const hints = pathHints.map((hint) => hint.toLowerCase());
529
+ const index = loadMemoryIndex(projectRoot);
530
+ return index.modules
531
+ .map((entry) => {
532
+ let score = 0;
533
+ score += scoreText(normalizedQuery, tokens, entry.key) * 2;
534
+ score += scoreText(normalizedQuery, tokens, entry.title) * 2;
535
+ score += scoreText(normalizedQuery, tokens, entry.summary);
536
+ score += entry.keywords.reduce((sum, keyword) => sum + scoreText(normalizedQuery, tokens, keyword), 0);
537
+ score += entry.paths.reduce((sum, path) => sum + scoreText(normalizedQuery, tokens, path), 0);
538
+ for (const hint of hints) {
539
+ if (entry.paths.some((path) => path.toLowerCase().includes(hint)))
540
+ score += 10;
541
+ }
542
+ return { ...entry, score };
543
+ })
544
+ .filter((entry) => entry.score > 0)
545
+ .sort((a, b) => b.score - a.score || a.key.localeCompare(b.key))
546
+ .slice(0, 8);
547
+ }
548
+ export function migrateLegacyMemories(projectRoot) {
549
+ ensureDir(moduleMemoriesDir(projectRoot));
550
+ const branchesDir = runsDir(projectRoot);
551
+ if (!fileExists(branchesDir)) {
552
+ return { branches: 0, contextsWritten: 0, moduleMemoriesWritten: 0, modulesTouched: [] };
553
+ }
554
+ let branches = 0;
555
+ let contextsWritten = 0;
556
+ let moduleMemoriesWritten = 0;
557
+ const modulesTouched = new Set();
558
+ for (const safeBranch of readdirSync(branchesDir)) {
559
+ const branchPath = resolve(branchesDir, safeBranch);
560
+ if (!statSync(branchPath).isDirectory())
561
+ continue;
562
+ const requirement = fileExists(resolve(branchPath, 'requirement.json'));
563
+ const analysis = fileExists(resolve(branchPath, 'analysis.md'));
564
+ const runFiles = walkRunJsonFiles(branchesDir).filter((file) => file.includes(`/${safeBranch}/`));
565
+ if (!requirement && !analysis && runFiles.length === 0)
566
+ continue;
567
+ const requirementData = requirement
568
+ ? readJson(resolve(branchPath, 'requirement.json'))
569
+ : null;
570
+ const branchName = requirementData?.branch || safeBranch.replace(/-/g, '/');
571
+ const context = buildRunContextFromBranch(projectRoot, branchName);
572
+ if (!context)
573
+ continue;
574
+ branches++;
575
+ saveRunContext(projectRoot, branchName, context);
576
+ contextsWritten++;
577
+ const runs = loadBranchRuns(projectRoot, branchName);
578
+ const req = loadRequirement(projectRoot, branchName);
579
+ const moduleCandidates = req?.modules?.length
580
+ ? req.modules.map((module) => ({ name: module.name, description: module.description }))
581
+ : context.modules.map((name) => ({ name, description: '' }));
582
+ for (const candidate of moduleCandidates) {
583
+ if (!candidate.name.trim())
584
+ continue;
585
+ const moduleKey = normalizeModuleKey(candidate.name);
586
+ const relatedPaths = collectRelatedPaths(candidate.name, context, runs);
587
+ upsertModuleMemory(projectRoot, {
588
+ moduleKey,
589
+ title: candidate.name,
590
+ summary: candidate.description || context.summary,
591
+ keywords: [candidate.name, moduleKey, context.title],
592
+ entryFiles: relatedPaths.slice(0, 5),
593
+ relatedPaths,
594
+ decisions: context.decisions,
595
+ constraints: context.constraints,
596
+ pitfalls: context.risks,
597
+ tickets: [{
598
+ ticket: context.ticket,
599
+ branch: branchName,
600
+ summary: context.summary,
601
+ updatedAt: context.updatedAt,
602
+ }],
603
+ });
604
+ moduleMemoriesWritten++;
605
+ modulesTouched.add(moduleKey);
606
+ }
607
+ }
608
+ buildMemoryViews(projectRoot);
609
+ return {
610
+ branches,
611
+ contextsWritten,
612
+ moduleMemoriesWritten,
613
+ modulesTouched: [...modulesTouched].sort(),
614
+ };
615
+ }
616
+ export function rebuildCurrentBranchMemory(projectRoot, branchName) {
617
+ const context = buildRunContextFromBranch(projectRoot, branchName);
618
+ if (!context) {
619
+ return { context: null, modules: [] };
620
+ }
621
+ saveRunContext(projectRoot, branchName, context);
622
+ const req = loadRequirement(projectRoot, branchName);
623
+ const runs = loadBranchRuns(projectRoot, branchName);
624
+ const modules = [];
625
+ for (const moduleName of inferModules(req, runs)) {
626
+ const relatedPaths = collectRelatedPaths(moduleName, context, runs);
627
+ modules.push(upsertModuleMemory(projectRoot, {
628
+ moduleKey: moduleName,
629
+ title: moduleName,
630
+ summary: req?.modules?.find((module) => module.name === moduleName)?.description || context.summary,
631
+ keywords: [moduleName, context.title],
632
+ entryFiles: relatedPaths.slice(0, 5),
633
+ relatedPaths,
634
+ decisions: context.decisions,
635
+ constraints: context.constraints,
636
+ pitfalls: context.risks,
637
+ tickets: [{
638
+ ticket: context.ticket,
639
+ branch: branchName,
640
+ summary: context.summary,
641
+ updatedAt: context.updatedAt,
642
+ }],
643
+ }));
644
+ }
645
+ buildMemoryViews(projectRoot);
646
+ return { context, modules };
647
+ }
648
+ export function loadRunMemoryPack(projectRoot, branchName) {
649
+ const context = loadRunContext(projectRoot, branchName);
650
+ if (!context)
651
+ return null;
652
+ const modules = context.modules
653
+ .map((moduleName) => loadModuleMemory(projectRoot, moduleName))
654
+ .filter((item) => item !== null);
655
+ return { context, modules };
656
+ }
657
+ //# sourceMappingURL=memory.js.map