devsmind-mcp 4.0.1 → 4.1.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.md +22 -14
- package/dist/cli/index.js +17 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/integrations/memory-topics.d.ts +12 -7
- package/dist/cli/integrations/memory-topics.js +21 -14
- package/dist/cli/integrations/memory-topics.js.map +1 -1
- package/dist/cli/integrations/memory.d.ts +20 -13
- package/dist/cli/integrations/memory.js +27 -14
- package/dist/cli/integrations/memory.js.map +1 -1
- package/dist/cli/integrations/prompt.d.ts +3 -1
- package/dist/cli/integrations/prompt.js +3 -1
- package/dist/cli/integrations/prompt.js.map +1 -1
- package/dist/cli/integrations/registry.d.ts +39 -13
- package/dist/cli/integrations/registry.js +27 -20
- package/dist/cli/integrations/registry.js.map +1 -1
- package/dist/cli/integrations/skill.d.ts +17 -0
- package/dist/cli/integrations/skill.js +103 -0
- package/dist/cli/integrations/skill.js.map +1 -0
- package/dist/cli/rule.js +7 -1
- package/dist/cli/rule.js.map +1 -1
- package/dist/cli/sync.d.ts +7 -2
- package/dist/cli/sync.js +15 -4
- package/dist/cli/sync.js.map +1 -1
- package/dist/db/database.d.ts +34 -1
- package/dist/db/database.js +70 -5
- package/dist/db/database.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -1
- package/dist/mcp/server.js +234 -10
- package/dist/mcp/server.js.map +1 -1
- package/dist/utils/edit.d.ts +14 -0
- package/dist/utils/edit.js +64 -0
- package/dist/utils/edit.js.map +1 -1
- package/package.json +1 -1
package/dist/db/database.d.ts
CHANGED
|
@@ -295,6 +295,8 @@ export declare class DevMindDatabase {
|
|
|
295
295
|
nodes: number;
|
|
296
296
|
connections: number;
|
|
297
297
|
history: number;
|
|
298
|
+
vectors: number;
|
|
299
|
+
workflows: number;
|
|
298
300
|
};
|
|
299
301
|
vacuum(): void;
|
|
300
302
|
/**
|
|
@@ -970,6 +972,27 @@ export declare class DevMindDatabase {
|
|
|
970
972
|
syncFromDisk(onProgress?: (phase: string, done: number, total: number) => void): void;
|
|
971
973
|
/** Escape LIKE metacharacters so a path is matched literally (use with ESCAPE '\\'). */
|
|
972
974
|
private likeEscape;
|
|
975
|
+
/**
|
|
976
|
+
* True when `targetPath` (a computed `graph/`/`vectors/` JSON path) does NOT land strictly
|
|
977
|
+
* inside `containerDir` as a real file — i.e. writing there would be writing to a directory,
|
|
978
|
+
* not a file inside one. Guards `writeGraphToDisk`/`writeVectorsToDisk` against a malformed
|
|
979
|
+
* node whose `file_path` IS a directory (the workspace root, or a repo root itself): observed
|
|
980
|
+
* in production as an EISDIR crash on `devsmind sync`, because `toRepoRelativePath` collapses
|
|
981
|
+
* to `''` (workspace root) or `'{repo}/'` (a repo root) for those, which `diskRelPath` then
|
|
982
|
+
* turns into `''`/a trailing-slash string/`'..'` depending on which branch collapsed — every
|
|
983
|
+
* one of which makes `path.join(containerDir, diskRelPath)` resolve to `containerDir` itself
|
|
984
|
+
* or some directory already inside it, never a fresh `.json` file. Multiple checks on purpose:
|
|
985
|
+
* the empty/trailing-slash/`..`-relative cases catch it structurally (works even on a brand
|
|
986
|
+
* new brain where nothing exists on disk yet, where an EISDIR would instead be a silent
|
|
987
|
+
* file-where-directory-belongs corruption); the existsSync+isDirectory check is a catch-all
|
|
988
|
+
* for any other collapse shape not foreseen above (existsSync is checked first specifically so
|
|
989
|
+
* a normal not-yet-written target — the common case — never reaches statSync at all). Callers
|
|
990
|
+
* already wrap their whole body in a try/catch that logs and returns, so nothing extra is
|
|
991
|
+
* needed here for a statSync that throws for some other reason (a permission error, a race).
|
|
992
|
+
* `devsmind analyze --fix` deprecates the node actually causing this; this only ever refuses
|
|
993
|
+
* the write, never touches the node.
|
|
994
|
+
*/
|
|
995
|
+
private isDegenerateDiskJsonPath;
|
|
973
996
|
writeGraphToDisk(filePath: string): void;
|
|
974
997
|
/**
|
|
975
998
|
* Mirrors `writeGraphToDisk` exactly (same file-matching logic, same directory shape) but into
|
|
@@ -979,6 +1002,16 @@ export declare class DevMindDatabase {
|
|
|
979
1002
|
* node's vector, so writing one is pure dead weight.
|
|
980
1003
|
*/
|
|
981
1004
|
writeVectorsToDisk(filePath: string): void;
|
|
982
|
-
/**
|
|
1005
|
+
/**
|
|
1006
|
+
* Force-syncs all database nodes, vectors, and workflows to disk JSON files. This is the
|
|
1007
|
+
* write-back half of `devsmind sync` (paired with `syncFromDisk`, which reads `vectors/*.json`
|
|
1008
|
+
* into `node_vectors` — see its comment). Vectors are re-written here for the same reason
|
|
1009
|
+
* graph JSON is: normal operation already keeps `vectors/` current (`writeVectorsToDisk` runs
|
|
1010
|
+
* immediately alongside every embedding write), but `sync` exists precisely for the abnormal
|
|
1011
|
+
* case — recovering a `.devmind` after `vectors/*.json` was deleted/corrupted independently of
|
|
1012
|
+
* the DB, or after a past write silently failed (e.g. the directory-typed file_path bug
|
|
1013
|
+
* `isDegenerateDiskJsonPath` now catches) — so leaving vectors out of the force-resync
|
|
1014
|
+
* defeated half the point of running it.
|
|
1015
|
+
*/
|
|
983
1016
|
syncToDisk(): void;
|
|
984
1017
|
}
|
package/dist/db/database.js
CHANGED
|
@@ -381,7 +381,7 @@ class DevMindDatabase {
|
|
|
381
381
|
/* istanbul ignore next -- every call site here is a `SELECT COUNT(*) AS c FROM ...`,
|
|
382
382
|
which always returns exactly one row; `row` can only be undefined if this helper is
|
|
383
383
|
ever repurposed for a query that can return zero rows. Kept as a real guard, not
|
|
384
|
-
because today's
|
|
384
|
+
because today's call sites can hit it. */
|
|
385
385
|
return row ? row.c : 0;
|
|
386
386
|
}
|
|
387
387
|
catch {
|
|
@@ -392,6 +392,8 @@ class DevMindDatabase {
|
|
|
392
392
|
nodes: one('SELECT COUNT(*) AS c FROM nodes WHERE deprecated = 0'),
|
|
393
393
|
connections: one('SELECT COUNT(*) AS c FROM node_connections'),
|
|
394
394
|
history: one('SELECT COUNT(*) AS c FROM history'),
|
|
395
|
+
vectors: one('SELECT COUNT(*) AS c FROM node_vectors'),
|
|
396
|
+
workflows: one('SELECT COUNT(*) AS c FROM workflows'),
|
|
395
397
|
};
|
|
396
398
|
}
|
|
397
399
|
vacuum() {
|
|
@@ -2797,9 +2799,21 @@ class DevMindDatabase {
|
|
|
2797
2799
|
if (node.file_path) {
|
|
2798
2800
|
const paths = node.file_path.split(',').map(p => p.trim()).filter(Boolean);
|
|
2799
2801
|
if (paths.length > 0) {
|
|
2802
|
+
// "Missing" also covers a file_path that resolves to a DIRECTORY rather than a real
|
|
2803
|
+
// file — plain fs.existsSync() alone says a directory "exists", so a node corrupted
|
|
2804
|
+
// this way (its file_path IS a repo or workspace root, from some earlier bug) sailed
|
|
2805
|
+
// through this check indefinitely; it only ever surfaced as an EISDIR crash when
|
|
2806
|
+
// writeGraphToDisk/writeVectorsToDisk later tried to write a JSON file at that same
|
|
2807
|
+
// path. statSync().isFile() catches both "doesn't exist" and "exists but isn't a file"
|
|
2808
|
+
// in one check — a node deprecated for either reason has no real source to hold anyway.
|
|
2800
2809
|
const allMissing = paths.every(p => {
|
|
2801
2810
|
const resolvedPath = path.isAbsolute(p) ? p : path.resolve(workspaceRoot, p);
|
|
2802
|
-
|
|
2811
|
+
try {
|
|
2812
|
+
return !fs.statSync(resolvedPath).isFile();
|
|
2813
|
+
}
|
|
2814
|
+
catch {
|
|
2815
|
+
return true;
|
|
2816
|
+
}
|
|
2803
2817
|
});
|
|
2804
2818
|
if (allMissing)
|
|
2805
2819
|
missingFile.push(node);
|
|
@@ -3351,6 +3365,34 @@ class DevMindDatabase {
|
|
|
3351
3365
|
likeEscape(s) {
|
|
3352
3366
|
return s.replace(/[\\%_]/g, ch => '\\' + ch);
|
|
3353
3367
|
}
|
|
3368
|
+
/**
|
|
3369
|
+
* True when `targetPath` (a computed `graph/`/`vectors/` JSON path) does NOT land strictly
|
|
3370
|
+
* inside `containerDir` as a real file — i.e. writing there would be writing to a directory,
|
|
3371
|
+
* not a file inside one. Guards `writeGraphToDisk`/`writeVectorsToDisk` against a malformed
|
|
3372
|
+
* node whose `file_path` IS a directory (the workspace root, or a repo root itself): observed
|
|
3373
|
+
* in production as an EISDIR crash on `devsmind sync`, because `toRepoRelativePath` collapses
|
|
3374
|
+
* to `''` (workspace root) or `'{repo}/'` (a repo root) for those, which `diskRelPath` then
|
|
3375
|
+
* turns into `''`/a trailing-slash string/`'..'` depending on which branch collapsed — every
|
|
3376
|
+
* one of which makes `path.join(containerDir, diskRelPath)` resolve to `containerDir` itself
|
|
3377
|
+
* or some directory already inside it, never a fresh `.json` file. Multiple checks on purpose:
|
|
3378
|
+
* the empty/trailing-slash/`..`-relative cases catch it structurally (works even on a brand
|
|
3379
|
+
* new brain where nothing exists on disk yet, where an EISDIR would instead be a silent
|
|
3380
|
+
* file-where-directory-belongs corruption); the existsSync+isDirectory check is a catch-all
|
|
3381
|
+
* for any other collapse shape not foreseen above (existsSync is checked first specifically so
|
|
3382
|
+
* a normal not-yet-written target — the common case — never reaches statSync at all). Callers
|
|
3383
|
+
* already wrap their whole body in a try/catch that logs and returns, so nothing extra is
|
|
3384
|
+
* needed here for a statSync that throws for some other reason (a permission error, a race).
|
|
3385
|
+
* `devsmind analyze --fix` deprecates the node actually causing this; this only ever refuses
|
|
3386
|
+
* the write, never touches the node.
|
|
3387
|
+
*/
|
|
3388
|
+
isDegenerateDiskJsonPath(containerDir, diskRelPath, targetPath) {
|
|
3389
|
+
if (!diskRelPath || diskRelPath.endsWith('/') || diskRelPath.endsWith('\\'))
|
|
3390
|
+
return true;
|
|
3391
|
+
const relToContainer = path.relative(containerDir, targetPath);
|
|
3392
|
+
if (relToContainer === '' || relToContainer.startsWith('..'))
|
|
3393
|
+
return true;
|
|
3394
|
+
return fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory();
|
|
3395
|
+
}
|
|
3354
3396
|
writeGraphToDisk(filePath) {
|
|
3355
3397
|
try {
|
|
3356
3398
|
if (!filePath)
|
|
@@ -3361,7 +3403,12 @@ class DevMindDatabase {
|
|
|
3361
3403
|
const repoRelPath = this.toRepoRelativePath(absPath);
|
|
3362
3404
|
// E.g., "{harrir-web}/app/page.tsx" -> "graph/harrir-web/app/page.json"
|
|
3363
3405
|
const diskRelPath = repoRelPath.replace(/^\{([^}]+)\}/, '$1').replace(/\.[^/.]+$/, '.json');
|
|
3364
|
-
const
|
|
3406
|
+
const graphDir = path.join(workspaceRoot, 'graph');
|
|
3407
|
+
const graphJsonPath = path.join(graphDir, diskRelPath);
|
|
3408
|
+
if (this.isDegenerateDiskJsonPath(graphDir, diskRelPath, graphJsonPath)) {
|
|
3409
|
+
console.warn(`⚠️ Skipped writing graph JSON: a node's file_path resolves to a directory, not a file (${absPath}) — run \`devsmind analyze --fix\` to clean it up.`);
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3365
3412
|
// Get all nodes in this file (active AND deprecated). A node's file_path is either
|
|
3366
3413
|
// exactly this absolute path, or (for the rare node spanning multiple files) a ", "-joined
|
|
3367
3414
|
// list containing it. We anchor on the FULL absolute path with ", " boundaries and escape
|
|
@@ -3442,7 +3489,14 @@ class DevMindDatabase {
|
|
|
3442
3489
|
const absPath = (0, config_1.canonicalizePath)(filePath);
|
|
3443
3490
|
const repoRelPath = this.toRepoRelativePath(absPath);
|
|
3444
3491
|
const diskRelPath = repoRelPath.replace(/^\{([^}]+)\}/, '$1').replace(/\.[^/.]+$/, '.json');
|
|
3445
|
-
const
|
|
3492
|
+
const vectorsDir = path.join(workspaceRoot, 'vectors');
|
|
3493
|
+
const vectorsJsonPath = path.join(vectorsDir, diskRelPath);
|
|
3494
|
+
// Same guard as writeGraphToDisk — see isDegenerateDiskJsonPath's comment for why this
|
|
3495
|
+
// happens and what it means.
|
|
3496
|
+
if (this.isDegenerateDiskJsonPath(vectorsDir, diskRelPath, vectorsJsonPath)) {
|
|
3497
|
+
console.warn(`⚠️ Skipped writing vectors JSON: a node's file_path resolves to a directory, not a file (${absPath}) — run \`devsmind analyze --fix\` to clean it up.`);
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3446
3500
|
const absLower = absPath.toLowerCase();
|
|
3447
3501
|
const absEscLower = this.likeEscape(absPath).toLowerCase();
|
|
3448
3502
|
const stmt = this.db.prepare(`
|
|
@@ -3474,7 +3528,17 @@ class DevMindDatabase {
|
|
|
3474
3528
|
console.warn('⚠️ SQLite warning: Failed to write vectors JSON to disk:', err);
|
|
3475
3529
|
}
|
|
3476
3530
|
}
|
|
3477
|
-
/**
|
|
3531
|
+
/**
|
|
3532
|
+
* Force-syncs all database nodes, vectors, and workflows to disk JSON files. This is the
|
|
3533
|
+
* write-back half of `devsmind sync` (paired with `syncFromDisk`, which reads `vectors/*.json`
|
|
3534
|
+
* into `node_vectors` — see its comment). Vectors are re-written here for the same reason
|
|
3535
|
+
* graph JSON is: normal operation already keeps `vectors/` current (`writeVectorsToDisk` runs
|
|
3536
|
+
* immediately alongside every embedding write), but `sync` exists precisely for the abnormal
|
|
3537
|
+
* case — recovering a `.devmind` after `vectors/*.json` was deleted/corrupted independently of
|
|
3538
|
+
* the DB, or after a past write silently failed (e.g. the directory-typed file_path bug
|
|
3539
|
+
* `isDegenerateDiskJsonPath` now catches) — so leaving vectors out of the force-resync
|
|
3540
|
+
* defeated half the point of running it.
|
|
3541
|
+
*/
|
|
3478
3542
|
syncToDisk() {
|
|
3479
3543
|
try {
|
|
3480
3544
|
const rows = this.db.prepare('SELECT DISTINCT file_path FROM nodes').all();
|
|
@@ -3488,6 +3552,7 @@ class DevMindDatabase {
|
|
|
3488
3552
|
}
|
|
3489
3553
|
for (const filePath of filePaths) {
|
|
3490
3554
|
this.writeGraphToDisk(filePath);
|
|
3555
|
+
this.writeVectorsToDisk(filePath);
|
|
3491
3556
|
}
|
|
3492
3557
|
const workflowRows = this.db.prepare('SELECT id FROM workflows').all();
|
|
3493
3558
|
for (const row of workflowRows) {
|