ai-dev-analytics 1.1.12 → 2.0.1
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 +51 -57
- package/README.md +68 -485
- 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 +125 -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 +19 -176
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/memory.d.ts.map +1 -1
- package/dist/cli/commands/memory.js +3 -13
- package/dist/cli/commands/memory.js.map +1 -1
- package/dist/cli/commands/rules.d.ts +0 -10
- package/dist/cli/commands/rules.d.ts.map +1 -1
- package/dist/cli/commands/rules.js +9 -34
- package/dist/cli/commands/rules.js.map +1 -1
- package/dist/cli/commands/skills.d.ts +0 -5
- package/dist/cli/commands/skills.d.ts.map +1 -1
- package/dist/cli/commands/skills.js +5 -24
- 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 +28 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.js +26 -85
- package/dist/cli/index.js.map +1 -1
- package/dist/{schemas/run-json.d.ts → internal/runtime/schema.d.ts} +4 -18
- package/dist/internal/runtime/schema.d.ts.map +1 -0
- package/dist/{schemas/run-json.js → internal/runtime/schema.js} +5 -9
- package/dist/internal/runtime/schema.js.map +1 -0
- package/dist/{utils/run-data.d.ts → internal/runtime/state.d.ts} +7 -8
- package/dist/internal/runtime/state.d.ts.map +1 -0
- package/dist/{utils/run-data.js → internal/runtime/state.js} +11 -12
- package/dist/internal/runtime/state.js.map +1 -0
- package/dist/internal/runtime/summary.d.ts +8 -0
- package/dist/internal/runtime/summary.d.ts.map +1 -0
- package/dist/internal/runtime/summary.js +260 -0
- package/dist/internal/runtime/summary.js.map +1 -0
- package/dist/internal/runtime/tokens.d.ts.map +1 -0
- package/dist/internal/runtime/tokens.js.map +1 -0
- package/dist/mcp/server.js +9 -8
- package/dist/mcp/server.js.map +1 -1
- package/dist/schemas/aida-project.d.ts +27 -1
- package/dist/schemas/aida-project.d.ts.map +1 -1
- package/dist/schemas/rules.d.ts +15 -0
- package/dist/schemas/rules.d.ts.map +1 -0
- package/dist/schemas/rules.js +5 -0
- package/dist/schemas/rules.js.map +1 -0
- package/dist/services/project-build.d.ts +44 -0
- package/dist/services/project-build.d.ts.map +1 -0
- package/dist/services/project-build.js +32 -0
- package/dist/services/project-build.js.map +1 -0
- package/dist/services/project-health.d.ts +14 -0
- package/dist/services/project-health.d.ts.map +1 -0
- package/dist/services/project-health.js +19 -0
- package/dist/services/project-health.js.map +1 -0
- package/dist/services/security-audit.d.ts +59 -0
- package/dist/services/security-audit.d.ts.map +1 -0
- package/dist/services/security-audit.js +638 -0
- package/dist/services/security-audit.js.map +1 -0
- package/dist/utils/ai-build.d.ts +3 -8
- package/dist/utils/ai-build.d.ts.map +1 -1
- package/dist/utils/ai-build.js +31 -117
- package/dist/utils/ai-build.js.map +1 -1
- package/dist/utils/guide.d.ts +3 -3
- package/dist/utils/guide.d.ts.map +1 -1
- package/dist/utils/guide.js +91 -68
- package/dist/utils/guide.js.map +1 -1
- package/dist/utils/import.d.ts.map +1 -1
- package/dist/utils/import.js +4 -15
- package/dist/utils/import.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 +440 -95
- package/dist/utils/memory.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -10
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +5 -17
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/project-health.d.ts +39 -0
- package/dist/utils/project-health.d.ts.map +1 -0
- package/dist/utils/project-health.js +286 -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 +11 -1
- package/dist/utils/rules.d.ts.map +1 -1
- package/dist/utils/rules.js +76 -8
- package/dist/utils/rules.js.map +1 -1
- package/dist/utils/skills.d.ts +6 -9
- package/dist/utils/skills.d.ts.map +1 -1
- package/dist/utils/skills.js +26 -54
- package/dist/utils/skills.js.map +1 -1
- package/package.json +12 -14
- package/dist/cli/commands/build.d.ts +0 -2
- package/dist/cli/commands/build.d.ts.map +0 -1
- package/dist/cli/commands/build.js +0 -56
- package/dist/cli/commands/build.js.map +0 -1
- package/dist/cli/commands/dashboard.d.ts +0 -2
- package/dist/cli/commands/dashboard.d.ts.map +0 -1
- package/dist/cli/commands/dashboard.js +0 -70
- package/dist/cli/commands/dashboard.js.map +0 -1
- package/dist/cli/commands/import.d.ts +0 -2
- package/dist/cli/commands/import.d.ts.map +0 -1
- package/dist/cli/commands/import.js +0 -71
- package/dist/cli/commands/import.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -2
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -440
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge-data.d.ts +0 -20
- package/dist/cli/commands/merge-data.d.ts.map +0 -1
- package/dist/cli/commands/merge-data.js +0 -574
- package/dist/cli/commands/merge-data.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -2
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -82
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/migrate-dir.d.ts +0 -6
- package/dist/cli/commands/migrate-dir.d.ts.map +0 -1
- package/dist/cli/commands/migrate-dir.js +0 -125
- package/dist/cli/commands/migrate-dir.js.map +0 -1
- package/dist/cli/commands/migrate-legacy.d.ts +0 -2
- package/dist/cli/commands/migrate-legacy.d.ts.map +0 -1
- package/dist/cli/commands/migrate-legacy.js +0 -143
- package/dist/cli/commands/migrate-legacy.js.map +0 -1
- package/dist/cli/commands/migrate.d.ts +0 -2
- package/dist/cli/commands/migrate.d.ts.map +0 -1
- package/dist/cli/commands/migrate.js +0 -300
- package/dist/cli/commands/migrate.js.map +0 -1
- package/dist/cli/commands/reindex.d.ts +0 -14
- package/dist/cli/commands/reindex.d.ts.map +0 -1
- package/dist/cli/commands/reindex.js +0 -139
- package/dist/cli/commands/reindex.js.map +0 -1
- package/dist/cli/commands/report.d.ts +0 -2
- package/dist/cli/commands/report.d.ts.map +0 -1
- package/dist/cli/commands/report.js +0 -219
- package/dist/cli/commands/report.js.map +0 -1
- package/dist/cli/commands/start.d.ts +0 -2
- package/dist/cli/commands/start.d.ts.map +0 -1
- package/dist/cli/commands/start.js +0 -155
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -2
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -70
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -2
- package/dist/cli/commands/update.d.ts.map +0 -1
- package/dist/cli/commands/update.js +0 -73
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/schemas/run-json.d.ts.map +0 -1
- package/dist/schemas/run-json.js.map +0 -1
- package/dist/server/api.d.ts +0 -30
- package/dist/server/api.d.ts.map +0 -1
- package/dist/server/api.js +0 -239
- package/dist/server/api.js.map +0 -1
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -228
- package/dist/server/index.js.map +0 -1
- package/dist/utils/run-data.d.ts.map +0 -1
- package/dist/utils/run-data.js.map +0 -1
- package/dist/utils/tokens.d.ts.map +0 -1
- package/dist/utils/tokens.js.map +0 -1
- package/src/assets/skills/audit.md +0 -98
- package/src/assets/skills/bug-fixer.md +0 -43
- package/src/assets/skills/code-generator.md +0 -71
- package/src/assets/skills/commit-code.md +0 -67
- package/src/assets/skills/dashboard-generator.md +0 -65
- package/src/assets/skills/dev-flower.md +0 -85
- package/src/assets/skills/deviation-recorder.md +0 -83
- package/src/assets/skills/docx-to-markdown.md +0 -69
- package/src/assets/skills/mcp-reviewer.md +0 -38
- package/src/assets/skills/requirement-analyzer.md +0 -103
- package/src/assets/skills/rules-evolver.md +0 -47
- package/src/assets/skills/self-reviewer.md +0 -49
- package/src/assets/skills/task-splitter.md +0 -60
- package/src/assets/skills/workflow-orchestrator.md +0 -209
- package/src/assets/templates/demo-run.json +0 -910
- package/src/assets/templates/run.json +0 -63
- package/src/dashboard/assets/index-B8QcPcg7.css +0 -1
- package/src/dashboard/assets/index-DcAl6lhS.js +0 -111
- package/src/dashboard/demo/overview.json +0 -71
- package/src/dashboard/demo/run.en.json +0 -1169
- package/src/dashboard/demo/run.json +0 -2667
- package/src/dashboard/demo/run.zh.json +0 -1169
- package/src/dashboard/demo/runs.json +0 -19
- package/src/dashboard/index.html +0 -13
- /package/dist/{utils → internal/runtime}/tokens.d.ts +0 -0
- /package/dist/{utils → internal/runtime}/tokens.js +0 -0
package/dist/utils/memory.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
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, legacyModuleMemoryPath, legacyModuleMemoryViewPath, 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) {
|
|
@@ -235,7 +289,7 @@ function pickKeyFiles(runs) {
|
|
|
235
289
|
const score = new Map();
|
|
236
290
|
for (const run of runs) {
|
|
237
291
|
for (const file of run.files || []) {
|
|
238
|
-
if (isAidaRuntimePath(file.path))
|
|
292
|
+
if (isAidaRuntimePath(file.path) || isNoisePath(file.path) || isGeneratedToolingPath(file.path))
|
|
239
293
|
continue;
|
|
240
294
|
const path = normalizeRepoPath(file.path);
|
|
241
295
|
const weight = (file.linesAdded || 0) + (file.linesRemoved || 0) + ((file.changeCount || 1) * 5);
|
|
@@ -257,14 +311,54 @@ function deriveCurrentPhase(runs) {
|
|
|
257
311
|
return 'Completed';
|
|
258
312
|
return 'Not Started';
|
|
259
313
|
}
|
|
260
|
-
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
|
+
};
|
|
261
339
|
if (requirement?.modules?.length) {
|
|
262
|
-
|
|
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
|
+
}
|
|
263
360
|
}
|
|
264
|
-
return
|
|
265
|
-
.flatMap((run) => run.tasks || [])
|
|
266
|
-
.map((task) => task.stageName)
|
|
267
|
-
.filter((stage) => stage && stage !== 'default'));
|
|
361
|
+
return [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
268
362
|
}
|
|
269
363
|
function renderListSection(title, values) {
|
|
270
364
|
if (values.length === 0)
|
|
@@ -314,24 +408,158 @@ export function normalizeModuleKey(value) {
|
|
|
314
408
|
return value
|
|
315
409
|
.trim()
|
|
316
410
|
.toLowerCase()
|
|
411
|
+
.replace(/\s*\/\s*/g, '/')
|
|
412
|
+
.replace(/-\//g, '/')
|
|
413
|
+
.replace(/\/-/g, '/')
|
|
317
414
|
.replace(/[^a-z0-9/_\u4e00-\u9fa5-]+/g, '-')
|
|
318
415
|
.replace(/\/+/g, '/')
|
|
319
416
|
.replace(/-+/g, '-')
|
|
320
417
|
.replace(/^-|-$/g, '');
|
|
321
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
|
+
}
|
|
322
535
|
export function loadMemoryIndex(projectRoot) {
|
|
323
536
|
const path = memoryIndexPath(projectRoot);
|
|
324
537
|
if (!fileExists(path)) {
|
|
325
538
|
return {
|
|
539
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
326
540
|
updatedAt: new Date().toISOString(),
|
|
327
|
-
|
|
541
|
+
items: [],
|
|
328
542
|
};
|
|
329
543
|
}
|
|
330
|
-
|
|
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
|
+
};
|
|
331
555
|
}
|
|
332
556
|
export function saveMemoryIndex(projectRoot, index) {
|
|
333
557
|
ensureDir(memoriesDir(projectRoot));
|
|
334
|
-
writeJson(memoryIndexPath(projectRoot),
|
|
558
|
+
writeJson(memoryIndexPath(projectRoot), {
|
|
559
|
+
schemaVersion: index.schemaVersion || MEMORY_SCHEMA_VERSION,
|
|
560
|
+
updatedAt: index.updatedAt,
|
|
561
|
+
items: index.items,
|
|
562
|
+
});
|
|
335
563
|
}
|
|
336
564
|
export function loadModuleMemory(projectRoot, moduleKey) {
|
|
337
565
|
const normalized = normalizeModuleKey(moduleKey);
|
|
@@ -352,12 +580,13 @@ export function loadModuleMemory(projectRoot, moduleKey) {
|
|
|
352
580
|
function upsertMemoryIndexEntry(projectRoot, record) {
|
|
353
581
|
const index = loadMemoryIndex(projectRoot);
|
|
354
582
|
const entry = memoryIndexEntryFromRecord(record);
|
|
355
|
-
const next = index.
|
|
583
|
+
const next = index.items.filter((item) => item.key !== record.moduleKey);
|
|
356
584
|
next.push(entry);
|
|
357
585
|
next.sort((a, b) => a.key.localeCompare(b.key));
|
|
358
586
|
saveMemoryIndex(projectRoot, {
|
|
587
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
359
588
|
updatedAt: new Date().toISOString(),
|
|
360
|
-
|
|
589
|
+
items: next,
|
|
361
590
|
});
|
|
362
591
|
}
|
|
363
592
|
function memoryIndexEntryFromRecord(record) {
|
|
@@ -367,9 +596,72 @@ function memoryIndexEntryFromRecord(record) {
|
|
|
367
596
|
summary: record.summary,
|
|
368
597
|
keywords: topItems([record.moduleKey, record.title, ...record.keywords], 12),
|
|
369
598
|
paths: filterMeaningfulPaths([...record.entryFiles, ...record.relatedPaths], 12),
|
|
599
|
+
tickets: topItems([...(record.tickets || [])]
|
|
600
|
+
.map((ticket) => ticket.ticket || ticket.branch || '')
|
|
601
|
+
.filter(Boolean), 8),
|
|
370
602
|
updatedAt: record.updatedAt,
|
|
371
603
|
};
|
|
372
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
|
+
}
|
|
373
665
|
function mergeMemoryIndexEntry(existing, next) {
|
|
374
666
|
if (!existing)
|
|
375
667
|
return next;
|
|
@@ -379,6 +671,7 @@ function mergeMemoryIndexEntry(existing, next) {
|
|
|
379
671
|
summary: next.summary || existing.summary,
|
|
380
672
|
keywords: topItems([...(existing.keywords || []), ...(next.keywords || [])], 12),
|
|
381
673
|
paths: filterMeaningfulPaths([...(existing.paths || []), ...(next.paths || [])], 12),
|
|
674
|
+
tickets: topItems([...(existing.tickets || []), ...(next.tickets || [])], 8),
|
|
382
675
|
updatedAt: next.updatedAt || existing.updatedAt,
|
|
383
676
|
};
|
|
384
677
|
}
|
|
@@ -392,6 +685,7 @@ function moduleMemoryRecordFromMarkdown(raw) {
|
|
|
392
685
|
const sections = extractMarkdownSections(raw);
|
|
393
686
|
const summary = sections.get('summary') || '';
|
|
394
687
|
return {
|
|
688
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
395
689
|
moduleKey,
|
|
396
690
|
title: raw.match(/^- Title:\s+(.+)$/m)?.[1]?.trim() || moduleKey,
|
|
397
691
|
summary,
|
|
@@ -404,6 +698,7 @@ function moduleMemoryRecordFromMarkdown(raw) {
|
|
|
404
698
|
pitfalls: parseListSection(raw, 'pitfalls'),
|
|
405
699
|
relatedRules: parseListSection(raw, 'related rules'),
|
|
406
700
|
tickets: [],
|
|
701
|
+
changes: [],
|
|
407
702
|
updatedAt: raw.match(/^- Updated At:\s+(.+)$/m)?.[1]?.trim() || new Date().toISOString(),
|
|
408
703
|
};
|
|
409
704
|
}
|
|
@@ -411,26 +706,46 @@ export function rebuildMemoryIndexFromDisk(projectRoot) {
|
|
|
411
706
|
ensureDir(moduleMemoriesDir(projectRoot));
|
|
412
707
|
migrateLegacyNestedModuleMemoryLayout(projectRoot);
|
|
413
708
|
const byKey = new Map();
|
|
414
|
-
|
|
415
|
-
byKey.set(entry.key, entry);
|
|
416
|
-
}
|
|
709
|
+
const expectedMarkdownViews = new Set();
|
|
417
710
|
for (const file of walkJsonFiles(moduleMemoriesDir(projectRoot))) {
|
|
418
711
|
if (basename(file) === 'index.json')
|
|
419
712
|
continue;
|
|
420
|
-
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
|
+
}
|
|
421
719
|
const entry = memoryIndexEntryFromRecord(record);
|
|
422
720
|
byKey.set(record.moduleKey, mergeMemoryIndexEntry(byKey.get(record.moduleKey), entry));
|
|
721
|
+
expectedMarkdownViews.add(moduleMemoryViewPath(projectRoot, record.moduleKey));
|
|
423
722
|
}
|
|
424
723
|
for (const file of walkMarkdownFiles(moduleMemoriesDir(projectRoot))) {
|
|
425
724
|
const record = moduleMemoryRecordFromMarkdown(readText(file));
|
|
426
725
|
if (!record)
|
|
427
726
|
continue;
|
|
428
|
-
const
|
|
429
|
-
|
|
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
|
+
}
|
|
430
744
|
}
|
|
431
745
|
const index = {
|
|
746
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
432
747
|
updatedAt: new Date().toISOString(),
|
|
433
|
-
|
|
748
|
+
items: [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key)),
|
|
434
749
|
};
|
|
435
750
|
saveMemoryIndex(projectRoot, index);
|
|
436
751
|
return index;
|
|
@@ -438,7 +753,10 @@ export function rebuildMemoryIndexFromDisk(projectRoot) {
|
|
|
438
753
|
export function saveModuleMemory(projectRoot, record) {
|
|
439
754
|
const path = moduleMemoryPath(projectRoot, record.moduleKey);
|
|
440
755
|
ensureDir(dirname(path));
|
|
441
|
-
writeJson(path,
|
|
756
|
+
writeJson(path, {
|
|
757
|
+
schemaVersion: record.schemaVersion || MEMORY_SCHEMA_VERSION,
|
|
758
|
+
...record,
|
|
759
|
+
});
|
|
442
760
|
upsertMemoryIndexEntry(projectRoot, record);
|
|
443
761
|
}
|
|
444
762
|
export function loadRunContext(projectRoot, branchName) {
|
|
@@ -479,6 +797,12 @@ export function renderModuleMemoryMarkdown(record) {
|
|
|
479
797
|
'',
|
|
480
798
|
renderListSection('Related Rules', record.relatedRules).trimEnd(),
|
|
481
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
|
+
'',
|
|
482
806
|
'## Related Tickets',
|
|
483
807
|
'',
|
|
484
808
|
...(record.tickets.length > 0
|
|
@@ -544,46 +868,29 @@ export function buildMemoryViews(projectRoot) {
|
|
|
544
868
|
for (const file of walkJsonFiles(moduleMemoriesDir(projectRoot))) {
|
|
545
869
|
if (basename(file) === 'index.json')
|
|
546
870
|
continue;
|
|
547
|
-
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
|
+
}
|
|
548
877
|
const viewPath = moduleMemoryViewPath(projectRoot, record.moduleKey);
|
|
549
878
|
ensureDir(dirname(viewPath));
|
|
550
879
|
writeText(viewPath, renderModuleMemoryMarkdown(record));
|
|
551
880
|
moduleViews++;
|
|
552
881
|
}
|
|
553
|
-
let contextViews = 0;
|
|
554
|
-
let packViews = 0;
|
|
555
|
-
const root = runsDir(projectRoot);
|
|
556
|
-
if (!fileExists(root)) {
|
|
557
|
-
rebuildMemoryIndexFromDisk(projectRoot);
|
|
558
|
-
return { moduleViews, contextViews, packViews };
|
|
559
|
-
}
|
|
560
|
-
for (const branchName of readdirSync(root)) {
|
|
561
|
-
const safeBranchDir = resolve(root, branchName);
|
|
562
|
-
if (!fileExists(safeBranchDir) || !statSync(safeBranchDir).isDirectory())
|
|
563
|
-
continue;
|
|
564
|
-
const contextPath = resolve(safeBranchDir, 'context.json');
|
|
565
|
-
if (!fileExists(contextPath))
|
|
566
|
-
continue;
|
|
567
|
-
const record = readJson(contextPath);
|
|
568
|
-
writeText(runContextViewPath(projectRoot, record.branch), renderRunContextMarkdown(record));
|
|
569
|
-
contextViews++;
|
|
570
|
-
const modules = record.modules
|
|
571
|
-
.map((moduleName) => loadModuleMemory(projectRoot, moduleName))
|
|
572
|
-
.filter((item) => item !== null);
|
|
573
|
-
writeText(runMemoryPackViewPath(projectRoot, record.branch), renderRunMemoryPackMarkdown(record, modules));
|
|
574
|
-
packViews++;
|
|
575
|
-
}
|
|
576
882
|
rebuildMemoryIndexFromDisk(projectRoot);
|
|
577
|
-
return { moduleViews, contextViews, packViews };
|
|
883
|
+
return { moduleViews, contextViews: 0, packViews: 0 };
|
|
578
884
|
}
|
|
579
885
|
export function buildRunContextFromBranch(projectRoot, branchName) {
|
|
886
|
+
const existing = loadRunContext(projectRoot, branchName);
|
|
580
887
|
const requirement = loadRequirement(projectRoot, branchName);
|
|
581
888
|
const analysis = loadAnalysis(projectRoot, branchName);
|
|
582
889
|
const runs = loadBranchRuns(projectRoot, branchName);
|
|
583
890
|
if (!requirement && !analysis && runs.length === 0) {
|
|
584
891
|
return null;
|
|
585
892
|
}
|
|
586
|
-
const
|
|
893
|
+
const moduleDescriptors = inferModuleDescriptors(requirement, runs);
|
|
587
894
|
const completed = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'done').map((task) => task.title));
|
|
588
895
|
const inProgress = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'in-progress').map((task) => task.title));
|
|
589
896
|
const next = topItems(runs.flatMap((run) => run.tasks || []).filter((task) => task.status === 'pending').map((task) => task.title));
|
|
@@ -595,25 +902,44 @@ export function buildRunContextFromBranch(projectRoot, branchName) {
|
|
|
595
902
|
...runs.flatMap((run) => run.deviations || []).map((deviation) => deviation.title),
|
|
596
903
|
];
|
|
597
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);
|
|
598
924
|
return {
|
|
599
925
|
branch: branchName,
|
|
600
926
|
ticket: extractTicket(requirement?.title || '') || extractTicket(branchName),
|
|
601
|
-
title,
|
|
602
|
-
summary:
|
|
603
|
-
currentPhase:
|
|
604
|
-
modules,
|
|
605
|
-
completed,
|
|
606
|
-
inProgress,
|
|
607
|
-
next,
|
|
608
|
-
decisions,
|
|
609
|
-
constraints,
|
|
610
|
-
keyFiles:
|
|
611
|
-
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 || []),
|
|
612
938
|
updatedAt: new Date().toISOString(),
|
|
613
939
|
};
|
|
614
940
|
}
|
|
615
|
-
function collectRelatedPaths(
|
|
616
|
-
const query =
|
|
941
|
+
function collectRelatedPaths(moduleDescriptor, branchContext, runs) {
|
|
942
|
+
const query = moduleDescriptor.key.toLowerCase();
|
|
617
943
|
const tokens = splitQueryTokens(query);
|
|
618
944
|
const scored = new Map();
|
|
619
945
|
for (const path of branchContext.keyFiles) {
|
|
@@ -623,10 +949,11 @@ function collectRelatedPaths(moduleName, branchContext, runs) {
|
|
|
623
949
|
}
|
|
624
950
|
for (const run of runs) {
|
|
625
951
|
for (const task of run.tasks || []) {
|
|
626
|
-
|
|
952
|
+
const taskDescriptor = deriveModuleDescriptor(task.stageName || '', (run.files || []).map((file) => file.path));
|
|
953
|
+
if (taskDescriptor.key !== moduleDescriptor.key)
|
|
627
954
|
continue;
|
|
628
955
|
for (const file of run.files || []) {
|
|
629
|
-
if (isAidaRuntimePath(file.path))
|
|
956
|
+
if (isAidaRuntimePath(file.path) || isNoisePath(file.path) || isGeneratedToolingPath(file.path))
|
|
630
957
|
continue;
|
|
631
958
|
const path = normalizeRepoPath(file.path);
|
|
632
959
|
const bonus = (file.linesAdded || 0) + (file.linesRemoved || 0) + 10;
|
|
@@ -640,24 +967,31 @@ function collectRelatedPaths(moduleName, branchContext, runs) {
|
|
|
640
967
|
export function upsertModuleMemory(projectRoot, input) {
|
|
641
968
|
const moduleKey = normalizeModuleKey(input.moduleKey);
|
|
642
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
|
+
}));
|
|
643
980
|
const record = {
|
|
981
|
+
schemaVersion: MEMORY_SCHEMA_VERSION,
|
|
644
982
|
moduleKey,
|
|
645
983
|
title: input.title || existing?.title || moduleKey,
|
|
646
984
|
summary: input.summary || existing?.summary || '',
|
|
647
985
|
keywords: topItems([...(existing?.keywords || []), ...(input.keywords || []), moduleKey, input.title || ''], 16),
|
|
648
|
-
entryFiles: filterMeaningfulPaths([...
|
|
649
|
-
relatedPaths: filterMeaningfulPaths([...
|
|
986
|
+
entryFiles: filterMeaningfulPaths([...existingEntryFiles, ...(input.entryFiles || [])], 8),
|
|
987
|
+
relatedPaths: filterMeaningfulPaths([...existingRelatedPaths, ...(input.relatedPaths || [])], 12),
|
|
650
988
|
dataFlow: topItems([...(existing?.dataFlow || []), ...(input.dataFlow || [])]),
|
|
651
989
|
decisions: topItems([...(existing?.decisions || []), ...(input.decisions || [])]),
|
|
652
990
|
constraints: topItems([...(existing?.constraints || []), ...(input.constraints || [])]),
|
|
653
991
|
pitfalls: topItems([...(existing?.pitfalls || []), ...(input.pitfalls || [])]),
|
|
654
992
|
relatedRules: topItems([...(existing?.relatedRules || []), ...(input.relatedRules || [])], 12),
|
|
655
|
-
tickets: [
|
|
656
|
-
|
|
657
|
-
...((input.tickets || []).filter((ticket) => ticket.summary.trim().length > 0)),
|
|
658
|
-
].filter((ticket, index, array) => array.findIndex((item) => item.ticket === ticket.ticket
|
|
659
|
-
&& item.branch === ticket.branch
|
|
660
|
-
&& 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),
|
|
661
995
|
updatedAt: new Date().toISOString(),
|
|
662
996
|
};
|
|
663
997
|
saveModuleMemory(projectRoot, record);
|
|
@@ -691,10 +1025,10 @@ export function searchModuleMemories(projectRoot, query, pathHints = []) {
|
|
|
691
1025
|
const tokens = splitQueryTokens(normalizedQuery);
|
|
692
1026
|
const hints = pathHints.map((hint) => hint.toLowerCase());
|
|
693
1027
|
let index = loadMemoryIndex(projectRoot);
|
|
694
|
-
if (index.
|
|
1028
|
+
if (index.items.length === 0 && fileExists(moduleMemoriesDir(projectRoot))) {
|
|
695
1029
|
index = rebuildMemoryIndexFromDisk(projectRoot);
|
|
696
1030
|
}
|
|
697
|
-
return index.
|
|
1031
|
+
return index.items
|
|
698
1032
|
.map((entry) => {
|
|
699
1033
|
let score = 0;
|
|
700
1034
|
score += scoreText(normalizedQuery, tokens, entry.key) * 2;
|
|
@@ -734,7 +1068,19 @@ export function migrateLegacyMemories(projectRoot) {
|
|
|
734
1068
|
const requirementData = requirement
|
|
735
1069
|
? readJson(resolve(branchPath, 'requirement.json'))
|
|
736
1070
|
: null;
|
|
737
|
-
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;
|
|
738
1084
|
const context = buildRunContextFromBranch(projectRoot, branchName);
|
|
739
1085
|
if (!context)
|
|
740
1086
|
continue;
|
|
@@ -743,24 +1089,22 @@ export function migrateLegacyMemories(projectRoot) {
|
|
|
743
1089
|
contextsWritten++;
|
|
744
1090
|
const runs = loadBranchRuns(projectRoot, branchName);
|
|
745
1091
|
const req = loadRequirement(projectRoot, branchName);
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
for (const candidate of moduleCandidates) {
|
|
750
|
-
if (!candidate.name.trim())
|
|
1092
|
+
const moduleDescriptors = inferModuleDescriptors(req, runs);
|
|
1093
|
+
for (const descriptor of moduleDescriptors) {
|
|
1094
|
+
if (!descriptor.key.trim())
|
|
751
1095
|
continue;
|
|
752
|
-
const
|
|
753
|
-
const relatedPaths = collectRelatedPaths(candidate.name, context, runs);
|
|
1096
|
+
const relatedPaths = collectRelatedPaths(descriptor, context, runs);
|
|
754
1097
|
upsertModuleMemory(projectRoot, {
|
|
755
|
-
moduleKey,
|
|
756
|
-
title:
|
|
757
|
-
summary:
|
|
758
|
-
keywords: [
|
|
1098
|
+
moduleKey: descriptor.key,
|
|
1099
|
+
title: descriptor.title,
|
|
1100
|
+
summary: descriptor.description || context.summary,
|
|
1101
|
+
keywords: [descriptor.title, descriptor.key, context.title],
|
|
759
1102
|
entryFiles: relatedPaths.slice(0, 5),
|
|
760
1103
|
relatedPaths,
|
|
761
1104
|
decisions: context.decisions,
|
|
762
1105
|
constraints: context.constraints,
|
|
763
1106
|
pitfalls: context.risks,
|
|
1107
|
+
changeTitle: context.title,
|
|
764
1108
|
tickets: [{
|
|
765
1109
|
ticket: context.ticket,
|
|
766
1110
|
branch: branchName,
|
|
@@ -769,7 +1113,7 @@ export function migrateLegacyMemories(projectRoot) {
|
|
|
769
1113
|
}],
|
|
770
1114
|
});
|
|
771
1115
|
moduleMemoriesWritten++;
|
|
772
|
-
modulesTouched.add(
|
|
1116
|
+
modulesTouched.add(descriptor.key);
|
|
773
1117
|
}
|
|
774
1118
|
}
|
|
775
1119
|
buildMemoryViews(projectRoot);
|
|
@@ -790,18 +1134,19 @@ export function rebuildCurrentBranchMemory(projectRoot, branchName) {
|
|
|
790
1134
|
const req = loadRequirement(projectRoot, branchName);
|
|
791
1135
|
const runs = loadBranchRuns(projectRoot, branchName);
|
|
792
1136
|
const modules = [];
|
|
793
|
-
for (const
|
|
794
|
-
const relatedPaths = collectRelatedPaths(
|
|
1137
|
+
for (const descriptor of inferModuleDescriptors(req, runs)) {
|
|
1138
|
+
const relatedPaths = collectRelatedPaths(descriptor, context, runs);
|
|
795
1139
|
modules.push(upsertModuleMemory(projectRoot, {
|
|
796
|
-
moduleKey:
|
|
797
|
-
title:
|
|
798
|
-
summary:
|
|
799
|
-
keywords: [
|
|
1140
|
+
moduleKey: descriptor.key,
|
|
1141
|
+
title: descriptor.title,
|
|
1142
|
+
summary: descriptor.description || context.summary,
|
|
1143
|
+
keywords: [descriptor.title, descriptor.key, context.title],
|
|
800
1144
|
entryFiles: relatedPaths.slice(0, 5),
|
|
801
1145
|
relatedPaths,
|
|
802
1146
|
decisions: context.decisions,
|
|
803
1147
|
constraints: context.constraints,
|
|
804
1148
|
pitfalls: context.risks,
|
|
1149
|
+
changeTitle: context.title,
|
|
805
1150
|
tickets: [{
|
|
806
1151
|
ticket: context.ticket,
|
|
807
1152
|
branch: branchName,
|