@graphmemory/server 1.3.0 → 1.3.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/dist/api/index.js +4 -4
- package/dist/api/rest/code.js +2 -1
- package/dist/api/rest/docs.js +2 -1
- package/dist/api/rest/embed.js +8 -1
- package/dist/api/rest/index.js +4 -3
- package/dist/api/rest/knowledge.js +4 -2
- package/dist/api/rest/skills.js +2 -1
- package/dist/api/rest/tasks.js +2 -1
- package/dist/api/rest/validation.js +41 -40
- package/dist/api/rest/websocket.js +24 -7
- package/dist/api/tools/knowledge/add-attachment.js +2 -1
- package/dist/api/tools/skills/add-attachment.js +2 -1
- package/dist/api/tools/tasks/add-attachment.js +2 -1
- package/dist/cli/index.js +5 -4
- package/dist/cli/indexer.js +2 -1
- package/dist/graphs/attachment-types.js +5 -0
- package/dist/graphs/code.js +34 -8
- package/dist/graphs/docs.js +5 -3
- package/dist/graphs/file-index.js +5 -3
- package/dist/graphs/knowledge.js +11 -4
- package/dist/graphs/skill.js +12 -5
- package/dist/graphs/task.js +12 -5
- package/dist/lib/defaults.js +78 -0
- package/dist/lib/embedder.js +11 -12
- package/dist/lib/embedding-codec.js +3 -5
- package/dist/lib/graph-persistence.js +68 -0
- package/dist/lib/mirror-watcher.js +4 -3
- package/dist/lib/parsers/docs.js +2 -1
- package/dist/lib/parsers/languages/typescript.js +34 -17
- package/dist/lib/project-manager.js +7 -1
- package/dist/lib/search/bm25.js +5 -4
- package/dist/lib/search/code.js +2 -1
- package/dist/lib/search/docs.js +2 -1
- package/dist/lib/search/file-index.js +2 -1
- package/dist/lib/search/files.js +3 -2
- package/dist/lib/search/knowledge.js +2 -1
- package/dist/lib/search/skills.js +2 -1
- package/dist/lib/search/tasks.js +2 -1
- package/package.json +5 -2
package/dist/graphs/docs.js
CHANGED
|
@@ -21,6 +21,8 @@ const docs_1 = require("../lib/search/docs");
|
|
|
21
21
|
const files_1 = require("../lib/search/files");
|
|
22
22
|
const bm25_1 = require("../lib/search/bm25");
|
|
23
23
|
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
24
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
25
|
+
const defaults_1 = require("../lib/defaults");
|
|
24
26
|
function createGraph() {
|
|
25
27
|
return new graphology_1.DirectedGraph({ multi: false, allowSelfLoops: false });
|
|
26
28
|
}
|
|
@@ -108,7 +110,7 @@ function getFileMtime(graph, fileId) {
|
|
|
108
110
|
return 0;
|
|
109
111
|
return graph.getNodeAttribute(nodes[0], 'mtime');
|
|
110
112
|
}
|
|
111
|
-
function listFiles(graph, filter, limit =
|
|
113
|
+
function listFiles(graph, filter, limit = defaults_1.LIST_LIMIT_SMALL) {
|
|
112
114
|
const files = new Map();
|
|
113
115
|
const lowerFilter = filter?.toLowerCase();
|
|
114
116
|
graph.forEachNode((_, attrs) => {
|
|
@@ -153,10 +155,10 @@ function loadGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
153
155
|
if (fresh)
|
|
154
156
|
return graph;
|
|
155
157
|
const file = path_1.default.join(graphMemory, 'docs.json');
|
|
156
|
-
|
|
158
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
159
|
+
if (!data)
|
|
157
160
|
return graph;
|
|
158
161
|
try {
|
|
159
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
160
162
|
const stored = data.embeddingModel;
|
|
161
163
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
162
164
|
process.stderr.write(`[graph] Embedding config changed, re-indexing docs graph\n`);
|
|
@@ -20,6 +20,8 @@ const file_lang_1 = require("../graphs/file-lang");
|
|
|
20
20
|
const manager_types_1 = require("../graphs/manager-types");
|
|
21
21
|
const file_index_1 = require("../lib/search/file-index");
|
|
22
22
|
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
23
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
24
|
+
const defaults_1 = require("../lib/defaults");
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
24
26
|
// CRUD
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
@@ -148,7 +150,7 @@ function getFileEntryMtime(graph, filePath) {
|
|
|
148
150
|
* Otherwise returns all file nodes matching the filters.
|
|
149
151
|
*/
|
|
150
152
|
function listAllFiles(graph, options = {}) {
|
|
151
|
-
const { directory, extension, language, filter, limit =
|
|
153
|
+
const { directory, extension, language, filter, limit = defaults_1.LIST_LIMIT_LARGE } = options;
|
|
152
154
|
const lowerFilter = filter?.toLowerCase();
|
|
153
155
|
const results = [];
|
|
154
156
|
if (directory !== undefined) {
|
|
@@ -261,10 +263,10 @@ function loadFileIndexGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
261
263
|
if (fresh)
|
|
262
264
|
return graph;
|
|
263
265
|
const file = path_1.default.join(graphMemory, 'file-index.json');
|
|
264
|
-
|
|
266
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
267
|
+
if (!data)
|
|
265
268
|
return graph;
|
|
266
269
|
try {
|
|
267
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
268
270
|
const stored = data.embeddingModel;
|
|
269
271
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
270
272
|
process.stderr.write(`[file-index] Embedding config changed, re-indexing file index\n`);
|
package/dist/graphs/knowledge.js
CHANGED
|
@@ -29,6 +29,8 @@ const knowledge_1 = require("../lib/search/knowledge");
|
|
|
29
29
|
const bm25_1 = require("../lib/search/bm25");
|
|
30
30
|
const file_mirror_1 = require("../lib/file-mirror");
|
|
31
31
|
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
32
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
33
|
+
const defaults_1 = require("../lib/defaults");
|
|
32
34
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
33
35
|
const file_import_1 = require("../lib/file-import");
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
@@ -162,7 +164,7 @@ function getNote(graph, noteId) {
|
|
|
162
164
|
return { id: noteId, ...graph.getNodeAttributes(noteId) };
|
|
163
165
|
}
|
|
164
166
|
/** List notes with optional filter (substring in title/id) and tag filter. Excludes proxy nodes. */
|
|
165
|
-
function listNotes(graph, filter, tag, limit =
|
|
167
|
+
function listNotes(graph, filter, tag, limit = defaults_1.LIST_LIMIT_SMALL) {
|
|
166
168
|
const lowerFilter = filter?.toLowerCase();
|
|
167
169
|
const lowerTag = tag?.toLowerCase();
|
|
168
170
|
const results = [];
|
|
@@ -179,7 +181,7 @@ function listNotes(graph, filter, tag, limit = 20) {
|
|
|
179
181
|
if (!attrs.tags.some(t => t.toLowerCase() === lowerTag))
|
|
180
182
|
return;
|
|
181
183
|
}
|
|
182
|
-
results.push({ id, title: attrs.title, content: attrs.content.slice(0,
|
|
184
|
+
results.push({ id, title: attrs.title, content: attrs.content.slice(0, defaults_1.CONTENT_PREVIEW_LEN), tags: attrs.tags, updatedAt: attrs.updatedAt });
|
|
183
185
|
});
|
|
184
186
|
return results
|
|
185
187
|
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
@@ -349,10 +351,10 @@ function loadKnowledgeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
349
351
|
if (fresh)
|
|
350
352
|
return graph;
|
|
351
353
|
const file = path_1.default.join(graphMemory, 'knowledge.json');
|
|
352
|
-
|
|
354
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
355
|
+
if (!data)
|
|
353
356
|
return graph;
|
|
354
357
|
try {
|
|
355
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
356
358
|
const stored = data.embeddingModel;
|
|
357
359
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
358
360
|
process.stderr.write(`[knowledge-graph] Embedding config changed, re-indexing knowledge graph\n`);
|
|
@@ -601,6 +603,11 @@ class KnowledgeGraphManager {
|
|
|
601
603
|
return null;
|
|
602
604
|
if (!this._graph.hasNode(noteId) || isProxy(this._graph, noteId))
|
|
603
605
|
return null;
|
|
606
|
+
if (data.length > attachment_types_1.MAX_ATTACHMENT_SIZE)
|
|
607
|
+
return null;
|
|
608
|
+
const existing = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, noteId));
|
|
609
|
+
if (existing.length >= attachment_types_1.MAX_ATTACHMENTS_PER_ENTITY)
|
|
610
|
+
return null;
|
|
604
611
|
const safe = (0, file_mirror_1.sanitizeFilename)(filename);
|
|
605
612
|
if (!safe)
|
|
606
613
|
return null;
|
package/dist/graphs/skill.js
CHANGED
|
@@ -31,6 +31,8 @@ const skills_1 = require("../lib/search/skills");
|
|
|
31
31
|
const bm25_1 = require("../lib/search/bm25");
|
|
32
32
|
const file_mirror_1 = require("../lib/file-mirror");
|
|
33
33
|
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
34
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
35
|
+
const defaults_1 = require("../lib/defaults");
|
|
34
36
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
35
37
|
const file_import_1 = require("../lib/file-import");
|
|
36
38
|
// ---------------------------------------------------------------------------
|
|
@@ -290,7 +292,7 @@ function getSkill(graph, skillId) {
|
|
|
290
292
|
}
|
|
291
293
|
/** List skills with optional filters. Excludes proxy nodes. */
|
|
292
294
|
function listSkills(graph, opts = {}) {
|
|
293
|
-
const { source, tag, filter, limit =
|
|
295
|
+
const { source, tag, filter, limit = defaults_1.LIST_LIMIT_LARGE } = opts;
|
|
294
296
|
const lowerFilter = filter?.toLowerCase();
|
|
295
297
|
const lowerTag = tag?.toLowerCase();
|
|
296
298
|
const results = [];
|
|
@@ -310,7 +312,7 @@ function listSkills(graph, opts = {}) {
|
|
|
310
312
|
results.push({
|
|
311
313
|
id,
|
|
312
314
|
title: attrs.title,
|
|
313
|
-
description: attrs.description?.slice(0,
|
|
315
|
+
description: attrs.description?.slice(0, defaults_1.CONTENT_PREVIEW_LEN),
|
|
314
316
|
steps: attrs.steps,
|
|
315
317
|
triggers: attrs.triggers,
|
|
316
318
|
inputHints: attrs.inputHints,
|
|
@@ -493,10 +495,10 @@ function loadSkillGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
493
495
|
if (fresh)
|
|
494
496
|
return graph;
|
|
495
497
|
const file = path_1.default.join(graphMemory, 'skills.json');
|
|
496
|
-
|
|
498
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
499
|
+
if (!data)
|
|
497
500
|
return graph;
|
|
498
501
|
try {
|
|
499
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
500
502
|
const stored = data.embeddingModel;
|
|
501
503
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
502
504
|
process.stderr.write(`[skill-graph] Embedding config changed, re-indexing skill graph\n`);
|
|
@@ -829,10 +831,15 @@ class SkillGraphManager {
|
|
|
829
831
|
return null;
|
|
830
832
|
if (!this._graph.hasNode(skillId) || isProxy(this._graph, skillId))
|
|
831
833
|
return null;
|
|
834
|
+
if (data.length > attachment_types_1.MAX_ATTACHMENT_SIZE)
|
|
835
|
+
return null;
|
|
836
|
+
const entityDir = path_1.default.join(dir, skillId);
|
|
837
|
+
const existing = (0, attachment_types_1.scanAttachments)(entityDir);
|
|
838
|
+
if (existing.length >= attachment_types_1.MAX_ATTACHMENTS_PER_ENTITY)
|
|
839
|
+
return null;
|
|
832
840
|
const safe = (0, file_mirror_1.sanitizeFilename)(filename);
|
|
833
841
|
if (!safe)
|
|
834
842
|
return null;
|
|
835
|
-
const entityDir = path_1.default.join(dir, skillId);
|
|
836
843
|
(0, file_mirror_1.writeAttachment)(dir, skillId, safe, data);
|
|
837
844
|
this.mirrorTracker?.recordWrite(path_1.default.join(entityDir, 'attachments', safe));
|
|
838
845
|
(0, file_mirror_1.mirrorAttachmentEvent)(entityDir, 'add', safe);
|
package/dist/graphs/task.js
CHANGED
|
@@ -31,6 +31,8 @@ const tasks_1 = require("../lib/search/tasks");
|
|
|
31
31
|
const bm25_1 = require("../lib/search/bm25");
|
|
32
32
|
const file_mirror_1 = require("../lib/file-mirror");
|
|
33
33
|
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
34
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
35
|
+
const defaults_1 = require("../lib/defaults");
|
|
34
36
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
35
37
|
const file_import_1 = require("../lib/file-import");
|
|
36
38
|
// ---------------------------------------------------------------------------
|
|
@@ -301,7 +303,7 @@ function getTask(graph, taskId) {
|
|
|
301
303
|
}
|
|
302
304
|
/** List tasks with optional filters. Excludes proxy nodes. */
|
|
303
305
|
function listTasks(graph, opts = {}) {
|
|
304
|
-
const { status, priority, tag, filter, assignee, limit =
|
|
306
|
+
const { status, priority, tag, filter, assignee, limit = defaults_1.LIST_LIMIT_LARGE } = opts;
|
|
305
307
|
const lowerFilter = filter?.toLowerCase();
|
|
306
308
|
const lowerTag = tag?.toLowerCase();
|
|
307
309
|
const results = [];
|
|
@@ -325,7 +327,7 @@ function listTasks(graph, opts = {}) {
|
|
|
325
327
|
results.push({
|
|
326
328
|
id,
|
|
327
329
|
title: attrs.title,
|
|
328
|
-
description: attrs.description?.slice(0,
|
|
330
|
+
description: attrs.description?.slice(0, defaults_1.CONTENT_PREVIEW_LEN),
|
|
329
331
|
status: attrs.status,
|
|
330
332
|
priority: attrs.priority,
|
|
331
333
|
tags: attrs.tags,
|
|
@@ -514,10 +516,10 @@ function loadTaskGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
514
516
|
if (fresh)
|
|
515
517
|
return graph;
|
|
516
518
|
const file = path_1.default.join(graphMemory, 'tasks.json');
|
|
517
|
-
|
|
519
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
520
|
+
if (!data)
|
|
518
521
|
return graph;
|
|
519
522
|
try {
|
|
520
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
521
523
|
const stored = data.embeddingModel;
|
|
522
524
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
523
525
|
process.stderr.write(`[task-graph] Embedding config changed, re-indexing task graph\n`);
|
|
@@ -800,10 +802,15 @@ class TaskGraphManager {
|
|
|
800
802
|
return null;
|
|
801
803
|
if (!this._graph.hasNode(taskId) || isProxy(this._graph, taskId))
|
|
802
804
|
return null;
|
|
805
|
+
if (data.length > attachment_types_1.MAX_ATTACHMENT_SIZE)
|
|
806
|
+
return null;
|
|
807
|
+
const entityDir = path_1.default.join(dir, taskId);
|
|
808
|
+
const existing = (0, attachment_types_1.scanAttachments)(entityDir);
|
|
809
|
+
if (existing.length >= attachment_types_1.MAX_ATTACHMENTS_PER_ENTITY)
|
|
810
|
+
return null;
|
|
803
811
|
const safe = (0, file_mirror_1.sanitizeFilename)(filename);
|
|
804
812
|
if (!safe)
|
|
805
813
|
return null;
|
|
806
|
-
const entityDir = path_1.default.join(dir, taskId);
|
|
807
814
|
(0, file_mirror_1.writeAttachment)(dir, taskId, safe, data);
|
|
808
815
|
this.mirrorTracker?.recordWrite(path_1.default.join(entityDir, 'attachments', safe));
|
|
809
816
|
(0, file_mirror_1.mirrorAttachmentEvent)(entityDir, 'add', safe);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Central named constants for all tunable values.
|
|
4
|
+
* Import from here instead of hardcoding magic numbers.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.WIKI_MAX_DEPTH = exports.DEFAULT_EMBEDDING_CACHE_SIZE = exports.MIRROR_MTIME_TOLERANCE_MS = exports.MIRROR_MAX_ENTRIES = exports.MIRROR_STALE_MS = exports.ERROR_BODY_LIMIT = exports.GRACEFUL_SHUTDOWN_TIMEOUT_MS = exports.SESSION_SWEEP_INTERVAL_MS = exports.RATE_LIMIT_WINDOW_MS = exports.REMOTE_BASE_DELAY_MS = exports.REMOTE_MAX_RETRIES = exports.WS_DEBOUNCE_MS = exports.AUTO_SAVE_INTERVAL_MS = exports.MAX_PASSWORD_LEN = exports.MAX_ATTACHMENT_FILENAME_LEN = exports.MAX_PROJECT_ID_LEN = exports.MAX_LINK_KIND_LEN = exports.MAX_TARGET_NODE_ID_LEN = exports.MAX_SKILL_TRIGGERS_COUNT = exports.MAX_SKILL_TRIGGER_LEN = exports.MAX_SKILL_STEPS_COUNT = exports.MAX_SKILL_STEP_LEN = exports.MAX_ASSIGNEE_LEN = exports.MAX_DESCRIPTION_LEN = exports.MAX_SEARCH_TOP_K = exports.MAX_SEARCH_QUERY_LEN = exports.MAX_TAGS_COUNT = exports.MAX_TAG_LEN = exports.MAX_NOTE_CONTENT_LEN = exports.MAX_TITLE_LEN = exports.INDEXER_PREVIEW_LEN = exports.CONTENT_PREVIEW_LEN = exports.LIST_LIMIT_LARGE = exports.LIST_LIMIT_SMALL = exports.SIGNATURE_MAX_LEN = exports.MAX_UPLOAD_SIZE = exports.MAX_BODY_SIZE = exports.BM25_BODY_MAX_CHARS = exports.FILE_SEARCH_TOP_K = exports.SEARCH_MIN_SCORE_FILES = exports.SEARCH_MIN_SCORE_CODE = exports.SEARCH_MIN_SCORE = exports.SEARCH_BFS_DECAY = exports.SEARCH_MAX_RESULTS = exports.SEARCH_BFS_DEPTH = exports.SEARCH_TOP_K = exports.RRF_K = exports.BM25_IDF_OFFSET = exports.BM25_B = exports.BM25_K1 = void 0;
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Search — BM25, RRF, BFS
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
exports.BM25_K1 = 1.2;
|
|
12
|
+
exports.BM25_B = 0.75;
|
|
13
|
+
exports.BM25_IDF_OFFSET = 0.5;
|
|
14
|
+
exports.RRF_K = 60;
|
|
15
|
+
exports.SEARCH_TOP_K = 5;
|
|
16
|
+
exports.SEARCH_BFS_DEPTH = 1;
|
|
17
|
+
exports.SEARCH_MAX_RESULTS = 20;
|
|
18
|
+
exports.SEARCH_BFS_DECAY = 0.8;
|
|
19
|
+
exports.SEARCH_MIN_SCORE = 0.5;
|
|
20
|
+
exports.SEARCH_MIN_SCORE_CODE = 0.3;
|
|
21
|
+
exports.SEARCH_MIN_SCORE_FILES = 0.3;
|
|
22
|
+
exports.FILE_SEARCH_TOP_K = 10;
|
|
23
|
+
exports.BM25_BODY_MAX_CHARS = 2000;
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Limits — sizes, counts, truncation
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
exports.MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
28
|
+
exports.MAX_UPLOAD_SIZE = 50 * 1024 * 1024; // 50 MB (multer)
|
|
29
|
+
exports.SIGNATURE_MAX_LEN = 300;
|
|
30
|
+
exports.LIST_LIMIT_SMALL = 20;
|
|
31
|
+
exports.LIST_LIMIT_LARGE = 50;
|
|
32
|
+
exports.CONTENT_PREVIEW_LEN = 500;
|
|
33
|
+
exports.INDEXER_PREVIEW_LEN = 200;
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Validation — REST API schema limits
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
exports.MAX_TITLE_LEN = 500;
|
|
38
|
+
exports.MAX_NOTE_CONTENT_LEN = 1_000_000;
|
|
39
|
+
exports.MAX_TAG_LEN = 100;
|
|
40
|
+
exports.MAX_TAGS_COUNT = 100;
|
|
41
|
+
exports.MAX_SEARCH_QUERY_LEN = 2000;
|
|
42
|
+
exports.MAX_SEARCH_TOP_K = 500;
|
|
43
|
+
exports.MAX_DESCRIPTION_LEN = 500_000;
|
|
44
|
+
exports.MAX_ASSIGNEE_LEN = 100;
|
|
45
|
+
exports.MAX_SKILL_STEP_LEN = 10_000;
|
|
46
|
+
exports.MAX_SKILL_STEPS_COUNT = 100;
|
|
47
|
+
exports.MAX_SKILL_TRIGGER_LEN = 500;
|
|
48
|
+
exports.MAX_SKILL_TRIGGERS_COUNT = 50;
|
|
49
|
+
exports.MAX_TARGET_NODE_ID_LEN = 500;
|
|
50
|
+
exports.MAX_LINK_KIND_LEN = 100;
|
|
51
|
+
exports.MAX_PROJECT_ID_LEN = 200;
|
|
52
|
+
exports.MAX_ATTACHMENT_FILENAME_LEN = 255;
|
|
53
|
+
exports.MAX_PASSWORD_LEN = 256;
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Timing — intervals, retries, timeouts
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
exports.AUTO_SAVE_INTERVAL_MS = 30_000;
|
|
58
|
+
exports.WS_DEBOUNCE_MS = 1000;
|
|
59
|
+
exports.REMOTE_MAX_RETRIES = 3;
|
|
60
|
+
exports.REMOTE_BASE_DELAY_MS = 200;
|
|
61
|
+
exports.RATE_LIMIT_WINDOW_MS = 60_000;
|
|
62
|
+
exports.SESSION_SWEEP_INTERVAL_MS = 60_000;
|
|
63
|
+
exports.GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
64
|
+
exports.ERROR_BODY_LIMIT = 500;
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Mirror
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
exports.MIRROR_STALE_MS = 10_000;
|
|
69
|
+
exports.MIRROR_MAX_ENTRIES = 10_000;
|
|
70
|
+
exports.MIRROR_MTIME_TOLERANCE_MS = 100;
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Embedder
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
exports.DEFAULT_EMBEDDING_CACHE_SIZE = 10_000;
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Parser
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
exports.WIKI_MAX_DEPTH = 10;
|
package/dist/lib/embedder.js
CHANGED
|
@@ -12,10 +12,11 @@ exports.cosineSimilarity = cosineSimilarity;
|
|
|
12
12
|
const transformers_1 = require("@huggingface/transformers");
|
|
13
13
|
const fs_1 = __importDefault(require("fs"));
|
|
14
14
|
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const defaults_1 = require("../lib/defaults");
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// LRU cache for embedding vectors (avoids re-computing identical texts)
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
|
-
const DEFAULT_CACHE_SIZE =
|
|
19
|
+
const DEFAULT_CACHE_SIZE = defaults_1.DEFAULT_EMBEDDING_CACHE_SIZE;
|
|
19
20
|
class LruCache {
|
|
20
21
|
maxSize;
|
|
21
22
|
map = new Map();
|
|
@@ -97,26 +98,24 @@ async function loadModel(model, embedding, modelsDir, name = 'default') {
|
|
|
97
98
|
// ---------------------------------------------------------------------------
|
|
98
99
|
// Remote embedding HTTP client
|
|
99
100
|
// ---------------------------------------------------------------------------
|
|
100
|
-
const REMOTE_MAX_RETRIES = 3;
|
|
101
|
-
const REMOTE_BASE_DELAY_MS = 200;
|
|
102
101
|
async function remoteEmbed(url, texts, apiKey) {
|
|
103
102
|
const headers = { 'Content-Type': 'application/json' };
|
|
104
103
|
if (apiKey)
|
|
105
104
|
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
106
105
|
const body = JSON.stringify({ texts });
|
|
107
|
-
for (let attempt = 0; attempt < REMOTE_MAX_RETRIES; attempt++) {
|
|
106
|
+
for (let attempt = 0; attempt < defaults_1.REMOTE_MAX_RETRIES; attempt++) {
|
|
108
107
|
let resp;
|
|
109
108
|
try {
|
|
110
109
|
resp = await fetch(url, { method: 'POST', headers, body });
|
|
111
110
|
}
|
|
112
111
|
catch (err) {
|
|
113
112
|
// Network error — retry
|
|
114
|
-
if (attempt < REMOTE_MAX_RETRIES - 1) {
|
|
115
|
-
const delay = REMOTE_BASE_DELAY_MS * 2 ** attempt;
|
|
113
|
+
if (attempt < defaults_1.REMOTE_MAX_RETRIES - 1) {
|
|
114
|
+
const delay = defaults_1.REMOTE_BASE_DELAY_MS * 2 ** attempt;
|
|
116
115
|
await new Promise(r => setTimeout(r, delay));
|
|
117
116
|
continue;
|
|
118
117
|
}
|
|
119
|
-
throw new Error(`Remote embed network error after ${REMOTE_MAX_RETRIES} attempts: ${err}`);
|
|
118
|
+
throw new Error(`Remote embed network error after ${defaults_1.REMOTE_MAX_RETRIES} attempts: ${err}`);
|
|
120
119
|
}
|
|
121
120
|
if (resp.ok) {
|
|
122
121
|
const data = await resp.json();
|
|
@@ -124,17 +123,17 @@ async function remoteEmbed(url, texts, apiKey) {
|
|
|
124
123
|
}
|
|
125
124
|
// Client errors (4xx) — don't retry
|
|
126
125
|
if (resp.status < 500) {
|
|
127
|
-
const respBody = (await resp.text()).slice(0,
|
|
126
|
+
const respBody = (await resp.text()).slice(0, defaults_1.ERROR_BODY_LIMIT);
|
|
128
127
|
throw new Error(`Remote embed failed (${resp.status}): ${respBody}`);
|
|
129
128
|
}
|
|
130
129
|
// Server errors (5xx) — retry
|
|
131
|
-
if (attempt < REMOTE_MAX_RETRIES - 1) {
|
|
132
|
-
const delay = REMOTE_BASE_DELAY_MS * 2 ** attempt;
|
|
130
|
+
if (attempt < defaults_1.REMOTE_MAX_RETRIES - 1) {
|
|
131
|
+
const delay = defaults_1.REMOTE_BASE_DELAY_MS * 2 ** attempt;
|
|
133
132
|
await new Promise(r => setTimeout(r, delay));
|
|
134
133
|
continue;
|
|
135
134
|
}
|
|
136
|
-
const respBody = (await resp.text()).slice(0,
|
|
137
|
-
throw new Error(`Remote embed failed after ${REMOTE_MAX_RETRIES} attempts (${resp.status}): ${respBody}`);
|
|
135
|
+
const respBody = (await resp.text()).slice(0, defaults_1.ERROR_BODY_LIMIT);
|
|
136
|
+
throw new Error(`Remote embed failed after ${defaults_1.REMOTE_MAX_RETRIES} attempts (${resp.status}): ${respBody}`);
|
|
138
137
|
}
|
|
139
138
|
throw new Error('Remote embed: unreachable');
|
|
140
139
|
}
|
|
@@ -5,17 +5,15 @@
|
|
|
5
5
|
* Backwards compatible: detects old format (number[]) on load.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.float32ToBase64 = float32ToBase64;
|
|
9
|
+
exports.base64ToFloat32 = base64ToFloat32;
|
|
8
10
|
exports.compressEmbeddings = compressEmbeddings;
|
|
9
11
|
exports.decompressEmbeddings = decompressEmbeddings;
|
|
10
12
|
const EMBEDDING_FIELDS = ['embedding', 'fileEmbedding'];
|
|
11
13
|
/** Convert a number[] to a Base64-encoded Float32Array. */
|
|
12
14
|
function float32ToBase64(arr) {
|
|
13
15
|
const f32 = new Float32Array(arr);
|
|
14
|
-
|
|
15
|
-
let binary = '';
|
|
16
|
-
for (let i = 0; i < bytes.length; i++)
|
|
17
|
-
binary += String.fromCharCode(bytes[i]);
|
|
18
|
-
return Buffer.from(binary, 'binary').toString('base64');
|
|
16
|
+
return Buffer.from(f32.buffer).toString('base64');
|
|
19
17
|
}
|
|
20
18
|
/** Convert a Base64-encoded Float32Array back to number[]. */
|
|
21
19
|
function base64ToFloat32(b64) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readJsonWithTmpFallback = readJsonWithTmpFallback;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
/**
|
|
39
|
+
* Try to read and parse a JSON file, falling back to .tmp if main file
|
|
40
|
+
* is missing or corrupted (recovery from interrupted save).
|
|
41
|
+
*/
|
|
42
|
+
function readJsonWithTmpFallback(file) {
|
|
43
|
+
const tmp = file + '.tmp';
|
|
44
|
+
// If main file missing but .tmp exists, recover it
|
|
45
|
+
if (!fs.existsSync(file) && fs.existsSync(tmp)) {
|
|
46
|
+
try {
|
|
47
|
+
fs.renameSync(tmp, file);
|
|
48
|
+
}
|
|
49
|
+
catch { /* ignore */ }
|
|
50
|
+
}
|
|
51
|
+
if (fs.existsSync(file)) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Main file corrupted — try .tmp as fallback
|
|
57
|
+
if (fs.existsSync(tmp)) {
|
|
58
|
+
try {
|
|
59
|
+
const data = JSON.parse(fs.readFileSync(tmp, 'utf-8'));
|
|
60
|
+
process.stderr.write(`[graph] Recovered from .tmp file: ${file}\n`);
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
catch { /* .tmp also bad */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
@@ -45,6 +45,7 @@ const chokidar_1 = __importDefault(require("chokidar"));
|
|
|
45
45
|
const file_import_1 = require("./file-import");
|
|
46
46
|
const events_log_1 = require("./events-log");
|
|
47
47
|
const frontmatter_1 = require("./frontmatter");
|
|
48
|
+
const defaults_1 = require("../lib/defaults");
|
|
48
49
|
/**
|
|
49
50
|
* Tracks recent mirror writes to suppress re-import (feedback loop prevention).
|
|
50
51
|
* When mirrorNote/mirrorTask writes a file, the watcher will fire —
|
|
@@ -53,8 +54,8 @@ const frontmatter_1 = require("./frontmatter");
|
|
|
53
54
|
class MirrorWriteTracker {
|
|
54
55
|
/** Map from filePath → { mtimeMs (for comparison), recordedAt (for eviction) } */
|
|
55
56
|
recentWrites = new Map();
|
|
56
|
-
static STALE_MS =
|
|
57
|
-
static MAX_ENTRIES =
|
|
57
|
+
static STALE_MS = defaults_1.MIRROR_STALE_MS;
|
|
58
|
+
static MAX_ENTRIES = defaults_1.MIRROR_MAX_ENTRIES;
|
|
58
59
|
/** Called by mirrorNote/mirrorTask after writing a file. */
|
|
59
60
|
recordWrite(filePath) {
|
|
60
61
|
try {
|
|
@@ -76,7 +77,7 @@ class MirrorWriteTracker {
|
|
|
76
77
|
const stat = fs.statSync(filePath, { throwIfNoEntry: false });
|
|
77
78
|
if (!stat)
|
|
78
79
|
return false;
|
|
79
|
-
if (Math.abs(stat.mtimeMs - recorded.mtimeMs) <
|
|
80
|
+
if (Math.abs(stat.mtimeMs - recorded.mtimeMs) < defaults_1.MIRROR_MTIME_TOLERANCE_MS) {
|
|
80
81
|
this.recentWrites.delete(filePath);
|
|
81
82
|
return true;
|
|
82
83
|
}
|
package/dist/lib/parsers/docs.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.clearWikiIndexCache = clearWikiIndexCache;
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const codeblock_1 = require("../../lib/parsers/codeblock");
|
|
11
|
+
const defaults_1 = require("../../lib/defaults");
|
|
11
12
|
// Parse a markdown file into chunks split by headings
|
|
12
13
|
async function parseFile(content, absolutePath, projectDir, chunkDepth) {
|
|
13
14
|
const fileId = path_1.default.relative(projectDir, absolutePath);
|
|
@@ -179,7 +180,7 @@ function getWikiIndex(projectDir) {
|
|
|
179
180
|
if (_wikiIndex.has(projectDir))
|
|
180
181
|
return _wikiIndex.get(projectDir);
|
|
181
182
|
const index = new Map();
|
|
182
|
-
const MAX_DEPTH =
|
|
183
|
+
const MAX_DEPTH = defaults_1.WIKI_MAX_DEPTH;
|
|
183
184
|
function walk(dir, depth) {
|
|
184
185
|
if (depth >= MAX_DEPTH)
|
|
185
186
|
return;
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerTypescript = registerTypescript;
|
|
4
4
|
const registry_1 = require("./registry");
|
|
5
|
+
const defaults_1 = require("../../../lib/defaults");
|
|
5
6
|
/** Get the previous named sibling that is a JSDoc comment. */
|
|
6
7
|
function getDocComment(node) {
|
|
7
|
-
let prev = node.
|
|
8
|
+
let prev = node.previousNamedSibling;
|
|
8
9
|
while (prev && prev.type === 'comment' && !prev.text.startsWith('/**')) {
|
|
9
|
-
prev = prev.
|
|
10
|
+
prev = prev.previousNamedSibling;
|
|
10
11
|
}
|
|
11
12
|
if (prev && prev.type === 'comment' && prev.text.startsWith('/**')) {
|
|
12
13
|
return prev.text.trim();
|
|
@@ -14,28 +15,45 @@ function getDocComment(node) {
|
|
|
14
15
|
return '';
|
|
15
16
|
}
|
|
16
17
|
/** Collapse whitespace and truncate. */
|
|
17
|
-
function truncate(text, maxLen =
|
|
18
|
+
function truncate(text, maxLen = defaults_1.SIGNATURE_MAX_LEN) {
|
|
18
19
|
const collapsed = text.replace(/\s+/g, ' ').trim();
|
|
19
20
|
return collapsed.length > maxLen ? collapsed.slice(0, maxLen) + '…' : collapsed;
|
|
20
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Slice outerNode.text up to where bodyNode begins, using line-based
|
|
24
|
+
* slicing to avoid tree-sitter byte-offset vs JS char-offset mismatch
|
|
25
|
+
* (startIndex is UTF-8 bytes, String.slice uses UTF-16 code units).
|
|
26
|
+
*/
|
|
27
|
+
function sliceBeforeBody(outerNode, bodyNode) {
|
|
28
|
+
const text = outerNode.text ?? '';
|
|
29
|
+
const outerStartRow = outerNode.startPosition.row;
|
|
30
|
+
const bodyStartRow = bodyNode.startPosition.row;
|
|
31
|
+
if (bodyStartRow > outerStartRow) {
|
|
32
|
+
const lines = text.split('\n');
|
|
33
|
+
const relativeRow = bodyStartRow - outerStartRow;
|
|
34
|
+
const beforeBody = lines.slice(0, relativeRow);
|
|
35
|
+
const bodyLine = lines[relativeRow] ?? '';
|
|
36
|
+
const braceIdx = bodyLine.indexOf('{');
|
|
37
|
+
if (braceIdx >= 0)
|
|
38
|
+
beforeBody.push(bodyLine.slice(0, braceIdx));
|
|
39
|
+
return beforeBody.join('\n');
|
|
40
|
+
}
|
|
41
|
+
const braceIdx = text.indexOf('{');
|
|
42
|
+
if (braceIdx > 0)
|
|
43
|
+
return text.slice(0, braceIdx);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
21
46
|
/**
|
|
22
47
|
* Build signature by taking everything before the body node.
|
|
23
|
-
* For code (ASCII-dominated), byte offset ≈ char offset.
|
|
24
48
|
* Falls back to first line if no body found.
|
|
25
49
|
*/
|
|
26
50
|
function buildSignature(outerNode, innerNode) {
|
|
27
51
|
const bodyNode = innerNode.childForFieldName('body');
|
|
28
52
|
const text = outerNode.text ?? '';
|
|
29
|
-
if (!bodyNode)
|
|
30
|
-
// No body (type alias, ambient declaration, etc.) — use full text
|
|
53
|
+
if (!bodyNode)
|
|
31
54
|
return truncate(text);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const headerBytes = bodyNode.startIndex - outerNode.startIndex;
|
|
35
|
-
if (headerBytes > 0) {
|
|
36
|
-
return truncate(text.slice(0, headerBytes));
|
|
37
|
-
}
|
|
38
|
-
return truncate(text.split('\n')[0]);
|
|
55
|
+
const header = sliceBeforeBody(outerNode, bodyNode);
|
|
56
|
+
return truncate(header ?? text.split('\n')[0]);
|
|
39
57
|
}
|
|
40
58
|
/**
|
|
41
59
|
* Build signature for variable declarations.
|
|
@@ -46,10 +64,9 @@ function buildVariableSignature(outerNode, declarator) {
|
|
|
46
64
|
if (value) {
|
|
47
65
|
const valueBody = value.childForFieldName('body');
|
|
48
66
|
if (valueBody) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return truncate(fullText.slice(0, bodyOffset));
|
|
67
|
+
const header = sliceBeforeBody(outerNode, valueBody);
|
|
68
|
+
if (header)
|
|
69
|
+
return truncate(header);
|
|
53
70
|
}
|
|
54
71
|
}
|
|
55
72
|
return truncate(outerNode.text ?? '');
|
|
@@ -13,11 +13,14 @@ const file_index_1 = require("../graphs/file-index");
|
|
|
13
13
|
const task_1 = require("../graphs/task");
|
|
14
14
|
const skill_1 = require("../graphs/skill");
|
|
15
15
|
const indexer_1 = require("../cli/indexer");
|
|
16
|
+
const code_2 = require("../lib/parsers/code");
|
|
17
|
+
const docs_2 = require("../lib/parsers/docs");
|
|
16
18
|
const promise_queue_1 = require("../lib/promise-queue");
|
|
17
19
|
const multi_config_1 = require("../lib/multi-config");
|
|
18
20
|
const mirror_watcher_1 = require("../lib/mirror-watcher");
|
|
19
21
|
const team_1 = require("../lib/team");
|
|
20
22
|
const path_1 = __importDefault(require("path"));
|
|
23
|
+
const defaults_1 = require("../lib/defaults");
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
22
25
|
// ProjectManager
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
@@ -264,6 +267,9 @@ class ProjectManager extends events_1.EventEmitter {
|
|
|
264
267
|
const instance = this.projects.get(id);
|
|
265
268
|
if (!instance)
|
|
266
269
|
throw new Error(`Project "${id}" not found`);
|
|
270
|
+
// Clear parser caches to prevent cross-project leaks in multi-project mode
|
|
271
|
+
(0, code_2.clearPathMappingsCache)();
|
|
272
|
+
(0, docs_2.clearWikiIndexCache)();
|
|
267
273
|
const gc = instance.config.graphConfigs;
|
|
268
274
|
const indexer = (0, indexer_1.createProjectIndexer)(instance.docGraph, instance.codeGraph, {
|
|
269
275
|
projectId: id,
|
|
@@ -358,7 +364,7 @@ class ProjectManager extends events_1.EventEmitter {
|
|
|
358
364
|
/**
|
|
359
365
|
* Start auto-save interval (every intervalMs, save dirty projects).
|
|
360
366
|
*/
|
|
361
|
-
startAutoSave(intervalMs =
|
|
367
|
+
startAutoSave(intervalMs = defaults_1.AUTO_SAVE_INTERVAL_MS) {
|
|
362
368
|
this.autoSaveInterval = setInterval(() => {
|
|
363
369
|
for (const instance of this.projects.values()) {
|
|
364
370
|
if (instance.dirty) {
|