@graphmemory/server 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +216 -0
- package/dist/api/index.js +473 -0
- package/dist/api/rest/code.js +78 -0
- package/dist/api/rest/docs.js +80 -0
- package/dist/api/rest/embed.js +47 -0
- package/dist/api/rest/files.js +64 -0
- package/dist/api/rest/graph.js +71 -0
- package/dist/api/rest/index.js +371 -0
- package/dist/api/rest/knowledge.js +239 -0
- package/dist/api/rest/skills.js +285 -0
- package/dist/api/rest/tasks.js +273 -0
- package/dist/api/rest/tools.js +157 -0
- package/dist/api/rest/validation.js +196 -0
- package/dist/api/rest/websocket.js +71 -0
- package/dist/api/tools/code/get-file-symbols.js +30 -0
- package/dist/api/tools/code/get-symbol.js +22 -0
- package/dist/api/tools/code/list-files.js +18 -0
- package/dist/api/tools/code/search-code.js +27 -0
- package/dist/api/tools/code/search-files.js +22 -0
- package/dist/api/tools/context/get-context.js +19 -0
- package/dist/api/tools/docs/cross-references.js +76 -0
- package/dist/api/tools/docs/explain-symbol.js +55 -0
- package/dist/api/tools/docs/find-examples.js +52 -0
- package/dist/api/tools/docs/get-node.js +24 -0
- package/dist/api/tools/docs/get-toc.js +22 -0
- package/dist/api/tools/docs/list-snippets.js +46 -0
- package/dist/api/tools/docs/list-topics.js +18 -0
- package/dist/api/tools/docs/search-files.js +22 -0
- package/dist/api/tools/docs/search-snippets.js +43 -0
- package/dist/api/tools/docs/search.js +27 -0
- package/dist/api/tools/file-index/get-file-info.js +21 -0
- package/dist/api/tools/file-index/list-all-files.js +28 -0
- package/dist/api/tools/file-index/search-all-files.js +24 -0
- package/dist/api/tools/knowledge/add-attachment.js +31 -0
- package/dist/api/tools/knowledge/create-note.js +20 -0
- package/dist/api/tools/knowledge/create-relation.js +29 -0
- package/dist/api/tools/knowledge/delete-note.js +19 -0
- package/dist/api/tools/knowledge/delete-relation.js +23 -0
- package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
- package/dist/api/tools/knowledge/get-note.js +20 -0
- package/dist/api/tools/knowledge/list-notes.js +18 -0
- package/dist/api/tools/knowledge/list-relations.js +17 -0
- package/dist/api/tools/knowledge/remove-attachment.js +19 -0
- package/dist/api/tools/knowledge/search-notes.js +25 -0
- package/dist/api/tools/knowledge/update-note.js +34 -0
- package/dist/api/tools/skills/add-attachment.js +31 -0
- package/dist/api/tools/skills/bump-usage.js +19 -0
- package/dist/api/tools/skills/create-skill-link.js +25 -0
- package/dist/api/tools/skills/create-skill.js +26 -0
- package/dist/api/tools/skills/delete-skill-link.js +23 -0
- package/dist/api/tools/skills/delete-skill.js +20 -0
- package/dist/api/tools/skills/find-linked-skills.js +25 -0
- package/dist/api/tools/skills/get-skill.js +21 -0
- package/dist/api/tools/skills/link-skill.js +23 -0
- package/dist/api/tools/skills/list-skills.js +20 -0
- package/dist/api/tools/skills/recall-skills.js +18 -0
- package/dist/api/tools/skills/remove-attachment.js +19 -0
- package/dist/api/tools/skills/search-skills.js +25 -0
- package/dist/api/tools/skills/update-skill.js +58 -0
- package/dist/api/tools/tasks/add-attachment.js +31 -0
- package/dist/api/tools/tasks/create-task-link.js +25 -0
- package/dist/api/tools/tasks/create-task.js +26 -0
- package/dist/api/tools/tasks/delete-task-link.js +23 -0
- package/dist/api/tools/tasks/delete-task.js +20 -0
- package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
- package/dist/api/tools/tasks/get-task.js +20 -0
- package/dist/api/tools/tasks/link-task.js +23 -0
- package/dist/api/tools/tasks/list-tasks.js +25 -0
- package/dist/api/tools/tasks/move-task.js +38 -0
- package/dist/api/tools/tasks/remove-attachment.js +19 -0
- package/dist/api/tools/tasks/search-tasks.js +25 -0
- package/dist/api/tools/tasks/update-task.js +58 -0
- package/dist/cli/index.js +617 -0
- package/dist/cli/indexer.js +275 -0
- package/dist/graphs/attachment-types.js +74 -0
- package/dist/graphs/code-types.js +10 -0
- package/dist/graphs/code.js +204 -0
- package/dist/graphs/docs.js +231 -0
- package/dist/graphs/file-index-types.js +10 -0
- package/dist/graphs/file-index.js +310 -0
- package/dist/graphs/file-lang.js +119 -0
- package/dist/graphs/knowledge-types.js +32 -0
- package/dist/graphs/knowledge.js +768 -0
- package/dist/graphs/manager-types.js +87 -0
- package/dist/graphs/skill-types.js +10 -0
- package/dist/graphs/skill.js +1016 -0
- package/dist/graphs/task-types.js +17 -0
- package/dist/graphs/task.js +972 -0
- package/dist/lib/access.js +67 -0
- package/dist/lib/embedder.js +235 -0
- package/dist/lib/events-log.js +401 -0
- package/dist/lib/file-import.js +328 -0
- package/dist/lib/file-mirror.js +461 -0
- package/dist/lib/frontmatter.js +17 -0
- package/dist/lib/jwt.js +146 -0
- package/dist/lib/mirror-watcher.js +637 -0
- package/dist/lib/multi-config.js +393 -0
- package/dist/lib/parsers/code.js +214 -0
- package/dist/lib/parsers/codeblock.js +33 -0
- package/dist/lib/parsers/docs.js +199 -0
- package/dist/lib/parsers/languages/index.js +15 -0
- package/dist/lib/parsers/languages/registry.js +68 -0
- package/dist/lib/parsers/languages/types.js +2 -0
- package/dist/lib/parsers/languages/typescript.js +306 -0
- package/dist/lib/project-manager.js +458 -0
- package/dist/lib/promise-queue.js +22 -0
- package/dist/lib/search/bm25.js +167 -0
- package/dist/lib/search/code.js +103 -0
- package/dist/lib/search/docs.js +106 -0
- package/dist/lib/search/file-index.js +31 -0
- package/dist/lib/search/files.js +61 -0
- package/dist/lib/search/knowledge.js +101 -0
- package/dist/lib/search/skills.js +104 -0
- package/dist/lib/search/tasks.js +103 -0
- package/dist/lib/team.js +89 -0
- package/dist/lib/watcher.js +67 -0
- package/dist/ui/assets/index-D6oxrVF7.js +1759 -0
- package/dist/ui/assets/index-kKd4mVrh.css +1 -0
- package/dist/ui/favicon.svg +1 -0
- package/dist/ui/icons.svg +24 -0
- package/dist/ui/index.html +14 -0
- package/package.json +89 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GRAPH_NAMES = void 0;
|
|
7
|
+
exports.embeddingFingerprint = embeddingFingerprint;
|
|
8
|
+
exports.formatAuthor = formatAuthor;
|
|
9
|
+
exports.loadMultiConfig = loadMultiConfig;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const yaml_1 = require("yaml");
|
|
14
|
+
const zod_1 = require("zod");
|
|
15
|
+
const HOME = os_1.default.homedir();
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Zod schemas
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const authorSchema = zod_1.z.object({
|
|
20
|
+
name: zod_1.z.string(),
|
|
21
|
+
email: zod_1.z.string(),
|
|
22
|
+
});
|
|
23
|
+
// Model config: taken as a whole from the first level that defines it (no field merge)
|
|
24
|
+
const modelConfigSchema = zod_1.z.object({
|
|
25
|
+
name: zod_1.z.string(),
|
|
26
|
+
pooling: zod_1.z.enum(['mean', 'cls']).optional(),
|
|
27
|
+
normalize: zod_1.z.boolean().optional(),
|
|
28
|
+
dtype: zod_1.z.string().optional(),
|
|
29
|
+
queryPrefix: zod_1.z.string().optional(),
|
|
30
|
+
documentPrefix: zod_1.z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
// Embedding config: each field individually inherits up the chain
|
|
33
|
+
const embeddingConfigSchema = zod_1.z.object({
|
|
34
|
+
batchSize: zod_1.z.number().int().positive().optional(),
|
|
35
|
+
maxChars: zod_1.z.number().int().positive().optional(),
|
|
36
|
+
cacheSize: zod_1.z.number().int().min(0).optional(),
|
|
37
|
+
remote: zod_1.z.string().optional(), // Remote embedding API URL
|
|
38
|
+
remoteApiKey: zod_1.z.string().optional(), // API key for remote embedding
|
|
39
|
+
});
|
|
40
|
+
const accessLevelSchema = zod_1.z.enum(['deny', 'r', 'rw']);
|
|
41
|
+
const accessMapSchema = zod_1.z.record(zod_1.z.string(), accessLevelSchema).optional();
|
|
42
|
+
// Exclude accepts string ("a,b") or array (["a", "b"]) in YAML
|
|
43
|
+
const excludeSchema = zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional();
|
|
44
|
+
const userSchema = zod_1.z.object({
|
|
45
|
+
name: zod_1.z.string(),
|
|
46
|
+
email: zod_1.z.string(),
|
|
47
|
+
apiKey: zod_1.z.string(),
|
|
48
|
+
passwordHash: zod_1.z.string().optional(),
|
|
49
|
+
});
|
|
50
|
+
const graphConfigSchema = zod_1.z.object({
|
|
51
|
+
enabled: zod_1.z.boolean().optional(),
|
|
52
|
+
include: zod_1.z.string().optional(),
|
|
53
|
+
exclude: excludeSchema,
|
|
54
|
+
model: modelConfigSchema.optional(),
|
|
55
|
+
embedding: embeddingConfigSchema.optional(),
|
|
56
|
+
access: accessMapSchema,
|
|
57
|
+
});
|
|
58
|
+
const graphsConfigSchema = zod_1.z.object({
|
|
59
|
+
docs: graphConfigSchema.optional(),
|
|
60
|
+
code: graphConfigSchema.optional(),
|
|
61
|
+
knowledge: graphConfigSchema.optional(),
|
|
62
|
+
tasks: graphConfigSchema.optional(),
|
|
63
|
+
files: graphConfigSchema.optional(),
|
|
64
|
+
skills: graphConfigSchema.optional(),
|
|
65
|
+
});
|
|
66
|
+
const projectSchema = zod_1.z.object({
|
|
67
|
+
projectDir: zod_1.z.string(),
|
|
68
|
+
graphMemory: zod_1.z.string().optional(),
|
|
69
|
+
exclude: excludeSchema,
|
|
70
|
+
chunkDepth: zod_1.z.number().int().positive().optional(),
|
|
71
|
+
maxFileSize: zod_1.z.number().int().positive().optional(),
|
|
72
|
+
model: modelConfigSchema.optional(),
|
|
73
|
+
embedding: embeddingConfigSchema.optional(),
|
|
74
|
+
graphs: graphsConfigSchema.optional(),
|
|
75
|
+
author: authorSchema.optional(),
|
|
76
|
+
access: accessMapSchema,
|
|
77
|
+
});
|
|
78
|
+
const embeddingApiSchema = zod_1.z.object({
|
|
79
|
+
enabled: zod_1.z.boolean().optional(),
|
|
80
|
+
apiKey: zod_1.z.string().optional(),
|
|
81
|
+
maxTexts: zod_1.z.number().int().positive().optional(), // max texts per request
|
|
82
|
+
maxTextChars: zod_1.z.number().int().positive().optional(), // max chars per text
|
|
83
|
+
});
|
|
84
|
+
const rateLimitSchema = zod_1.z.object({
|
|
85
|
+
global: zod_1.z.number().int().min(0).optional(), // req/min per IP (0 = disabled)
|
|
86
|
+
search: zod_1.z.number().int().min(0).optional(), // req/min per IP for search/embed
|
|
87
|
+
auth: zod_1.z.number().int().min(0).optional(), // req/min per IP for login
|
|
88
|
+
});
|
|
89
|
+
const serverSchema = zod_1.z.object({
|
|
90
|
+
host: zod_1.z.string().optional(),
|
|
91
|
+
port: zod_1.z.number().int().positive().optional(),
|
|
92
|
+
sessionTimeout: zod_1.z.number().int().positive().optional(),
|
|
93
|
+
modelsDir: zod_1.z.string().optional(),
|
|
94
|
+
corsOrigins: zod_1.z.array(zod_1.z.string()).optional(),
|
|
95
|
+
model: modelConfigSchema.optional(),
|
|
96
|
+
embedding: embeddingConfigSchema.optional(),
|
|
97
|
+
embeddingApi: embeddingApiSchema.optional(),
|
|
98
|
+
defaultAccess: accessLevelSchema.optional(),
|
|
99
|
+
access: accessMapSchema,
|
|
100
|
+
jwtSecret: zod_1.z.string().optional(),
|
|
101
|
+
accessTokenTtl: zod_1.z.string().optional(),
|
|
102
|
+
refreshTokenTtl: zod_1.z.string().optional(),
|
|
103
|
+
rateLimit: rateLimitSchema.optional(),
|
|
104
|
+
maxFileSize: zod_1.z.number().int().positive().optional(),
|
|
105
|
+
exclude: excludeSchema,
|
|
106
|
+
});
|
|
107
|
+
const wsGraphConfigSchema = zod_1.z.object({
|
|
108
|
+
enabled: zod_1.z.boolean().optional(),
|
|
109
|
+
exclude: excludeSchema,
|
|
110
|
+
model: modelConfigSchema.optional(),
|
|
111
|
+
embedding: embeddingConfigSchema.optional(),
|
|
112
|
+
access: accessMapSchema,
|
|
113
|
+
});
|
|
114
|
+
const wsGraphsConfigSchema = zod_1.z.object({
|
|
115
|
+
knowledge: wsGraphConfigSchema.optional(),
|
|
116
|
+
tasks: wsGraphConfigSchema.optional(),
|
|
117
|
+
skills: wsGraphConfigSchema.optional(),
|
|
118
|
+
});
|
|
119
|
+
const workspaceSchema = zod_1.z.object({
|
|
120
|
+
projects: zod_1.z.array(zod_1.z.string()),
|
|
121
|
+
graphMemory: zod_1.z.string().optional(),
|
|
122
|
+
mirrorDir: zod_1.z.string().optional(),
|
|
123
|
+
model: modelConfigSchema.optional(),
|
|
124
|
+
embedding: embeddingConfigSchema.optional(),
|
|
125
|
+
graphs: wsGraphsConfigSchema.optional(),
|
|
126
|
+
author: authorSchema.optional(),
|
|
127
|
+
access: accessMapSchema,
|
|
128
|
+
maxFileSize: zod_1.z.number().int().positive().optional(),
|
|
129
|
+
exclude: excludeSchema,
|
|
130
|
+
});
|
|
131
|
+
const configFileSchema = zod_1.z.object({
|
|
132
|
+
author: authorSchema.optional(),
|
|
133
|
+
server: serverSchema.optional(),
|
|
134
|
+
users: zod_1.z.record(zod_1.z.string(), userSchema).optional(),
|
|
135
|
+
projects: zod_1.z.record(zod_1.z.string(), projectSchema),
|
|
136
|
+
workspaces: zod_1.z.record(zod_1.z.string(), workspaceSchema).optional(),
|
|
137
|
+
});
|
|
138
|
+
exports.GRAPH_NAMES = ['docs', 'code', 'knowledge', 'tasks', 'files', 'skills'];
|
|
139
|
+
/**
|
|
140
|
+
* Build a stable fingerprint string from model config fields that affect stored vectors.
|
|
141
|
+
* queryPrefix excluded: only affects query-time, not stored document vectors.
|
|
142
|
+
*/
|
|
143
|
+
function embeddingFingerprint(model) {
|
|
144
|
+
return `${model.name}|${model.pooling}|${model.normalize}|${model.dtype ?? ''}|${model.documentPrefix}`;
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Helpers
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
const AUTHOR_DEFAULT = { name: '', email: '' };
|
|
150
|
+
/**
|
|
151
|
+
* Format an author as a git-style string: "Name <email>".
|
|
152
|
+
* Returns empty string if name is not set.
|
|
153
|
+
*/
|
|
154
|
+
function formatAuthor(author) {
|
|
155
|
+
if (!author.name)
|
|
156
|
+
return '';
|
|
157
|
+
return `${author.name} <${author.email}>`;
|
|
158
|
+
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Defaults
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
const MODEL_DEFAULTS = {
|
|
163
|
+
name: 'Xenova/bge-m3',
|
|
164
|
+
pooling: 'cls',
|
|
165
|
+
normalize: true,
|
|
166
|
+
queryPrefix: '',
|
|
167
|
+
documentPrefix: '',
|
|
168
|
+
};
|
|
169
|
+
const EMBEDDING_DEFAULTS = {
|
|
170
|
+
batchSize: 1,
|
|
171
|
+
maxChars: 8000,
|
|
172
|
+
cacheSize: 10_000,
|
|
173
|
+
};
|
|
174
|
+
const RATE_LIMIT_DEFAULTS = {
|
|
175
|
+
global: 600,
|
|
176
|
+
search: 120,
|
|
177
|
+
auth: 10,
|
|
178
|
+
};
|
|
179
|
+
const SERVER_DEFAULTS = {
|
|
180
|
+
host: '127.0.0.1',
|
|
181
|
+
port: 3000,
|
|
182
|
+
sessionTimeout: 1800,
|
|
183
|
+
modelsDir: path_1.default.join(HOME, '.graph-memory/models'),
|
|
184
|
+
model: MODEL_DEFAULTS,
|
|
185
|
+
embedding: EMBEDDING_DEFAULTS,
|
|
186
|
+
defaultAccess: 'rw',
|
|
187
|
+
accessTokenTtl: '15m',
|
|
188
|
+
refreshTokenTtl: '7d',
|
|
189
|
+
rateLimit: RATE_LIMIT_DEFAULTS,
|
|
190
|
+
maxFileSize: 1 * 1024 * 1024, // 1 MB
|
|
191
|
+
exclude: ['**/node_modules/**', '**/dist/**'],
|
|
192
|
+
};
|
|
193
|
+
const PROJECT_DEFAULTS = {
|
|
194
|
+
docsInclude: '**/*.md',
|
|
195
|
+
codeInclude: '**/*.{js,ts,jsx,tsx}',
|
|
196
|
+
chunkDepth: 4,
|
|
197
|
+
};
|
|
198
|
+
/** Parse comma-separated exclude string into array of patterns. */
|
|
199
|
+
function parseExclude(raw) {
|
|
200
|
+
if (!raw)
|
|
201
|
+
return [];
|
|
202
|
+
if (Array.isArray(raw))
|
|
203
|
+
return raw.map(p => p.trim()).filter(Boolean);
|
|
204
|
+
return raw.split(',').map(p => p.trim()).filter(Boolean);
|
|
205
|
+
}
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Resolution
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
/**
|
|
210
|
+
* Resolve ModelConfig: take the whole object from the first level that defines it.
|
|
211
|
+
* If undefined, returns parent (next level up the chain).
|
|
212
|
+
*/
|
|
213
|
+
function resolveModel(raw, parent) {
|
|
214
|
+
if (!raw)
|
|
215
|
+
return parent;
|
|
216
|
+
return {
|
|
217
|
+
name: raw.name,
|
|
218
|
+
pooling: raw.pooling ?? MODEL_DEFAULTS.pooling,
|
|
219
|
+
normalize: raw.normalize ?? MODEL_DEFAULTS.normalize,
|
|
220
|
+
dtype: raw.dtype,
|
|
221
|
+
queryPrefix: raw.queryPrefix ?? MODEL_DEFAULTS.queryPrefix,
|
|
222
|
+
documentPrefix: raw.documentPrefix ?? MODEL_DEFAULTS.documentPrefix,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Resolve EmbeddingConfig: each field individually inherits up the chain.
|
|
227
|
+
*/
|
|
228
|
+
function resolveEmbedding(raw, parent) {
|
|
229
|
+
if (!raw)
|
|
230
|
+
return parent;
|
|
231
|
+
return {
|
|
232
|
+
batchSize: raw.batchSize ?? parent.batchSize,
|
|
233
|
+
maxChars: raw.maxChars ?? parent.maxChars,
|
|
234
|
+
cacheSize: raw.cacheSize ?? parent.cacheSize,
|
|
235
|
+
remote: raw.remote ?? parent.remote,
|
|
236
|
+
remoteApiKey: raw.remoteApiKey ?? parent.remoteApiKey,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Load
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
/**
|
|
243
|
+
* Load and validate a `graph-memory.yaml` config file.
|
|
244
|
+
* Resolves all paths to absolute, applies defaults.
|
|
245
|
+
*/
|
|
246
|
+
function loadMultiConfig(yamlPath) {
|
|
247
|
+
const raw = fs_1.default.readFileSync(yamlPath, 'utf-8');
|
|
248
|
+
const parsed = (0, yaml_1.parse)(raw);
|
|
249
|
+
const validated = configFileSchema.parse(parsed);
|
|
250
|
+
const srv = validated.server ?? {};
|
|
251
|
+
const globalAuthor = validated.author ?? AUTHOR_DEFAULT;
|
|
252
|
+
// Server-level: model + embedding
|
|
253
|
+
const serverModel = resolveModel(srv.model, MODEL_DEFAULTS);
|
|
254
|
+
const serverEmbedding = resolveEmbedding(srv.embedding, EMBEDDING_DEFAULTS);
|
|
255
|
+
const server = {
|
|
256
|
+
host: srv.host ?? SERVER_DEFAULTS.host,
|
|
257
|
+
port: srv.port ?? SERVER_DEFAULTS.port,
|
|
258
|
+
sessionTimeout: srv.sessionTimeout ?? SERVER_DEFAULTS.sessionTimeout,
|
|
259
|
+
modelsDir: path_1.default.resolve(srv.modelsDir ?? SERVER_DEFAULTS.modelsDir),
|
|
260
|
+
corsOrigins: srv.corsOrigins,
|
|
261
|
+
model: serverModel,
|
|
262
|
+
embedding: serverEmbedding,
|
|
263
|
+
embeddingApi: srv.embeddingApi ? { enabled: !!srv.embeddingApi.enabled, apiKey: srv.embeddingApi.apiKey, maxTexts: srv.embeddingApi.maxTexts ?? 100, maxTextChars: srv.embeddingApi.maxTextChars ?? 10_000 } : undefined,
|
|
264
|
+
defaultAccess: srv.defaultAccess ?? SERVER_DEFAULTS.defaultAccess,
|
|
265
|
+
access: srv.access ?? undefined,
|
|
266
|
+
jwtSecret: srv.jwtSecret,
|
|
267
|
+
accessTokenTtl: srv.accessTokenTtl ?? SERVER_DEFAULTS.accessTokenTtl,
|
|
268
|
+
refreshTokenTtl: srv.refreshTokenTtl ?? SERVER_DEFAULTS.refreshTokenTtl,
|
|
269
|
+
rateLimit: {
|
|
270
|
+
global: srv.rateLimit?.global ?? RATE_LIMIT_DEFAULTS.global,
|
|
271
|
+
search: srv.rateLimit?.search ?? RATE_LIMIT_DEFAULTS.search,
|
|
272
|
+
auth: srv.rateLimit?.auth ?? RATE_LIMIT_DEFAULTS.auth,
|
|
273
|
+
},
|
|
274
|
+
maxFileSize: srv.maxFileSize ?? SERVER_DEFAULTS.maxFileSize,
|
|
275
|
+
exclude: [...SERVER_DEFAULTS.exclude, ...parseExclude(srv.exclude)],
|
|
276
|
+
};
|
|
277
|
+
// Users
|
|
278
|
+
const users = {};
|
|
279
|
+
if (validated.users) {
|
|
280
|
+
for (const [id, raw] of Object.entries(validated.users)) {
|
|
281
|
+
users[id] = { name: raw.name, email: raw.email, apiKey: raw.apiKey, passwordHash: raw.passwordHash };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const projects = new Map();
|
|
285
|
+
for (const [id, raw] of Object.entries(validated.projects)) {
|
|
286
|
+
const projectDir = path_1.default.resolve(raw.projectDir);
|
|
287
|
+
const graphMemory = raw.graphMemory
|
|
288
|
+
? path_1.default.resolve(projectDir, raw.graphMemory)
|
|
289
|
+
: path_1.default.join(projectDir, '.graph-memory');
|
|
290
|
+
const projectModel = resolveModel(raw.model, serverModel);
|
|
291
|
+
const projectEmbedding = resolveEmbedding(raw.embedding, serverEmbedding);
|
|
292
|
+
// Exclude accumulates: server + project
|
|
293
|
+
const projectExclude = [...server.exclude, ...parseExclude(raw.exclude)];
|
|
294
|
+
const rawGraphs = raw.graphs ?? {};
|
|
295
|
+
const graphConfigs = {};
|
|
296
|
+
for (const gn of exports.GRAPH_NAMES) {
|
|
297
|
+
const gc = rawGraphs[gn];
|
|
298
|
+
// Exclude accumulates: server + project + graph
|
|
299
|
+
const graphExclude = [...projectExclude, ...parseExclude(gc?.exclude)];
|
|
300
|
+
graphConfigs[gn] = {
|
|
301
|
+
enabled: gc?.enabled ?? true,
|
|
302
|
+
include: gc?.include ?? (gn === 'docs' ? PROJECT_DEFAULTS.docsInclude : gn === 'code' ? PROJECT_DEFAULTS.codeInclude : undefined),
|
|
303
|
+
exclude: graphExclude,
|
|
304
|
+
model: resolveModel(gc?.model, projectModel),
|
|
305
|
+
embedding: resolveEmbedding(gc?.embedding, projectEmbedding),
|
|
306
|
+
access: gc?.access ?? undefined,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
projects.set(id, {
|
|
310
|
+
projectDir,
|
|
311
|
+
graphMemory,
|
|
312
|
+
exclude: projectExclude,
|
|
313
|
+
chunkDepth: raw.chunkDepth ?? PROJECT_DEFAULTS.chunkDepth,
|
|
314
|
+
maxFileSize: raw.maxFileSize ?? -1,
|
|
315
|
+
model: projectModel,
|
|
316
|
+
embedding: projectEmbedding,
|
|
317
|
+
graphConfigs,
|
|
318
|
+
author: raw.author ?? globalAuthor,
|
|
319
|
+
access: raw.access ?? undefined,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// --- Workspaces ---
|
|
323
|
+
const workspaces = new Map();
|
|
324
|
+
if (validated.workspaces) {
|
|
325
|
+
for (const [wsId, raw] of Object.entries(validated.workspaces)) {
|
|
326
|
+
for (const projId of raw.projects) {
|
|
327
|
+
if (!projects.has(projId)) {
|
|
328
|
+
throw new Error(`Workspace "${wsId}" references unknown project "${projId}"`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const firstProject = projects.get(raw.projects[0]);
|
|
332
|
+
const graphMemory = raw.graphMemory
|
|
333
|
+
? path_1.default.resolve(raw.graphMemory)
|
|
334
|
+
: path_1.default.join(firstProject.projectDir, '.graph-memory', 'workspace');
|
|
335
|
+
const mirrorDir = raw.mirrorDir
|
|
336
|
+
? path_1.default.resolve(raw.mirrorDir)
|
|
337
|
+
: graphMemory;
|
|
338
|
+
const wsModel = resolveModel(raw.model, serverModel);
|
|
339
|
+
const wsEmbedding = resolveEmbedding(raw.embedding, serverEmbedding);
|
|
340
|
+
// Exclude accumulates: server + workspace
|
|
341
|
+
const wsExclude = [...server.exclude, ...parseExclude(raw.exclude)];
|
|
342
|
+
const rawGraphs = raw.graphs ?? {};
|
|
343
|
+
const WS_GRAPH_NAMES = ['knowledge', 'tasks', 'skills'];
|
|
344
|
+
const graphConfigs = {};
|
|
345
|
+
for (const gn of WS_GRAPH_NAMES) {
|
|
346
|
+
const gc = rawGraphs[gn];
|
|
347
|
+
graphConfigs[gn] = {
|
|
348
|
+
enabled: gc?.enabled ?? true,
|
|
349
|
+
exclude: [...wsExclude, ...parseExclude(gc?.exclude)],
|
|
350
|
+
model: resolveModel(gc?.model, wsModel),
|
|
351
|
+
embedding: resolveEmbedding(gc?.embedding, wsEmbedding),
|
|
352
|
+
access: gc?.access ?? undefined,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
workspaces.set(wsId, {
|
|
356
|
+
projects: raw.projects,
|
|
357
|
+
graphMemory,
|
|
358
|
+
mirrorDir,
|
|
359
|
+
model: wsModel,
|
|
360
|
+
embedding: wsEmbedding,
|
|
361
|
+
graphConfigs,
|
|
362
|
+
author: raw.author ?? globalAuthor,
|
|
363
|
+
access: raw.access ?? undefined,
|
|
364
|
+
maxFileSize: raw.maxFileSize,
|
|
365
|
+
exclude: wsExclude,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// --- Post-parse: inject workspace-level settings into member projects ---
|
|
370
|
+
const wsForProject = new Map();
|
|
371
|
+
for (const ws of workspaces.values()) {
|
|
372
|
+
for (const pid of ws.projects)
|
|
373
|
+
wsForProject.set(pid, ws);
|
|
374
|
+
}
|
|
375
|
+
for (const [pid, proj] of projects) {
|
|
376
|
+
const ws = wsForProject.get(pid);
|
|
377
|
+
// maxFileSize: project → workspace → server
|
|
378
|
+
if (proj.maxFileSize === -1) {
|
|
379
|
+
proj.maxFileSize = ws?.maxFileSize ?? server.maxFileSize;
|
|
380
|
+
}
|
|
381
|
+
// Inject workspace excludes into project + graph excludes
|
|
382
|
+
if (ws) {
|
|
383
|
+
const wsExtra = ws.exclude.filter(e => !server.exclude.includes(e)); // ws-only patterns
|
|
384
|
+
if (wsExtra.length > 0) {
|
|
385
|
+
proj.exclude = [...proj.exclude, ...wsExtra];
|
|
386
|
+
for (const gn of exports.GRAPH_NAMES) {
|
|
387
|
+
proj.graphConfigs[gn].exclude = [...proj.graphConfigs[gn].exclude, ...wsExtra];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return { author: globalAuthor, server, users, projects, workspaces };
|
|
393
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseCodeFile = parseCodeFile;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const languages_1 = require("../../lib/parsers/languages");
|
|
10
|
+
const file_lang_1 = require("../../graphs/file-lang");
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Import resolution — replaces ts-morph's getModuleSpecifierSourceFile()
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const RESOLVE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'];
|
|
15
|
+
/** Resolve a relative import specifier to an absolute file path, or null. */
|
|
16
|
+
function resolveRelativeImport(fromFile, specifier) {
|
|
17
|
+
const dir = path_1.default.dirname(fromFile);
|
|
18
|
+
const base = path_1.default.resolve(dir, specifier);
|
|
19
|
+
// Exact match (e.g. './foo.ts')
|
|
20
|
+
if (hasFile(base))
|
|
21
|
+
return base;
|
|
22
|
+
// Try adding extensions (e.g. './foo' → './foo.ts')
|
|
23
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
24
|
+
const candidate = base + ext;
|
|
25
|
+
if (hasFile(candidate))
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
// Try index files (e.g. './foo' → './foo/index.ts')
|
|
29
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
30
|
+
const candidate = path_1.default.join(base, 'index' + ext);
|
|
31
|
+
if (hasFile(candidate))
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function hasFile(p) {
|
|
37
|
+
try {
|
|
38
|
+
return fs_1.default.statSync(p).isFile();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Main parser
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
async function parseCodeFile(absolutePath, codeDir, mtime) {
|
|
48
|
+
const fileId = path_1.default.relative(codeDir, absolutePath);
|
|
49
|
+
// Determine language from file extension
|
|
50
|
+
const ext = path_1.default.extname(absolutePath);
|
|
51
|
+
const language = (0, file_lang_1.getLanguage)(ext);
|
|
52
|
+
if (!language || !(0, languages_1.isLanguageSupported)(language)) {
|
|
53
|
+
// Unsupported language — return file-only node, no symbols
|
|
54
|
+
return {
|
|
55
|
+
fileId,
|
|
56
|
+
mtime,
|
|
57
|
+
nodes: [{
|
|
58
|
+
id: fileId,
|
|
59
|
+
attrs: makeFileAttrs(fileId, '', '', 1, mtime),
|
|
60
|
+
}],
|
|
61
|
+
edges: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const source = fs_1.default.readFileSync(absolutePath, 'utf-8');
|
|
65
|
+
const rootNode = await (0, languages_1.parseSource)(source, language);
|
|
66
|
+
if (!rootNode) {
|
|
67
|
+
return {
|
|
68
|
+
fileId,
|
|
69
|
+
mtime,
|
|
70
|
+
nodes: [{
|
|
71
|
+
id: fileId,
|
|
72
|
+
attrs: makeFileAttrs(fileId, '', '', 1, mtime),
|
|
73
|
+
}],
|
|
74
|
+
edges: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const mapper = (0, languages_1.getMapper)(language);
|
|
78
|
+
const symbols = mapper.extractSymbols(rootNode);
|
|
79
|
+
const edgeInfos = mapper.extractEdges(rootNode);
|
|
80
|
+
const imports = mapper.extractImports(rootNode);
|
|
81
|
+
const nodes = [];
|
|
82
|
+
const edges = [];
|
|
83
|
+
const fileNodeId = fileId;
|
|
84
|
+
// --- File root node ---
|
|
85
|
+
const fileDocComment = extractFileDocComment(rootNode);
|
|
86
|
+
const importSummary = buildImportSummary(rootNode);
|
|
87
|
+
const lastLine = (rootNode.endPosition?.row ?? 0) + 1;
|
|
88
|
+
nodes.push({
|
|
89
|
+
id: fileNodeId,
|
|
90
|
+
attrs: makeFileAttrs(fileId, fileDocComment, importSummary, lastLine, mtime),
|
|
91
|
+
});
|
|
92
|
+
// --- Symbols ---
|
|
93
|
+
for (const sym of symbols) {
|
|
94
|
+
if (!sym.name)
|
|
95
|
+
continue;
|
|
96
|
+
const symbolId = makeId(fileId, sym.name);
|
|
97
|
+
nodes.push({
|
|
98
|
+
id: symbolId,
|
|
99
|
+
attrs: {
|
|
100
|
+
kind: sym.kind,
|
|
101
|
+
fileId,
|
|
102
|
+
name: sym.name,
|
|
103
|
+
signature: sym.signature,
|
|
104
|
+
docComment: sym.docComment,
|
|
105
|
+
body: sym.body,
|
|
106
|
+
startLine: sym.startLine,
|
|
107
|
+
endLine: sym.endLine,
|
|
108
|
+
isExported: sym.isExported,
|
|
109
|
+
embedding: [],
|
|
110
|
+
fileEmbedding: [],
|
|
111
|
+
mtime,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
edges.push({ from: fileNodeId, to: symbolId, attrs: { kind: 'contains' } });
|
|
115
|
+
// Child symbols (e.g. methods)
|
|
116
|
+
if (sym.children) {
|
|
117
|
+
for (const child of sym.children) {
|
|
118
|
+
if (!child.name)
|
|
119
|
+
continue;
|
|
120
|
+
const childId = makeId(fileId, sym.name, child.name);
|
|
121
|
+
nodes.push({
|
|
122
|
+
id: childId,
|
|
123
|
+
attrs: {
|
|
124
|
+
kind: child.kind,
|
|
125
|
+
fileId,
|
|
126
|
+
name: child.name,
|
|
127
|
+
signature: child.signature,
|
|
128
|
+
docComment: child.docComment,
|
|
129
|
+
body: child.body,
|
|
130
|
+
startLine: child.startLine,
|
|
131
|
+
endLine: child.endLine,
|
|
132
|
+
isExported: child.isExported,
|
|
133
|
+
embedding: [],
|
|
134
|
+
fileEmbedding: [],
|
|
135
|
+
mtime,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
edges.push({ from: symbolId, to: childId, attrs: { kind: 'contains' } });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// --- Extends / implements edges ---
|
|
143
|
+
for (const edge of edgeInfos) {
|
|
144
|
+
const fromId = makeId(fileId, edge.fromName);
|
|
145
|
+
const toId = makeId(fileId, edge.toName);
|
|
146
|
+
edges.push({ from: fromId, to: toId, attrs: { kind: edge.kind } });
|
|
147
|
+
}
|
|
148
|
+
// --- Import edges: file → imported file ---
|
|
149
|
+
for (const imp of imports) {
|
|
150
|
+
const targetAbsolute = resolveRelativeImport(absolutePath, imp.specifier);
|
|
151
|
+
if (!targetAbsolute)
|
|
152
|
+
continue;
|
|
153
|
+
const targetRel = path_1.default.relative(codeDir, targetAbsolute);
|
|
154
|
+
if (targetRel.startsWith('..') || path_1.default.isAbsolute(targetRel))
|
|
155
|
+
continue;
|
|
156
|
+
const targetFileId = path_1.default.relative(codeDir, targetAbsolute);
|
|
157
|
+
if (targetFileId !== fileNodeId) {
|
|
158
|
+
edges.push({
|
|
159
|
+
from: fileNodeId,
|
|
160
|
+
to: targetFileId,
|
|
161
|
+
attrs: { kind: 'imports' },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { fileId, mtime, nodes, edges };
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Helpers
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
function makeId(fileId, ...parts) {
|
|
171
|
+
return [fileId, ...parts].join('::');
|
|
172
|
+
}
|
|
173
|
+
function makeFileAttrs(fileId, docComment, importSummary, lastLine, mtime) {
|
|
174
|
+
return {
|
|
175
|
+
kind: 'file',
|
|
176
|
+
fileId,
|
|
177
|
+
name: path_1.default.basename(fileId),
|
|
178
|
+
signature: fileId,
|
|
179
|
+
docComment,
|
|
180
|
+
body: importSummary,
|
|
181
|
+
startLine: 1,
|
|
182
|
+
endLine: lastLine,
|
|
183
|
+
isExported: false,
|
|
184
|
+
embedding: [],
|
|
185
|
+
fileEmbedding: [],
|
|
186
|
+
mtime,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Extract the file-level doc comment (first JSDoc comment before any declaration).
|
|
191
|
+
*/
|
|
192
|
+
function extractFileDocComment(rootNode) {
|
|
193
|
+
for (const child of rootNode.children) {
|
|
194
|
+
if (child.type === 'comment' && child.text.startsWith('/**')) {
|
|
195
|
+
return child.text.trim();
|
|
196
|
+
}
|
|
197
|
+
// Stop at first non-comment node
|
|
198
|
+
if (child.type !== 'comment')
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
return '';
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Build a summary of import statements.
|
|
205
|
+
*/
|
|
206
|
+
function buildImportSummary(rootNode) {
|
|
207
|
+
const imports = [];
|
|
208
|
+
for (const child of rootNode.children) {
|
|
209
|
+
if (child.type === 'import_statement') {
|
|
210
|
+
imports.push(child.text.trim());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return imports.join('\n');
|
|
214
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractSymbols = extractSymbols;
|
|
4
|
+
const languages_1 = require("../../lib/parsers/languages");
|
|
5
|
+
/** Map of common code fence language tags to language names used by the registry. */
|
|
6
|
+
const TAG_TO_LANGUAGE = {
|
|
7
|
+
ts: 'typescript',
|
|
8
|
+
typescript: 'typescript',
|
|
9
|
+
tsx: 'typescript',
|
|
10
|
+
js: 'javascript',
|
|
11
|
+
javascript: 'javascript',
|
|
12
|
+
jsx: 'javascript',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Extract top-level symbol names from a code block using tree-sitter.
|
|
16
|
+
* Returns [] for unsupported languages or on parse failure.
|
|
17
|
+
*/
|
|
18
|
+
async function extractSymbols(code, language) {
|
|
19
|
+
const lang = TAG_TO_LANGUAGE[language.toLowerCase()];
|
|
20
|
+
if (!lang || !(0, languages_1.isLanguageSupported)(lang))
|
|
21
|
+
return [];
|
|
22
|
+
try {
|
|
23
|
+
const rootNode = await (0, languages_1.parseSource)(code, lang);
|
|
24
|
+
if (!rootNode)
|
|
25
|
+
return [];
|
|
26
|
+
const mapper = (0, languages_1.getMapper)(lang);
|
|
27
|
+
const symbols = mapper.extractSymbols(rootNode);
|
|
28
|
+
return symbols.map(s => s.name).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|