memorix 1.8.6 → 1.8.8

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 (51) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +2 -1
  3. package/README.zh-CN.md +2 -1
  4. package/dist/cli/index.js +2209 -1161
  5. package/dist/cli/memcode.js +0 -0
  6. package/dist/dashboard/static/app.js +51 -3
  7. package/dist/dashboard/static/style.css +88 -0
  8. package/dist/index.js +1726 -736
  9. package/dist/maintenance-runner.js +855 -218
  10. package/dist/media-audio-runner.js +856 -219
  11. package/dist/media-video-runner.js +854 -217
  12. package/dist/memcode-runtime/CHANGELOG.md +41 -0
  13. package/dist/sdk.js +1716 -726
  14. package/dist/vector-backfill-runner.js +853 -216
  15. package/docs/1.8.7-CODEGRAPH.md +166 -0
  16. package/docs/API_REFERENCE.md +23 -2
  17. package/docs/ARCHITECTURE.md +13 -0
  18. package/docs/README.md +4 -1
  19. package/package.json +4 -3
  20. package/plugins/antigravity/memorix/plugin.json +1 -1
  21. package/plugins/claude/memorix/.claude-plugin/plugin.json +2 -2
  22. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +2 -2
  23. package/plugins/codex/memorix/.codex-plugin/plugin.json +3 -3
  24. package/plugins/copilot/memorix/plugin.json +2 -2
  25. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  26. package/plugins/hermes/memorix/plugin.yaml +1 -1
  27. package/plugins/omp/memorix/package.json +1 -1
  28. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +3 -3
  29. package/plugins/pi/memorix/package.json +1 -1
  30. package/server.json +3 -3
  31. package/src/cli/commands/codegraph.ts +25 -11
  32. package/src/cli/commands/doctor.ts +11 -2
  33. package/src/cli/commands/serve-http.ts +27 -1
  34. package/src/cli/commands/setup.ts +1 -1
  35. package/src/codegraph/auto-context.ts +19 -10
  36. package/src/codegraph/context-pack.ts +20 -6
  37. package/src/codegraph/dashboard.ts +82 -0
  38. package/src/codegraph/external-provider.ts +10 -1
  39. package/src/codegraph/freshness.ts +1 -1
  40. package/src/codegraph/lite-provider.ts +81 -0
  41. package/src/codegraph/project-context.ts +18 -5
  42. package/src/codegraph/scip-provider.ts +181 -0
  43. package/src/codegraph/semantic-context.ts +139 -0
  44. package/src/codegraph/semantic-provider.ts +404 -0
  45. package/src/codegraph/store.ts +253 -18
  46. package/src/codegraph/types.ts +25 -3
  47. package/src/dashboard/server.ts +6 -0
  48. package/src/dashboard/static/style.css +88 -0
  49. package/src/knowledge/workset.ts +46 -44
  50. package/src/server/mcp-binding-store.ts +17 -0
  51. package/src/store/sqlite-db.ts +13 -0
@@ -0,0 +1,181 @@
1
+ import { isAbsolute, relative, resolve } from 'node:path';
2
+ import { sanitizeCredentials } from '../memory/secret-filter.js';
3
+ import { isCodeGraphExcludedPath } from './exclude.js';
4
+ import { normalizeCodePath } from './ids.js';
5
+ import type { CodeGraphOutline, ExternalCodeGraphRelation, ExternalCodeGraphSymbol } from './types.js';
6
+
7
+ /**
8
+ * SCIP is intentionally an input boundary here, not a second persistence
9
+ * format. The normalized outline is bounded and remains separate from the
10
+ * canonical local CodeGraph tables.
11
+ */
12
+ export interface ScipOutlineOptions {
13
+ projectRoot: string;
14
+ exclude?: string[];
15
+ maxNodes?: number;
16
+ maxEdges?: number;
17
+ maxFiles?: number;
18
+ }
19
+
20
+ const DEFAULT_MAX_NODES = 16;
21
+ const DEFAULT_MAX_EDGES = 24;
22
+ const DEFAULT_MAX_FILES = 12;
23
+
24
+ function isRecord(value: unknown): value is Record<string, unknown> {
25
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
26
+ }
27
+
28
+ function text(value: unknown, max = 240): string | undefined {
29
+ if (typeof value !== 'string') return undefined;
30
+ const normalized = sanitizeCredentials(value).replace(/[\u0000-\u001f]/g, ' ').replace(/\s+/g, ' ').trim();
31
+ return normalized && normalized.length <= max ? normalized : undefined;
32
+ }
33
+
34
+ function safeRelativePath(projectRoot: string, value: unknown, exclude?: string[]): string | undefined {
35
+ const raw = text(value, 1_024);
36
+ if (!raw || isAbsolute(raw)) return undefined;
37
+ const absolute = resolve(projectRoot, raw);
38
+ const fromRoot = relative(resolve(projectRoot), absolute);
39
+ if (!fromRoot || fromRoot === '..' || fromRoot.startsWith('..\\') || fromRoot.startsWith('../') || isAbsolute(fromRoot)) return undefined;
40
+ const normalized = normalizeCodePath(fromRoot);
41
+ return isCodeGraphExcludedPath(normalized, exclude) ? undefined : normalized;
42
+ }
43
+
44
+ function integer(value: unknown): number | undefined {
45
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;
46
+ }
47
+
48
+ function lineFromRange(value: unknown): number | undefined {
49
+ if (!Array.isArray(value) || value.length === 0) return undefined;
50
+ const line = integer(value[0]);
51
+ return line === undefined ? undefined : line + 1;
52
+ }
53
+
54
+ function symbolKind(value: unknown): string {
55
+ if (typeof value === 'string' && value.trim()) return value.trim().slice(0, 80);
56
+ if (typeof value === 'number' && Number.isInteger(value)) return `scip-kind-${value}`;
57
+ return 'symbol';
58
+ }
59
+
60
+ function symbolName(symbolId: string, value: unknown): string {
61
+ const displayName = text(value, 180);
62
+ if (displayName) return displayName;
63
+ const pieces = symbolId.split(/[/#.:]/).filter(Boolean);
64
+ return (pieces[pieces.length - 1] || symbolId).slice(0, 180);
65
+ }
66
+
67
+ function relationKind(relationship: Record<string, unknown>): string {
68
+ if (relationship.isImplementation === true || relationship.is_implementation === true) return 'implements';
69
+ if (relationship.isDefinition === true || relationship.is_definition === true) return 'defines';
70
+ if (relationship.isReference === true || relationship.is_reference === true) return 'references';
71
+ return 'references';
72
+ }
73
+
74
+ interface ScipNodeInfo {
75
+ symbolId: string;
76
+ node: ExternalCodeGraphSymbol;
77
+ defined: boolean;
78
+ line?: number;
79
+ relationships: Array<{ target: string; kind: string }>;
80
+ }
81
+
82
+ /** Normalize a bounded `scip print --json` payload into Memorix's outline. */
83
+ export function normalizeScipOutline(value: unknown, options: ScipOutlineOptions): CodeGraphOutline | undefined {
84
+ if (!isRecord(value) || !Array.isArray(value.documents)) return undefined;
85
+ const maxNodes = Math.max(2, Math.min(64, Math.floor(options.maxNodes ?? DEFAULT_MAX_NODES)));
86
+ const maxEdges = Math.max(1, Math.min(96, Math.floor(options.maxEdges ?? DEFAULT_MAX_EDGES)));
87
+ const maxFiles = Math.max(1, Math.min(32, Math.floor(options.maxFiles ?? DEFAULT_MAX_FILES)));
88
+ const nodes = new Map<string, ScipNodeInfo>();
89
+ const relatedFiles = new Set<string>();
90
+ const definitions = new Set<string>();
91
+
92
+ for (const document of value.documents.slice(0, maxFiles * 4)) {
93
+ if (!isRecord(document)) continue;
94
+ const filePath = safeRelativePath(options.projectRoot, document.relativePath ?? document.relative_path, options.exclude);
95
+ if (!filePath || relatedFiles.size >= maxFiles && !relatedFiles.has(filePath)) continue;
96
+ relatedFiles.add(filePath);
97
+ const occurrences = Array.isArray(document.occurrences) ? document.occurrences : [];
98
+ const definitionLines = new Map<string, number>();
99
+ for (const occurrence of occurrences) {
100
+ if (!isRecord(occurrence)) continue;
101
+ const id = text(occurrence.symbol, 240);
102
+ const isDefinition = occurrence.isDefinition === true || occurrence.is_definition === true;
103
+ const rawRoles = occurrence.symbolRoles ?? occurrence.symbol_roles;
104
+ if (!id || (rawRoles === undefined && !isDefinition)) continue;
105
+ const roles = integer(rawRoles) ?? 0;
106
+ // SCIP SymbolRole.Definition is bit 1. Accept the explicit boolean used
107
+ // by a few JSON emitters as well, without trusting arbitrary roles.
108
+ if ((roles & 1) !== 0 || isDefinition) {
109
+ definitions.add(id);
110
+ const line = lineFromRange(occurrence.range);
111
+ if (line !== undefined) definitionLines.set(id, line);
112
+ }
113
+ }
114
+
115
+ const symbolInfos = Array.isArray(document.symbols) ? document.symbols : [];
116
+ for (const rawSymbol of symbolInfos) {
117
+ if (!isRecord(rawSymbol)) continue;
118
+ const id = text(rawSymbol.symbol, 240);
119
+ if (!id || nodes.has(id)) continue;
120
+ const relationships: Array<{ target: string; kind: string }> = [];
121
+ if (Array.isArray(rawSymbol.relationships)) {
122
+ for (const rawRelationship of rawSymbol.relationships.slice(0, 32)) {
123
+ if (!isRecord(rawRelationship)) continue;
124
+ const target = text(rawRelationship.symbol, 240);
125
+ if (target) relationships.push({ target, kind: relationKind(rawRelationship) });
126
+ }
127
+ }
128
+ nodes.set(id, {
129
+ symbolId: id,
130
+ node: {
131
+ id,
132
+ name: symbolName(id, rawSymbol.displayName ?? rawSymbol.display_name),
133
+ qualifiedName: text(rawSymbol.displayName ?? rawSymbol.display_name, 220),
134
+ kind: symbolKind(rawSymbol.kind),
135
+ path: filePath,
136
+ language: text(document.language, 80),
137
+ ...(definitionLines.has(id) ? { startLine: definitionLines.get(id) } : {}),
138
+ },
139
+ defined: definitions.has(id),
140
+ ...(definitionLines.has(id) ? { line: definitionLines.get(id) } : {}),
141
+ relationships,
142
+ });
143
+ }
144
+ }
145
+
146
+ if (nodes.size === 0) return undefined;
147
+
148
+ const selectedNodes = [...nodes.values()]
149
+ .sort((left, right) => Number(right.defined) - Number(left.defined) || left.node.path.localeCompare(right.node.path) || left.node.name.localeCompare(right.node.name))
150
+ .slice(0, maxNodes);
151
+ const selectedById = new Map(selectedNodes.map(item => [item.symbolId, item]));
152
+ const relations: ExternalCodeGraphRelation[] = [];
153
+ for (const source of selectedNodes) {
154
+ for (const relationship of source.relationships) {
155
+ const target = selectedById.get(relationship.target);
156
+ if (!target || relations.length >= maxEdges) continue;
157
+ relations.push({
158
+ from: source.node,
159
+ to: target.node,
160
+ kind: relationship.kind,
161
+ ...(source.line ? { line: source.line } : {}),
162
+ });
163
+ }
164
+ }
165
+
166
+ const entryPoints = selectedNodes
167
+ .filter(item => item.defined)
168
+ .slice(0, 5)
169
+ .map(item => item.node);
170
+ return {
171
+ provider: 'external',
172
+ entryPoints: entryPoints.length > 0 ? entryPoints : selectedNodes.slice(0, 5).map(item => item.node),
173
+ relations,
174
+ relatedFiles: [...new Set(selectedNodes.map(item => item.node.path))].slice(0, maxFiles),
175
+ stats: {
176
+ nodes: nodes.size,
177
+ edges: relations.length,
178
+ files: relatedFiles.size,
179
+ },
180
+ };
181
+ }
@@ -0,0 +1,139 @@
1
+ import type { CodeGraphOutline, CodeGraphProviderQuality, CodeGraphStatus, CodeSymbol } from './types.js';
2
+ import type { CodeGraphStore } from './store.js';
3
+
4
+ interface SemanticContextInput {
5
+ store: CodeGraphStore;
6
+ projectId: string;
7
+ task: string;
8
+ preferredPaths?: string[];
9
+ maxNodes?: number;
10
+ maxEdges?: number;
11
+ }
12
+
13
+ function tokens(text: string): string[] {
14
+ return [...new Set((text.toLowerCase().match(/[a-z0-9_$./-]+|[\u4e00-\u9fff]+/g) ?? [])
15
+ .filter(token => token.length > 1))];
16
+ }
17
+
18
+ function scoreSymbol(symbol: CodeSymbol, taskTokens: string[], preferred: Set<string>): number {
19
+ const name = `${symbol.name} ${symbol.qualifiedName} ${symbol.path}`.toLowerCase();
20
+ let score = preferred.has(symbol.path) ? 100 : 0;
21
+ for (const token of taskTokens) {
22
+ if (symbol.name.toLowerCase() === token) score += 20;
23
+ else if (name.includes(token)) score += 6;
24
+ }
25
+ if (score > 0 && (symbol.kind === 'function' || symbol.kind === 'method')) score += 1;
26
+ return score;
27
+ }
28
+
29
+ /** Build a small internal semantic outline from the persisted compiler graph. */
30
+ export function buildSemanticContext(input: SemanticContextInput): CodeGraphOutline | undefined {
31
+ const maxNodes = Math.max(2, Math.min(16, Math.floor(input.maxNodes ?? 8)));
32
+ const maxEdges = Math.max(1, Math.min(24, Math.floor(input.maxEdges ?? 12)));
33
+ const taskTokens = tokens(input.task);
34
+ const preferred = new Set((input.preferredPaths ?? []).map(path => path.replace(/\\/g, '/')));
35
+ const symbols = input.store.listSymbols(input.projectId)
36
+ .filter(symbol => symbol.source === 'typescript-compiler' && !symbol.stale)
37
+ .map((symbol, index) => ({ symbol, score: scoreSymbol(symbol, taskTokens, preferred), index }))
38
+ .filter(item => item.score > 0)
39
+ .sort((a, b) => b.score - a.score || a.symbol.path.localeCompare(b.symbol.path) || a.symbol.startLine! - b.symbol.startLine! || a.index - b.index)
40
+ .slice(0, maxNodes)
41
+ .map(item => item.symbol);
42
+ if (symbols.length === 0) return undefined;
43
+
44
+ const byId = new Map(symbols.map(symbol => [symbol.id, symbol]));
45
+ const edges = input.store.listEdges(input.projectId)
46
+ .filter(edge => edge.source === 'typescript-compiler')
47
+ .filter(edge => (edge.fromSymbolId && byId.has(edge.fromSymbolId)) || (edge.toSymbolId && byId.has(edge.toSymbolId)))
48
+ .slice(0, maxEdges);
49
+ const endpointIds = new Set<string>();
50
+ for (const edge of edges) {
51
+ if (edge.fromSymbolId) endpointIds.add(edge.fromSymbolId);
52
+ if (edge.toSymbolId) endpointIds.add(edge.toSymbolId);
53
+ }
54
+ const endpointSymbols = input.store.listSymbols(input.projectId)
55
+ .filter(symbol => symbol.source === 'typescript-compiler' && endpointIds.has(symbol.id));
56
+ const allSymbols = [...symbols, ...endpointSymbols]
57
+ .filter((symbol, index, items) => items.findIndex(item => item.id === symbol.id) === index)
58
+ .slice(0, maxNodes + maxEdges);
59
+ const allById = new Map(allSymbols.map(symbol => [symbol.id, symbol]));
60
+ const relations = edges.flatMap(edge => {
61
+ const from = edge.fromSymbolId ? allById.get(edge.fromSymbolId) : undefined;
62
+ const to = edge.toSymbolId ? allById.get(edge.toSymbolId) : undefined;
63
+ if (!from || !to) return [];
64
+ return [{
65
+ from: {
66
+ id: from.id,
67
+ name: from.name,
68
+ qualifiedName: from.qualifiedName,
69
+ kind: from.kind,
70
+ path: from.path,
71
+ ...(from.startLine ? { startLine: from.startLine } : {}),
72
+ ...(from.endLine ? { endLine: from.endLine } : {}),
73
+ language: 'typescript',
74
+ },
75
+ to: {
76
+ id: to.id,
77
+ name: to.name,
78
+ qualifiedName: to.qualifiedName,
79
+ kind: to.kind,
80
+ path: to.path,
81
+ ...(to.startLine ? { startLine: to.startLine } : {}),
82
+ ...(to.endLine ? { endLine: to.endLine } : {}),
83
+ language: 'typescript',
84
+ },
85
+ kind: edge.type,
86
+ ...(edge.evidence?.match(/line (\d+)/)?.[1] ? { line: Number(edge.evidence.match(/line (\d+)/)![1]) } : {}),
87
+ }];
88
+ });
89
+ const entryPoints = symbols.slice(0, Math.min(5, symbols.length)).map(symbol => ({
90
+ id: symbol.id,
91
+ name: symbol.name,
92
+ qualifiedName: symbol.qualifiedName,
93
+ kind: symbol.kind,
94
+ path: symbol.path,
95
+ ...(symbol.startLine ? { startLine: symbol.startLine } : {}),
96
+ ...(symbol.endLine ? { endLine: symbol.endLine } : {}),
97
+ language: 'typescript',
98
+ }));
99
+ const files = input.store.listFiles(input.projectId);
100
+ const fileById = new Map(files.map(file => [file.id, file.path]));
101
+ const relatedFiles = [...new Set([
102
+ ...allSymbols.map(symbol => symbol.path),
103
+ ...edges.flatMap(edge => [
104
+ edge.fromFileId ? fileById.get(edge.fromFileId) : undefined,
105
+ edge.toFileId ? fileById.get(edge.toFileId) : undefined,
106
+ ]),
107
+ ].filter((path): path is string => Boolean(path)))].slice(0, 8);
108
+ return {
109
+ provider: 'semantic',
110
+ entryPoints,
111
+ relations,
112
+ relatedFiles,
113
+ stats: {
114
+ nodes: allSymbols.length,
115
+ edges: relations.length,
116
+ files: relatedFiles.length,
117
+ },
118
+ };
119
+ }
120
+
121
+ export function qualityWithPersistedSemantic(
122
+ quality: CodeGraphProviderQuality,
123
+ status: Pick<CodeGraphStatus, 'semanticSymbols' | 'semanticEdges' | 'parserErrors'>,
124
+ ): CodeGraphProviderQuality {
125
+ const hasSemantic = status.semanticSymbols > 0 || status.semanticEdges > 0;
126
+ const semantic = {
127
+ state: status.parserErrors > 0 ? 'partial' as const : !hasSemantic ? 'not-indexed' as const : 'ready' as const,
128
+ symbols: status.semanticSymbols,
129
+ edges: status.semanticEdges,
130
+ parserErrors: status.parserErrors,
131
+ };
132
+ return {
133
+ ...quality,
134
+ semantic,
135
+ ...(hasSemantic && quality.selected !== 'external'
136
+ ? { selected: 'semantic' as const, selectedQuality: 'semantic' as const }
137
+ : {}),
138
+ };
139
+ }