ai-dev-analytics 1.1.11 → 2.0.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/README.en.md +63 -55
- package/README.md +49 -60
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +0 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +70 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +17 -176
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/merge-data.d.ts.map +1 -1
- package/dist/cli/commands/merge-data.js +65 -5
- package/dist/cli/commands/merge-data.js.map +1 -1
- package/dist/cli/commands/merge.js +1 -1
- package/dist/cli/commands/merge.js.map +1 -1
- package/dist/cli/commands/migrate-legacy.d.ts.map +1 -1
- package/dist/cli/commands/migrate-legacy.js +7 -9
- package/dist/cli/commands/migrate-legacy.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +4 -1
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/reindex.d.ts.map +1 -1
- package/dist/cli/commands/reindex.js +4 -74
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +7 -2
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/rules.d.ts +1 -1
- package/dist/cli/commands/rules.d.ts.map +1 -1
- package/dist/cli/commands/rules.js +9 -8
- package/dist/cli/commands/rules.js.map +1 -1
- package/dist/cli/commands/skills.d.ts.map +1 -1
- package/dist/cli/commands/skills.js +5 -4
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/sync.d.ts +2 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +37 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +24 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/schemas/aida-project.d.ts +23 -1
- package/dist/schemas/aida-project.d.ts.map +1 -1
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +2 -9
- package/dist/server/api.js.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/utils/ai-build.d.ts +3 -3
- package/dist/utils/ai-build.d.ts.map +1 -1
- package/dist/utils/ai-build.js +33 -86
- package/dist/utils/ai-build.js.map +1 -1
- package/dist/utils/guide.d.ts.map +1 -1
- package/dist/utils/guide.js +90 -55
- package/dist/utils/guide.js.map +1 -1
- package/dist/utils/memory.d.ts +2 -0
- package/dist/utils/memory.d.ts.map +1 -1
- package/dist/utils/memory.js +491 -98
- package/dist/utils/memory.js.map +1 -1
- package/dist/utils/paths.d.ts +8 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +19 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/project-health.d.ts +38 -0
- package/dist/utils/project-health.d.ts.map +1 -0
- package/dist/utils/project-health.js +263 -0
- package/dist/utils/project-health.js.map +1 -0
- package/dist/utils/registry.d.ts +11 -0
- package/dist/utils/registry.d.ts.map +1 -0
- package/dist/utils/registry.js +65 -0
- package/dist/utils/registry.js.map +1 -0
- package/dist/utils/rules.d.ts.map +1 -1
- package/dist/utils/rules.js +50 -8
- package/dist/utils/rules.js.map +1 -1
- package/dist/utils/skills.d.ts +1 -0
- package/dist/utils/skills.d.ts.map +1 -1
- package/dist/utils/skills.js +21 -5
- package/dist/utils/skills.js.map +1 -1
- package/dist/utils/summary.d.ts +8 -0
- package/dist/utils/summary.d.ts.map +1 -0
- package/dist/utils/summary.js +260 -0
- package/dist/utils/summary.js.map +1 -0
- package/package.json +2 -2
package/dist/utils/memory.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readdirSync, statSync } from 'node:fs';
|
|
1
|
+
import { readdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
2
2
|
import { basename, dirname, resolve } from 'node:path';
|
|
3
3
|
import { ensureDir, fileExists, readJson, readText, writeJson, writeText } from './fs.js';
|
|
4
|
-
import { branchDir, memoriesDir, memoryIndexPath, moduleMemoriesDir, moduleMemoryPath, moduleMemoryViewPath, requirementPath, runContextPath,
|
|
4
|
+
import { branchDir, legacyModuleMemoryPath, legacyModuleMemoryViewPath, memoriesDir, memoryIndexPath, moduleMemoriesDir, moduleMemoryPath, moduleMemoryViewPath, requirementPath, runContextPath, runsDir, } from './paths.js';
|
|
5
|
+
const MEMORY_SCHEMA_VERSION = '2.0';
|
|
5
6
|
function uniqueStrings(values) {
|
|
6
7
|
const result = [];
|
|
7
8
|
const seen = new Set();
|
|
@@ -14,18 +15,71 @@ function uniqueStrings(values) {
|
|
|
14
15
|
}
|
|
15
16
|
return result;
|
|
16
17
|
}
|
|
18
|
+
function latestIso(a, b) {
|
|
19
|
+
if (!a)
|
|
20
|
+
return b || '';
|
|
21
|
+
if (!b)
|
|
22
|
+
return a;
|
|
23
|
+
return new Date(a).getTime() >= new Date(b).getTime() ? a : b;
|
|
24
|
+
}
|
|
17
25
|
function normalizeRepoPath(value) {
|
|
18
26
|
return value.trim().replace(/\\/g, '/');
|
|
19
27
|
}
|
|
28
|
+
function stripKnownSourcePrefix(path) {
|
|
29
|
+
const normalized = normalizeRepoPath(path);
|
|
30
|
+
const prefixes = [
|
|
31
|
+
'src/modules/',
|
|
32
|
+
'src/module/',
|
|
33
|
+
'src/features/',
|
|
34
|
+
'src/feature/',
|
|
35
|
+
'src/pages/',
|
|
36
|
+
'src/views/',
|
|
37
|
+
'src/components/',
|
|
38
|
+
'src/',
|
|
39
|
+
];
|
|
40
|
+
for (const prefix of prefixes) {
|
|
41
|
+
if (normalized.startsWith(prefix)) {
|
|
42
|
+
return normalized.slice(prefix.length);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
function isNoisePath(value) {
|
|
48
|
+
const path = normalizeRepoPath(value);
|
|
49
|
+
const fileName = basename(path);
|
|
50
|
+
return path.startsWith('.../')
|
|
51
|
+
|| fileName.startsWith('.')
|
|
52
|
+
|| fileName === 'yarn.lock'
|
|
53
|
+
|| fileName === 'package-lock.json'
|
|
54
|
+
|| fileName === 'pnpm-lock.yaml'
|
|
55
|
+
|| fileName === 'bun.lockb';
|
|
56
|
+
}
|
|
57
|
+
function isGeneratedToolingPath(value) {
|
|
58
|
+
const path = normalizeRepoPath(value);
|
|
59
|
+
return path === 'AGENTS.md'
|
|
60
|
+
|| path === 'CLAUDE.md'
|
|
61
|
+
|| path === '.mcp.json'
|
|
62
|
+
|| path.startsWith('.claude/')
|
|
63
|
+
|| path.startsWith('.cursor/')
|
|
64
|
+
|| path.startsWith('.codex/')
|
|
65
|
+
|| path.startsWith('.kiro/')
|
|
66
|
+
|| path.startsWith('.agents/')
|
|
67
|
+
|| path.startsWith('.agent/')
|
|
68
|
+
|| path.startsWith('.roo/')
|
|
69
|
+
|| path.startsWith('.roo-code/')
|
|
70
|
+
|| path.startsWith('.augment/')
|
|
71
|
+
|| path.startsWith('.gemini/')
|
|
72
|
+
|| path.startsWith('.vscode/')
|
|
73
|
+
|| path.startsWith('.lingma/')
|
|
74
|
+
|| path.startsWith('.windsurf/');
|
|
75
|
+
}
|
|
20
76
|
function isAidaRuntimePath(value) {
|
|
21
77
|
const path = normalizeRepoPath(value);
|
|
22
|
-
return path.startsWith('.aida/
|
|
23
|
-
|| path.startsWith('.aida/memories/')
|
|
24
|
-
|| path === '.aida/bootstrap-state.local.json';
|
|
78
|
+
return path.startsWith('.aida/');
|
|
25
79
|
}
|
|
26
80
|
function filterMeaningfulPaths(values, limit) {
|
|
27
81
|
const filtered = uniqueStrings(values.map((value) => normalizeRepoPath(value || '')))
|
|
28
|
-
.filter((value) => value && !isAidaRuntimePath(value));
|
|
82
|
+
.filter((value) => value && !isAidaRuntimePath(value) && !isNoisePath(value) && !isGeneratedToolingPath(value));
|
|
29
83
|
return typeof limit === 'number' ? filtered.slice(0, limit) : filtered;
|
|
30
84
|
}
|
|
31
85
|
function topItems(values, limit = 8) {
|
|
@@ -71,6 +125,46 @@ function walkMarkdownFiles(rootDir) {
|
|
|
71
125
|
}
|
|
72
126
|
return result.sort();
|
|
73
127
|
}
|
|
128
|
+
function pruneEmptyModuleMemoryDirs(rootDir) {
|
|
129
|
+
if (!fileExists(rootDir))
|
|
130
|
+
return;
|
|
131
|
+
for (const name of readdirSync(rootDir)) {
|
|
132
|
+
const full = resolve(rootDir, name);
|
|
133
|
+
if (!statSync(full).isDirectory())
|
|
134
|
+
continue;
|
|
135
|
+
pruneEmptyModuleMemoryDirs(full);
|
|
136
|
+
if (readdirSync(full).length === 0) {
|
|
137
|
+
rmSync(full, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function migrateLegacyNestedModuleMemoryLayout(projectRoot) {
|
|
142
|
+
const rootDir = moduleMemoriesDir(projectRoot);
|
|
143
|
+
if (!fileExists(rootDir))
|
|
144
|
+
return;
|
|
145
|
+
const files = [
|
|
146
|
+
...walkJsonFiles(rootDir).filter((file) => basename(file) !== 'index.json'),
|
|
147
|
+
...walkMarkdownFiles(rootDir),
|
|
148
|
+
];
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
const relative = file.slice(rootDir.length + 1).replace(/\\/g, '/');
|
|
151
|
+
if (!relative.includes('/'))
|
|
152
|
+
continue;
|
|
153
|
+
const moduleKey = relative.replace(/\.(json|md)$/u, '');
|
|
154
|
+
const target = file.endsWith('.json')
|
|
155
|
+
? moduleMemoryPath(projectRoot, moduleKey)
|
|
156
|
+
: moduleMemoryViewPath(projectRoot, moduleKey);
|
|
157
|
+
if (target === file)
|
|
158
|
+
continue;
|
|
159
|
+
ensureDir(dirname(target));
|
|
160
|
+
if (fileExists(target)) {
|
|
161
|
+
rmSync(file, { force: true });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
renameSync(file, target);
|
|
165
|
+
}
|
|
166
|
+
pruneEmptyModuleMemoryDirs(rootDir);
|
|
167
|
+
}
|
|
74
168
|
function walkRunJsonFiles(rootDir) {
|
|
75
169
|
if (!fileExists(rootDir))
|
|
76
170
|
return [];
|
|
@@ -195,7 +289,7 @@ function pickKeyFiles(runs) {
|
|
|
195
289
|
const score = new Map();
|
|
196
290
|
for (const run of runs) {
|
|
197
291
|
for (const file of run.files || []) {
|
|
198
|
-
if (isAidaRuntimePath(file.path))
|
|
292
|
+
if (isAidaRuntimePath(file.path) || isNoisePath(file.path) || isGeneratedToolingPath(file.path))
|
|
199
293
|
continue;
|
|
200
294
|
const path = normalizeRepoPath(file.path);
|
|
201
295
|
const weight = (file.linesAdded || 0) + (file.linesRemoved || 0) + ((file.changeCount || 1) * 5);
|
|
@@ -217,14 +311,54 @@ function deriveCurrentPhase(runs) {
|
|
|
217
311
|
return 'Completed';
|
|
218
312
|
return 'Not Started';
|
|
219
313
|
}
|
|
220
|
-
function
|
|
314
|
+
function inferModuleDescriptors(requirement, runs) {
|
|
315
|
+
const byKey = new Map();
|
|
316
|
+
const meaningfulRunPaths = runs
|
|
317
|
+
.flatMap((run) => run.files || [])
|
|
318
|
+
.filter((file) => !isAidaRuntimePath(file.path) && !isNoisePath(file.path) && !isGeneratedToolingPath(file.path))
|
|
319
|
+
.map((file) => file.path);
|
|
320
|
+
const put = (descriptor) => {
|
|
321
|
+
const key = normalizeModuleKey(descriptor.key);
|
|
322
|
+
if (!key || key === 'default')
|
|
323
|
+
return;
|
|
324
|
+
const existing = byKey.get(key);
|
|
325
|
+
if (!existing) {
|
|
326
|
+
byKey.set(key, {
|
|
327
|
+
key,
|
|
328
|
+
title: descriptor.title || key,
|
|
329
|
+
description: descriptor.description || undefined,
|
|
330
|
+
});
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
byKey.set(key, {
|
|
334
|
+
key,
|
|
335
|
+
title: existing.title || descriptor.title || key,
|
|
336
|
+
description: existing.description || descriptor.description || undefined,
|
|
337
|
+
});
|
|
338
|
+
};
|
|
221
339
|
if (requirement?.modules?.length) {
|
|
222
|
-
|
|
340
|
+
for (const module of requirement.modules) {
|
|
341
|
+
const candidatePaths = typeof module.file === 'string' && module.file.trim().length > 0
|
|
342
|
+
? [module.file]
|
|
343
|
+
: meaningfulRunPaths;
|
|
344
|
+
put(deriveModuleDescriptor(module.name, candidatePaths, module.description || ''));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const stageNames = uniqueStrings(runs
|
|
349
|
+
.flatMap((run) => run.tasks || [])
|
|
350
|
+
.map((task) => task.stageName)
|
|
351
|
+
.filter((stage) => stage && stage !== 'default'));
|
|
352
|
+
for (const stageName of stageNames) {
|
|
353
|
+
put(deriveModuleDescriptor(stageName, meaningfulRunPaths));
|
|
354
|
+
}
|
|
355
|
+
if (byKey.size === 0) {
|
|
356
|
+
for (const descriptor of inferModuleDescriptorsFromPaths(meaningfulRunPaths)) {
|
|
357
|
+
put(descriptor);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
223
360
|
}
|
|
224
|
-
return
|
|
225
|
-
.flatMap((run) => run.tasks || [])
|
|
226
|
-
.map((task) => task.stageName)
|
|
227
|
-
.filter((stage) => stage && stage !== 'default'));
|
|
361
|
+
return [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
228
362
|
}
|
|
229
363
|
function renderListSection(title, values) {
|
|
230
364
|
if (values.length === 0)
|
|
@@ -274,44 +408,185 @@ export function normalizeModuleKey(value) {
|
|
|
274
408
|
return value
|
|
275
409
|
.trim()
|
|
276
410
|
.toLowerCase()
|
|
411
|
+
.replace(/\s*\/\s*/g, '/')
|
|
412
|
+
.replace(/-\//g, '/')
|
|
413
|
+
.replace(/\/-/g, '/')
|
|
277
414
|
.replace(/[^a-z0-9/_\u4e00-\u9fa5-]+/g, '-')
|
|
278
415
|
.replace(/\/+/g, '/')
|
|
279
416
|
.replace(/-+/g, '-')
|
|
280
417
|
.replace(/^-|-$/g, '');
|
|
281
418
|
}
|
|
419
|
+
function normalizeModuleSegment(value) {
|
|
420
|
+
return normalizeModuleKey(value).replace(/\//g, '-');
|
|
421
|
+
}
|
|
422
|
+
function isGenericPathStem(value) {
|
|
423
|
+
return new Set([
|
|
424
|
+
'index',
|
|
425
|
+
'page',
|
|
426
|
+
'view',
|
|
427
|
+
'component',
|
|
428
|
+
'service',
|
|
429
|
+
'services',
|
|
430
|
+
'store',
|
|
431
|
+
'model',
|
|
432
|
+
'models',
|
|
433
|
+
'type',
|
|
434
|
+
'types',
|
|
435
|
+
'utils',
|
|
436
|
+
'util',
|
|
437
|
+
'helper',
|
|
438
|
+
'helpers',
|
|
439
|
+
'api',
|
|
440
|
+
'hooks',
|
|
441
|
+
'hook',
|
|
442
|
+
]).has(value);
|
|
443
|
+
}
|
|
444
|
+
function isGenericModuleName(value) {
|
|
445
|
+
return new Set([
|
|
446
|
+
'module',
|
|
447
|
+
'modules',
|
|
448
|
+
'feature',
|
|
449
|
+
'features',
|
|
450
|
+
'page',
|
|
451
|
+
'pages',
|
|
452
|
+
'view',
|
|
453
|
+
'views',
|
|
454
|
+
'component',
|
|
455
|
+
'components',
|
|
456
|
+
'api',
|
|
457
|
+
'utils',
|
|
458
|
+
'util',
|
|
459
|
+
'helper',
|
|
460
|
+
'helpers',
|
|
461
|
+
'基础设施',
|
|
462
|
+
'视图层',
|
|
463
|
+
'集成',
|
|
464
|
+
'国际化',
|
|
465
|
+
'公共工具',
|
|
466
|
+
'工具',
|
|
467
|
+
'配置',
|
|
468
|
+
]).has(value);
|
|
469
|
+
}
|
|
470
|
+
function areAllModulesGeneric(values) {
|
|
471
|
+
return values.length > 0 && values.every((value) => isGenericModuleName(normalizeModuleKey(value)));
|
|
472
|
+
}
|
|
473
|
+
function isPlaceholderBranchText(value, branchName) {
|
|
474
|
+
if (!value)
|
|
475
|
+
return true;
|
|
476
|
+
const normalized = value.trim();
|
|
477
|
+
if (!normalized)
|
|
478
|
+
return true;
|
|
479
|
+
return normalized === branchName || normalized === branchName.replace(/-/g, '/');
|
|
480
|
+
}
|
|
481
|
+
function inferModuleDescriptorsFromPaths(paths) {
|
|
482
|
+
const byKey = new Map();
|
|
483
|
+
for (const path of filterMeaningfulPaths(paths)) {
|
|
484
|
+
const key = deriveModuleKeyFromPaths([path]);
|
|
485
|
+
if (!key || byKey.has(key))
|
|
486
|
+
continue;
|
|
487
|
+
byKey.set(key, { key, title: key });
|
|
488
|
+
}
|
|
489
|
+
return [...byKey.values()];
|
|
490
|
+
}
|
|
491
|
+
export function deriveModuleKeyFromPaths(paths) {
|
|
492
|
+
for (const value of paths) {
|
|
493
|
+
const normalized = stripKnownSourcePrefix(value || '');
|
|
494
|
+
if (!normalized)
|
|
495
|
+
continue;
|
|
496
|
+
const segments = normalized
|
|
497
|
+
.split('/')
|
|
498
|
+
.map((segment, index, all) => {
|
|
499
|
+
if (index === all.length - 1)
|
|
500
|
+
return segment.replace(/\.[^.]+$/u, '');
|
|
501
|
+
return segment;
|
|
502
|
+
})
|
|
503
|
+
.map((segment) => normalizeModuleSegment(segment))
|
|
504
|
+
.filter(Boolean);
|
|
505
|
+
while (segments.length > 1 && isGenericPathStem(segments[segments.length - 1])) {
|
|
506
|
+
segments.pop();
|
|
507
|
+
}
|
|
508
|
+
if (segments.length >= 2)
|
|
509
|
+
return `${segments[0]}/${segments[1]}`;
|
|
510
|
+
if (segments.length === 1)
|
|
511
|
+
return segments[0];
|
|
512
|
+
}
|
|
513
|
+
return '';
|
|
514
|
+
}
|
|
515
|
+
function deriveModuleDescriptor(rawName, candidatePaths = [], description = '') {
|
|
516
|
+
const normalizedName = normalizeModuleKey(rawName);
|
|
517
|
+
const pathDerived = deriveModuleKeyFromPaths(candidatePaths);
|
|
518
|
+
const key = normalizedName.includes('/')
|
|
519
|
+
? normalizedName
|
|
520
|
+
: (!normalizedName || isGenericModuleName(normalizedName))
|
|
521
|
+
? (pathDerived || normalizedName)
|
|
522
|
+
: normalizedName;
|
|
523
|
+
return {
|
|
524
|
+
key: key || normalizedName || 'module',
|
|
525
|
+
title: rawName.trim() || key || 'module',
|
|
526
|
+
description: description.trim() || undefined,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function normalizeModuleMemoryRecord(record) {
|
|
530
|
+
return {
|
|
531
|
+
...record,
|
|
532
|
+
moduleKey: normalizeModuleKey(record.moduleKey),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
282
535
|
export function loadMemoryIndex(projectRoot) {
|
|
283
536
|
const path = memoryIndexPath(projectRoot);
|
|
284
537
|
if (!fileExists(path)) {
|
|
285
538
|
return {
|
|
539
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
286
540
|
updatedAt: new Date().toISOString(),
|
|
287
|
-
|
|
541
|
+
items: [],
|
|
288
542
|
};
|
|
289
543
|
}
|
|
290
|
-
|
|
544
|
+
const raw = readJson(path);
|
|
545
|
+
const items = Array.isArray(raw?.items)
|
|
546
|
+
? raw.items
|
|
547
|
+
: Array.isArray(raw?.modules)
|
|
548
|
+
? raw.modules
|
|
549
|
+
: [];
|
|
550
|
+
return {
|
|
551
|
+
schemaVersion: raw?.schemaVersion || MEMORY_SCHEMA_VERSION,
|
|
552
|
+
updatedAt: raw?.updatedAt || new Date().toISOString(),
|
|
553
|
+
items,
|
|
554
|
+
};
|
|
291
555
|
}
|
|
292
556
|
export function saveMemoryIndex(projectRoot, index) {
|
|
293
557
|
ensureDir(memoriesDir(projectRoot));
|
|
294
|
-
writeJson(memoryIndexPath(projectRoot),
|
|
558
|
+
writeJson(memoryIndexPath(projectRoot), {
|
|
559
|
+
schemaVersion: index.schemaVersion || MEMORY_SCHEMA_VERSION,
|
|
560
|
+
updatedAt: index.updatedAt,
|
|
561
|
+
items: index.items,
|
|
562
|
+
});
|
|
295
563
|
}
|
|
296
564
|
export function loadModuleMemory(projectRoot, moduleKey) {
|
|
297
565
|
const normalized = normalizeModuleKey(moduleKey);
|
|
298
566
|
const path = moduleMemoryPath(projectRoot, normalized);
|
|
299
567
|
if (fileExists(path))
|
|
300
568
|
return readJson(path);
|
|
569
|
+
const legacyPath = legacyModuleMemoryPath(projectRoot, normalized);
|
|
570
|
+
if (fileExists(legacyPath))
|
|
571
|
+
return readJson(legacyPath);
|
|
301
572
|
const viewPath = moduleMemoryViewPath(projectRoot, normalized);
|
|
302
|
-
if (
|
|
573
|
+
if (fileExists(viewPath))
|
|
574
|
+
return moduleMemoryRecordFromMarkdown(readText(viewPath));
|
|
575
|
+
const legacyViewPath = legacyModuleMemoryViewPath(projectRoot, normalized);
|
|
576
|
+
if (!fileExists(legacyViewPath))
|
|
303
577
|
return null;
|
|
304
|
-
return moduleMemoryRecordFromMarkdown(readText(
|
|
578
|
+
return moduleMemoryRecordFromMarkdown(readText(legacyViewPath));
|
|
305
579
|
}
|
|
306
580
|
function upsertMemoryIndexEntry(projectRoot, record) {
|
|
307
581
|
const index = loadMemoryIndex(projectRoot);
|
|
308
582
|
const entry = memoryIndexEntryFromRecord(record);
|
|
309
|
-
const next = index.
|
|
583
|
+
const next = index.items.filter((item) => item.key !== record.moduleKey);
|
|
310
584
|
next.push(entry);
|
|
311
585
|
next.sort((a, b) => a.key.localeCompare(b.key));
|
|
312
586
|
saveMemoryIndex(projectRoot, {
|
|
587
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
313
588
|
updatedAt: new Date().toISOString(),
|
|
314
|
-
|
|
589
|
+
items: next,
|
|
315
590
|
});
|
|
316
591
|
}
|
|
317
592
|
function memoryIndexEntryFromRecord(record) {
|
|
@@ -321,9 +596,72 @@ function memoryIndexEntryFromRecord(record) {
|
|
|
321
596
|
summary: record.summary,
|
|
322
597
|
keywords: topItems([record.moduleKey, record.title, ...record.keywords], 12),
|
|
323
598
|
paths: filterMeaningfulPaths([...record.entryFiles, ...record.relatedPaths], 12),
|
|
599
|
+
tickets: topItems([...(record.tickets || [])]
|
|
600
|
+
.map((ticket) => ticket.ticket || ticket.branch || '')
|
|
601
|
+
.filter(Boolean), 8),
|
|
324
602
|
updatedAt: record.updatedAt,
|
|
325
603
|
};
|
|
326
604
|
}
|
|
605
|
+
function mergeTicketReferences(existing, incoming) {
|
|
606
|
+
const byKey = new Map();
|
|
607
|
+
for (const ticket of [...existing, ...incoming]) {
|
|
608
|
+
const summary = ticket.summary.trim();
|
|
609
|
+
if (!summary)
|
|
610
|
+
continue;
|
|
611
|
+
const key = `${ticket.ticket || ''}|${ticket.branch || ''}`;
|
|
612
|
+
const current = byKey.get(key);
|
|
613
|
+
if (!current) {
|
|
614
|
+
byKey.set(key, {
|
|
615
|
+
ticket: ticket.ticket,
|
|
616
|
+
branch: ticket.branch,
|
|
617
|
+
summary,
|
|
618
|
+
updatedAt: ticket.updatedAt,
|
|
619
|
+
});
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
const useIncoming = latestIso(current.updatedAt, ticket.updatedAt) === ticket.updatedAt;
|
|
623
|
+
byKey.set(key, {
|
|
624
|
+
ticket: current.ticket || ticket.ticket,
|
|
625
|
+
branch: current.branch || ticket.branch,
|
|
626
|
+
summary: useIncoming ? summary : current.summary,
|
|
627
|
+
updatedAt: latestIso(current.updatedAt, ticket.updatedAt),
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return [...byKey.values()].sort((a, b) => `${b.updatedAt || ''}`.localeCompare(a.updatedAt || '') || `${a.ticket || a.branch || ''}`.localeCompare(`${b.ticket || b.branch || ''}`));
|
|
631
|
+
}
|
|
632
|
+
function mergeChangeEntries(existing, incoming) {
|
|
633
|
+
const byKey = new Map();
|
|
634
|
+
for (const change of [...existing, ...incoming]) {
|
|
635
|
+
const summary = change.summary.trim();
|
|
636
|
+
if (!summary)
|
|
637
|
+
continue;
|
|
638
|
+
const key = change.ticket || change.branch
|
|
639
|
+
? `${change.ticket || ''}|${change.branch || ''}`
|
|
640
|
+
: `${change.title || ''}|${summary}`;
|
|
641
|
+
const current = byKey.get(key);
|
|
642
|
+
if (!current) {
|
|
643
|
+
byKey.set(key, {
|
|
644
|
+
ticket: change.ticket,
|
|
645
|
+
branch: change.branch,
|
|
646
|
+
title: change.title,
|
|
647
|
+
summary,
|
|
648
|
+
updatedAt: change.updatedAt,
|
|
649
|
+
});
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const useIncoming = latestIso(current.updatedAt, change.updatedAt) === change.updatedAt;
|
|
653
|
+
byKey.set(key, {
|
|
654
|
+
ticket: current.ticket || change.ticket,
|
|
655
|
+
branch: current.branch || change.branch,
|
|
656
|
+
title: useIncoming ? (change.title || current.title) : (current.title || change.title),
|
|
657
|
+
summary: useIncoming ? summary : current.summary,
|
|
658
|
+
updatedAt: latestIso(current.updatedAt, change.updatedAt),
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return [...byKey.values()]
|
|
662
|
+
.sort((a, b) => `${b.updatedAt}`.localeCompare(`${a.updatedAt}`))
|
|
663
|
+
.slice(0, 20);
|
|
664
|
+
}
|
|
327
665
|
function mergeMemoryIndexEntry(existing, next) {
|
|
328
666
|
if (!existing)
|
|
329
667
|
return next;
|
|
@@ -333,6 +671,7 @@ function mergeMemoryIndexEntry(existing, next) {
|
|
|
333
671
|
summary: next.summary || existing.summary,
|
|
334
672
|
keywords: topItems([...(existing.keywords || []), ...(next.keywords || [])], 12),
|
|
335
673
|
paths: filterMeaningfulPaths([...(existing.paths || []), ...(next.paths || [])], 12),
|
|
674
|
+
tickets: topItems([...(existing.tickets || []), ...(next.tickets || [])], 8),
|
|
336
675
|
updatedAt: next.updatedAt || existing.updatedAt,
|
|
337
676
|
};
|
|
338
677
|
}
|
|
@@ -346,6 +685,7 @@ function moduleMemoryRecordFromMarkdown(raw) {
|
|
|
346
685
|
const sections = extractMarkdownSections(raw);
|
|
347
686
|
const summary = sections.get('summary') || '';
|
|
348
687
|
return {
|
|
688
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
349
689
|
moduleKey,
|
|
350
690
|
title: raw.match(/^- Title:\s+(.+)$/m)?.[1]?.trim() || moduleKey,
|
|
351
691
|
summary,
|
|
@@ -358,32 +698,54 @@ function moduleMemoryRecordFromMarkdown(raw) {
|
|
|
358
698
|
pitfalls: parseListSection(raw, 'pitfalls'),
|
|
359
699
|
relatedRules: parseListSection(raw, 'related rules'),
|
|
360
700
|
tickets: [],
|
|
701
|
+
changes: [],
|
|
361
702
|
updatedAt: raw.match(/^- Updated At:\s+(.+)$/m)?.[1]?.trim() || new Date().toISOString(),
|
|
362
703
|
};
|
|
363
704
|
}
|
|
364
705
|
export function rebuildMemoryIndexFromDisk(projectRoot) {
|
|
365
706
|
ensureDir(moduleMemoriesDir(projectRoot));
|
|
707
|
+
migrateLegacyNestedModuleMemoryLayout(projectRoot);
|
|
366
708
|
const byKey = new Map();
|
|
367
|
-
|
|
368
|
-
byKey.set(entry.key, entry);
|
|
369
|
-
}
|
|
709
|
+
const expectedMarkdownViews = new Set();
|
|
370
710
|
for (const file of walkJsonFiles(moduleMemoriesDir(projectRoot))) {
|
|
371
711
|
if (basename(file) === 'index.json')
|
|
372
712
|
continue;
|
|
373
|
-
const
|
|
713
|
+
const original = readJson(file);
|
|
714
|
+
const record = normalizeModuleMemoryRecord(original);
|
|
715
|
+
if (record.moduleKey !== original.moduleKey) {
|
|
716
|
+
saveModuleMemory(projectRoot, record);
|
|
717
|
+
rmSync(file, { force: true });
|
|
718
|
+
}
|
|
374
719
|
const entry = memoryIndexEntryFromRecord(record);
|
|
375
720
|
byKey.set(record.moduleKey, mergeMemoryIndexEntry(byKey.get(record.moduleKey), entry));
|
|
721
|
+
expectedMarkdownViews.add(moduleMemoryViewPath(projectRoot, record.moduleKey));
|
|
376
722
|
}
|
|
377
723
|
for (const file of walkMarkdownFiles(moduleMemoriesDir(projectRoot))) {
|
|
378
724
|
const record = moduleMemoryRecordFromMarkdown(readText(file));
|
|
379
725
|
if (!record)
|
|
380
726
|
continue;
|
|
381
|
-
const
|
|
382
|
-
|
|
727
|
+
const normalized = normalizeModuleMemoryRecord(record);
|
|
728
|
+
if (normalized.moduleKey !== record.moduleKey) {
|
|
729
|
+
const targetViewPath = moduleMemoryViewPath(projectRoot, normalized.moduleKey);
|
|
730
|
+
ensureDir(dirname(targetViewPath));
|
|
731
|
+
if (!fileExists(targetViewPath)) {
|
|
732
|
+
writeText(targetViewPath, renderModuleMemoryMarkdown(normalized));
|
|
733
|
+
}
|
|
734
|
+
rmSync(file, { force: true });
|
|
735
|
+
}
|
|
736
|
+
const entry = memoryIndexEntryFromRecord(normalized);
|
|
737
|
+
byKey.set(normalized.moduleKey, mergeMemoryIndexEntry(byKey.get(normalized.moduleKey), entry));
|
|
738
|
+
expectedMarkdownViews.add(moduleMemoryViewPath(projectRoot, normalized.moduleKey));
|
|
739
|
+
}
|
|
740
|
+
for (const file of walkMarkdownFiles(moduleMemoriesDir(projectRoot))) {
|
|
741
|
+
if (!expectedMarkdownViews.has(file)) {
|
|
742
|
+
rmSync(file, { force: true });
|
|
743
|
+
}
|
|
383
744
|
}
|
|
384
745
|
const index = {
|
|
746
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
385
747
|
updatedAt: new Date().toISOString(),
|
|
386
|
-
|
|
748
|
+
items: [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key)),
|
|
387
749
|
};
|
|
388
750
|
saveMemoryIndex(projectRoot, index);
|
|
389
751
|
return index;
|
|
@@ -391,7 +753,10 @@ export function rebuildMemoryIndexFromDisk(projectRoot) {
|
|
|
391
753
|
export function saveModuleMemory(projectRoot, record) {
|
|
392
754
|
const path = moduleMemoryPath(projectRoot, record.moduleKey);
|
|
393
755
|
ensureDir(dirname(path));
|
|
394
|
-
writeJson(path,
|
|
756
|
+
writeJson(path, {
|
|
757
|
+
schemaVersion: record.schemaVersion || MEMORY_SCHEMA_VERSION,
|
|
758
|
+
...record,
|
|
759
|
+
});
|
|
395
760
|
upsertMemoryIndexEntry(projectRoot, record);
|
|
396
761
|
}
|
|
397
762
|
export function loadRunContext(projectRoot, branchName) {
|
|
@@ -432,6 +797,12 @@ export function renderModuleMemoryMarkdown(record) {
|
|
|
432
797
|
'',
|
|
433
798
|
renderListSection('Related Rules', record.relatedRules).trimEnd(),
|
|
434
799
|
'',
|
|
800
|
+
'## Changes',
|
|
801
|
+
'',
|
|
802
|
+
...((record.changes || []).length > 0
|
|
803
|
+
? (record.changes || []).map((change) => `- ${[change.ticket || change.branch, change.title, change.summary].filter(Boolean).join(' | ')}`)
|
|
804
|
+
: ['- None']),
|
|
805
|
+
'',
|
|
435
806
|
'## Related Tickets',
|
|
436
807
|
'',
|
|
437
808
|
...(record.tickets.length > 0
|
|
@@ -492,50 +863,34 @@ export function renderRunMemoryPackMarkdown(context, modules) {
|
|
|
492
863
|
}
|
|
493
864
|
export function buildMemoryViews(projectRoot) {
|
|
494
865
|
ensureDir(moduleMemoriesDir(projectRoot));
|
|
866
|
+
migrateLegacyNestedModuleMemoryLayout(projectRoot);
|
|
495
867
|
let moduleViews = 0;
|
|
496
868
|
for (const file of walkJsonFiles(moduleMemoriesDir(projectRoot))) {
|
|
497
869
|
if (basename(file) === 'index.json')
|
|
498
870
|
continue;
|
|
499
|
-
const
|
|
871
|
+
const original = readJson(file);
|
|
872
|
+
const record = normalizeModuleMemoryRecord(original);
|
|
873
|
+
if (record.moduleKey !== original.moduleKey) {
|
|
874
|
+
saveModuleMemory(projectRoot, record);
|
|
875
|
+
rmSync(file, { force: true });
|
|
876
|
+
}
|
|
500
877
|
const viewPath = moduleMemoryViewPath(projectRoot, record.moduleKey);
|
|
501
878
|
ensureDir(dirname(viewPath));
|
|
502
879
|
writeText(viewPath, renderModuleMemoryMarkdown(record));
|
|
503
880
|
moduleViews++;
|
|
504
881
|
}
|
|
505
|
-
let contextViews = 0;
|
|
506
|
-
let packViews = 0;
|
|
507
|
-
const root = runsDir(projectRoot);
|
|
508
|
-
if (!fileExists(root)) {
|
|
509
|
-
rebuildMemoryIndexFromDisk(projectRoot);
|
|
510
|
-
return { moduleViews, contextViews, packViews };
|
|
511
|
-
}
|
|
512
|
-
for (const branchName of readdirSync(root)) {
|
|
513
|
-
const safeBranchDir = resolve(root, branchName);
|
|
514
|
-
if (!fileExists(safeBranchDir) || !statSync(safeBranchDir).isDirectory())
|
|
515
|
-
continue;
|
|
516
|
-
const contextPath = resolve(safeBranchDir, 'context.json');
|
|
517
|
-
if (!fileExists(contextPath))
|
|
518
|
-
continue;
|
|
519
|
-
const record = readJson(contextPath);
|
|
520
|
-
writeText(runContextViewPath(projectRoot, record.branch), renderRunContextMarkdown(record));
|
|
521
|
-
contextViews++;
|
|
522
|
-
const modules = record.modules
|
|
523
|
-
.map((moduleName) => loadModuleMemory(projectRoot, moduleName))
|
|
524
|
-
.filter((item) => item !== null);
|
|
525
|
-
writeText(runMemoryPackViewPath(projectRoot, record.branch), renderRunMemoryPackMarkdown(record, modules));
|
|
526
|
-
packViews++;
|
|
527
|
-
}
|
|
528
882
|
rebuildMemoryIndexFromDisk(projectRoot);
|
|
529
|
-
return { moduleViews, contextViews, packViews };
|
|
883
|
+
return { moduleViews, contextViews: 0, packViews: 0 };
|
|
530
884
|
}
|
|
531
885
|
export function buildRunContextFromBranch(projectRoot, branchName) {
|
|
886
|
+
const existing = loadRunContext(projectRoot, branchName);
|
|
532
887
|
const requirement = loadRequirement(projectRoot, branchName);
|
|
533
888
|
const analysis = loadAnalysis(projectRoot, branchName);
|
|
534
889
|
const runs = loadBranchRuns(projectRoot, branchName);
|
|
535
890
|
if (!requirement && !analysis && runs.length === 0) {
|
|
536
891
|
return null;
|
|
537
892
|
}
|
|
538
|
-
const
|
|
893
|
+
const moduleDescriptors = inferModuleDescriptors(requirement, runs);
|
|
539
894
|
const completed = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'done').map((task) => task.title));
|
|
540
895
|
const inProgress = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'in-progress').map((task) => task.title));
|
|
541
896
|
const next = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'pending').map((task) => task.title));
|
|
@@ -547,25 +902,44 @@ export function buildRunContextFromBranch(projectRoot, branchName) {
|
|
|
547
902
|
...runs.flatMap((run) => run.deviations || []).map((deviation) => deviation.title),
|
|
548
903
|
];
|
|
549
904
|
const title = requirement?.title || extractTicket(branchName) || branchName;
|
|
905
|
+
const derivedPhase = deriveCurrentPhase(runs);
|
|
906
|
+
const derivedModules = moduleDescriptors.map((descriptor) => descriptor.key);
|
|
907
|
+
const derivedKeyFiles = pickKeyFiles(runs);
|
|
908
|
+
const derivedRisks = topItems([...analysisRisks, ...runRisks]);
|
|
909
|
+
const existingModules = existing?.modules || [];
|
|
910
|
+
const fallbackPathModules = inferModuleDescriptorsFromPaths([
|
|
911
|
+
...derivedKeyFiles,
|
|
912
|
+
...(existing?.keyFiles || []),
|
|
913
|
+
]).map((descriptor) => descriptor.key);
|
|
914
|
+
const nextModules = derivedModules.length > 0
|
|
915
|
+
? derivedModules
|
|
916
|
+
: areAllModulesGeneric(existingModules) && fallbackPathModules.length > 0
|
|
917
|
+
? fallbackPathModules
|
|
918
|
+
: existingModules;
|
|
919
|
+
const nextTitle = isPlaceholderBranchText(existing?.title, branchName) ? title : (existing?.title || title);
|
|
920
|
+
const nextSummary = requirement?.summary
|
|
921
|
+
|| extractSummaryFromAnalysis(analysis)
|
|
922
|
+
|| (!isPlaceholderBranchText(existing?.summary, branchName) ? existing.summary : '')
|
|
923
|
+
|| (nextModules.length > 0 ? `涉及模块: ${nextModules.join(', ')}` : title);
|
|
550
924
|
return {
|
|
551
925
|
branch: branchName,
|
|
552
926
|
ticket: extractTicket(requirement?.title || '') || extractTicket(branchName),
|
|
553
|
-
title,
|
|
554
|
-
summary:
|
|
555
|
-
currentPhase:
|
|
556
|
-
modules,
|
|
557
|
-
completed,
|
|
558
|
-
inProgress,
|
|
559
|
-
next,
|
|
560
|
-
decisions,
|
|
561
|
-
constraints,
|
|
562
|
-
keyFiles:
|
|
563
|
-
risks:
|
|
927
|
+
title: nextTitle,
|
|
928
|
+
summary: nextSummary,
|
|
929
|
+
currentPhase: derivedPhase === 'Not Started' && existing?.currentPhase ? existing.currentPhase : derivedPhase,
|
|
930
|
+
modules: nextModules,
|
|
931
|
+
completed: completed.length > 0 ? completed : (existing?.completed || []),
|
|
932
|
+
inProgress: inProgress.length > 0 ? inProgress : (existing?.inProgress || []),
|
|
933
|
+
next: next.length > 0 ? next : (existing?.next || []),
|
|
934
|
+
decisions: decisions.length > 0 ? decisions : (existing?.decisions || []),
|
|
935
|
+
constraints: constraints.length > 0 ? constraints : (existing?.constraints || []),
|
|
936
|
+
keyFiles: derivedKeyFiles.length > 0 ? derivedKeyFiles : (existing?.keyFiles || []),
|
|
937
|
+
risks: derivedRisks.length > 0 ? derivedRisks : (existing?.risks || []),
|
|
564
938
|
updatedAt: new Date().toISOString(),
|
|
565
939
|
};
|
|
566
940
|
}
|
|
567
|
-
function collectRelatedPaths(
|
|
568
|
-
const query =
|
|
941
|
+
function collectRelatedPaths(moduleDescriptor, branchContext, runs) {
|
|
942
|
+
const query = moduleDescriptor.key.toLowerCase();
|
|
569
943
|
const tokens = splitQueryTokens(query);
|
|
570
944
|
const scored = new Map();
|
|
571
945
|
for (const path of branchContext.keyFiles) {
|
|
@@ -575,10 +949,11 @@ function collectRelatedPaths(moduleName, branchContext, runs) {
|
|
|
575
949
|
}
|
|
576
950
|
for (const run of runs) {
|
|
577
951
|
for (const task of run.tasks || []) {
|
|
578
|
-
|
|
952
|
+
const taskDescriptor = deriveModuleDescriptor(task.stageName || '', (run.files || []).map((file) => file.path));
|
|
953
|
+
if (taskDescriptor.key !== moduleDescriptor.key)
|
|
579
954
|
continue;
|
|
580
955
|
for (const file of run.files || []) {
|
|
581
|
-
if (isAidaRuntimePath(file.path))
|
|
956
|
+
if (isAidaRuntimePath(file.path) || isNoisePath(file.path) || isGeneratedToolingPath(file.path))
|
|
582
957
|
continue;
|
|
583
958
|
const path = normalizeRepoPath(file.path);
|
|
584
959
|
const bonus = (file.linesAdded || 0) + (file.linesRemoved || 0) + 10;
|
|
@@ -592,24 +967,31 @@ function collectRelatedPaths(moduleName, branchContext, runs) {
|
|
|
592
967
|
export function upsertModuleMemory(projectRoot, input) {
|
|
593
968
|
const moduleKey = normalizeModuleKey(input.moduleKey);
|
|
594
969
|
const existing = loadModuleMemory(projectRoot, moduleKey);
|
|
970
|
+
const existingEntryFiles = filterMeaningfulPaths(existing?.entryFiles || [], 8);
|
|
971
|
+
const existingRelatedPaths = filterMeaningfulPaths(existing?.relatedPaths || [], 12);
|
|
972
|
+
const nextChanges = ((input.tickets || []).filter((ticket) => ticket.summary.trim().length > 0))
|
|
973
|
+
.map((ticket) => ({
|
|
974
|
+
ticket: ticket.ticket,
|
|
975
|
+
branch: ticket.branch,
|
|
976
|
+
title: input.changeTitle || ticket.ticket || ticket.branch || input.title || existing?.title || moduleKey,
|
|
977
|
+
summary: ticket.summary,
|
|
978
|
+
updatedAt: ticket.updatedAt || new Date().toISOString(),
|
|
979
|
+
}));
|
|
595
980
|
const record = {
|
|
981
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
596
982
|
moduleKey,
|
|
597
983
|
title: input.title || existing?.title || moduleKey,
|
|
598
984
|
summary: input.summary || existing?.summary || '',
|
|
599
985
|
keywords: topItems([...(existing?.keywords || []), ...(input.keywords || []), moduleKey, input.title || ''], 16),
|
|
600
|
-
entryFiles: filterMeaningfulPaths([...
|
|
601
|
-
relatedPaths: filterMeaningfulPaths([...
|
|
986
|
+
entryFiles: filterMeaningfulPaths([...existingEntryFiles, ...(input.entryFiles || [])], 8),
|
|
987
|
+
relatedPaths: filterMeaningfulPaths([...existingRelatedPaths, ...(input.relatedPaths || [])], 12),
|
|
602
988
|
dataFlow: topItems([...(existing?.dataFlow || []), ...(input.dataFlow || [])]),
|
|
603
989
|
decisions: topItems([...(existing?.decisions || []), ...(input.decisions || [])]),
|
|
604
990
|
constraints: topItems([...(existing?.constraints || []), ...(input.constraints || [])]),
|
|
605
991
|
pitfalls: topItems([...(existing?.pitfalls || []), ...(input.pitfalls || [])]),
|
|
606
992
|
relatedRules: topItems([...(existing?.relatedRules || []), ...(input.relatedRules || [])], 12),
|
|
607
|
-
tickets: [
|
|
608
|
-
|
|
609
|
-
...((input.tickets || []).filter((ticket) => ticket.summary.trim().length > 0)),
|
|
610
|
-
].filter((ticket, index, array) => array.findIndex((item) => item.ticket === ticket.ticket
|
|
611
|
-
&& item.branch === ticket.branch
|
|
612
|
-
&& item.summary === ticket.summary) === index),
|
|
993
|
+
tickets: mergeTicketReferences(existing?.tickets || [], (input.tickets || []).filter((ticket) => ticket.summary.trim().length > 0)),
|
|
994
|
+
changes: mergeChangeEntries(existing?.changes || [], nextChanges),
|
|
613
995
|
updatedAt: new Date().toISOString(),
|
|
614
996
|
};
|
|
615
997
|
saveModuleMemory(projectRoot, record);
|
|
@@ -643,10 +1025,10 @@ export function searchModuleMemories(projectRoot, query, pathHints = []) {
|
|
|
643
1025
|
const tokens = splitQueryTokens(normalizedQuery);
|
|
644
1026
|
const hints = pathHints.map((hint) => hint.toLowerCase());
|
|
645
1027
|
let index = loadMemoryIndex(projectRoot);
|
|
646
|
-
if (index.
|
|
1028
|
+
if (index.items.length === 0 && fileExists(moduleMemoriesDir(projectRoot))) {
|
|
647
1029
|
index = rebuildMemoryIndexFromDisk(projectRoot);
|
|
648
1030
|
}
|
|
649
|
-
return index.
|
|
1031
|
+
return index.items
|
|
650
1032
|
.map((entry) => {
|
|
651
1033
|
let score = 0;
|
|
652
1034
|
score += scoreText(normalizedQuery, tokens, entry.key) * 2;
|
|
@@ -686,7 +1068,19 @@ export function migrateLegacyMemories(projectRoot) {
|
|
|
686
1068
|
const requirementData = requirement
|
|
687
1069
|
? readJson(resolve(branchPath, 'requirement.json'))
|
|
688
1070
|
: null;
|
|
689
|
-
const
|
|
1071
|
+
const branchRunsFromFiles = runFiles
|
|
1072
|
+
.map((path) => {
|
|
1073
|
+
try {
|
|
1074
|
+
return readJson(path);
|
|
1075
|
+
}
|
|
1076
|
+
catch {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
})
|
|
1080
|
+
.filter((item) => item !== null);
|
|
1081
|
+
const branchName = requirementData?.branch
|
|
1082
|
+
|| branchRunsFromFiles.find((run) => run.meta?.branch)?.meta?.branch
|
|
1083
|
+
|| safeBranch;
|
|
690
1084
|
const context = buildRunContextFromBranch(projectRoot, branchName);
|
|
691
1085
|
if (!context)
|
|
692
1086
|
continue;
|
|
@@ -695,24 +1089,22 @@ export function migrateLegacyMemories(projectRoot) {
|
|
|
695
1089
|
contextsWritten++;
|
|
696
1090
|
const runs = loadBranchRuns(projectRoot, branchName);
|
|
697
1091
|
const req = loadRequirement(projectRoot, branchName);
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
for (const candidate of moduleCandidates) {
|
|
702
|
-
if (!candidate.name.trim())
|
|
1092
|
+
const moduleDescriptors = inferModuleDescriptors(req, runs);
|
|
1093
|
+
for (const descriptor of moduleDescriptors) {
|
|
1094
|
+
if (!descriptor.key.trim())
|
|
703
1095
|
continue;
|
|
704
|
-
const
|
|
705
|
-
const relatedPaths = collectRelatedPaths(candidate.name, context, runs);
|
|
1096
|
+
const relatedPaths = collectRelatedPaths(descriptor, context, runs);
|
|
706
1097
|
upsertModuleMemory(projectRoot, {
|
|
707
|
-
moduleKey,
|
|
708
|
-
title:
|
|
709
|
-
summary:
|
|
710
|
-
keywords: [
|
|
1098
|
+
moduleKey: descriptor.key,
|
|
1099
|
+
title: descriptor.title,
|
|
1100
|
+
summary: descriptor.description || context.summary,
|
|
1101
|
+
keywords: [descriptor.title, descriptor.key, context.title],
|
|
711
1102
|
entryFiles: relatedPaths.slice(0, 5),
|
|
712
1103
|
relatedPaths,
|
|
713
1104
|
decisions: context.decisions,
|
|
714
1105
|
constraints: context.constraints,
|
|
715
1106
|
pitfalls: context.risks,
|
|
1107
|
+
changeTitle: context.title,
|
|
716
1108
|
tickets: [{
|
|
717
1109
|
ticket: context.ticket,
|
|
718
1110
|
branch: branchName,
|
|
@@ -721,7 +1113,7 @@ export function migrateLegacyMemories(projectRoot) {
|
|
|
721
1113
|
}],
|
|
722
1114
|
});
|
|
723
1115
|
moduleMemoriesWritten++;
|
|
724
|
-
modulesTouched.add(
|
|
1116
|
+
modulesTouched.add(descriptor.key);
|
|
725
1117
|
}
|
|
726
1118
|
}
|
|
727
1119
|
buildMemoryViews(projectRoot);
|
|
@@ -742,18 +1134,19 @@ export function rebuildCurrentBranchMemory(projectRoot, branchName) {
|
|
|
742
1134
|
const req = loadRequirement(projectRoot, branchName);
|
|
743
1135
|
const runs = loadBranchRuns(projectRoot, branchName);
|
|
744
1136
|
const modules = [];
|
|
745
|
-
for (const
|
|
746
|
-
const relatedPaths = collectRelatedPaths(
|
|
1137
|
+
for (const descriptor of inferModuleDescriptors(req, runs)) {
|
|
1138
|
+
const relatedPaths = collectRelatedPaths(descriptor, context, runs);
|
|
747
1139
|
modules.push(upsertModuleMemory(projectRoot, {
|
|
748
|
-
moduleKey:
|
|
749
|
-
title:
|
|
750
|
-
summary:
|
|
751
|
-
keywords: [
|
|
1140
|
+
moduleKey: descriptor.key,
|
|
1141
|
+
title: descriptor.title,
|
|
1142
|
+
summary: descriptor.description || context.summary,
|
|
1143
|
+
keywords: [descriptor.title, descriptor.key, context.title],
|
|
752
1144
|
entryFiles: relatedPaths.slice(0, 5),
|
|
753
1145
|
relatedPaths,
|
|
754
1146
|
decisions: context.decisions,
|
|
755
1147
|
constraints: context.constraints,
|
|
756
1148
|
pitfalls: context.risks,
|
|
1149
|
+
changeTitle: context.title,
|
|
757
1150
|
tickets: [{
|
|
758
1151
|
ticket: context.ticket,
|
|
759
1152
|
branch: branchName,
|