@coreyuan/vector-mind 1.0.39 → 1.0.41
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 +47 -6
- package/dist/index.js +587 -71
- package/dist/index.js.map +1 -1
- package/dist/rtk-shim.d.ts +2 -0
- package/dist/rtk-shim.js +255 -0
- package/dist/rtk-shim.js.map +1 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
|
|
|
16
16
|
import { BUILTIN_CONVENTIONS } from "./builtin-conventions.js";
|
|
17
17
|
import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS, BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS, BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS, BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
|
|
18
18
|
const SERVER_NAME = "vector-mind";
|
|
19
|
-
const SERVER_VERSION = "1.0.
|
|
19
|
+
const SERVER_VERSION = "1.0.41";
|
|
20
20
|
const rootFromEnv = process.env.VECTORMIND_ROOT?.trim() ?? "";
|
|
21
21
|
const prettyJsonOutput = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_PRETTY_JSON ?? "").trim().toLowerCase());
|
|
22
22
|
const debugLogEnabled = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_DEBUG_LOG ?? "").trim().toLowerCase());
|
|
@@ -164,9 +164,13 @@ let getConventionByKeyStmt;
|
|
|
164
164
|
let insertConventionStmt;
|
|
165
165
|
let updateConventionByIdStmt;
|
|
166
166
|
let listConventionsStmt;
|
|
167
|
+
let upsertDecisionStmt;
|
|
168
|
+
let getDecisionByKeyStmt;
|
|
169
|
+
let listCurrentDecisionsStmt;
|
|
167
170
|
let upsertProjectSummaryStmt;
|
|
168
171
|
let getProjectSummaryStmt;
|
|
169
172
|
let listRecentNotesStmt;
|
|
173
|
+
let getLatestChangeIntentForFileStmt;
|
|
170
174
|
let deleteFileChunkItemsStmt;
|
|
171
175
|
let getEmbeddingMetaStmt;
|
|
172
176
|
let upsertEmbeddingStmt;
|
|
@@ -556,6 +560,89 @@ function shouldIgnoreDbFilePath(filePath) {
|
|
|
556
560
|
return false;
|
|
557
561
|
return pathHasIgnoredSegments(filePath);
|
|
558
562
|
}
|
|
563
|
+
function isProbablyGitRepository() {
|
|
564
|
+
try {
|
|
565
|
+
return fs.existsSync(path.join(projectRoot, ".git"));
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function normalizeGitStatusPath(raw) {
|
|
572
|
+
const first = raw.split("\0")[0] ?? "";
|
|
573
|
+
return first.trim().replace(/\\/g, "/").replace(/^"(.*)"$/, "$1");
|
|
574
|
+
}
|
|
575
|
+
function collectGitPendingChanges(limit) {
|
|
576
|
+
if (limit <= 0 || !isProbablyGitRepository())
|
|
577
|
+
return [];
|
|
578
|
+
const git = spawnSync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=normal"], {
|
|
579
|
+
cwd: projectRoot,
|
|
580
|
+
encoding: "utf8",
|
|
581
|
+
timeout: 5000,
|
|
582
|
+
windowsHide: true,
|
|
583
|
+
maxBuffer: 2_000_000,
|
|
584
|
+
});
|
|
585
|
+
if (git.error || git.status !== 0 || !git.stdout)
|
|
586
|
+
return [];
|
|
587
|
+
const parts = git.stdout.split("\0").filter(Boolean);
|
|
588
|
+
const rows = [];
|
|
589
|
+
for (let i = 0; i < parts.length && rows.length < limit; i++) {
|
|
590
|
+
const rec = parts[i] ?? "";
|
|
591
|
+
const status = rec.slice(0, 2);
|
|
592
|
+
let rawPath = rec.slice(3);
|
|
593
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
594
|
+
// Porcelain -z rename/copy records include the destination in the next NUL field.
|
|
595
|
+
rawPath = parts[i + 1] ?? rawPath;
|
|
596
|
+
i += 1;
|
|
597
|
+
}
|
|
598
|
+
const filePath = normalizeGitStatusPath(rawPath);
|
|
599
|
+
if (!filePath || filePath === ".vectormind" || filePath.startsWith(".vectormind/"))
|
|
600
|
+
continue;
|
|
601
|
+
rows.push({
|
|
602
|
+
file_path: filePath,
|
|
603
|
+
last_event: status.includes("D") ? "unlink" : status === "??" ? "add" : "change",
|
|
604
|
+
updated_at: new Date().toISOString(),
|
|
605
|
+
source: "git",
|
|
606
|
+
git_status: status.trim() || "modified",
|
|
607
|
+
file_state_hash: getFileStateHash(filePath) ?? undefined,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return rows;
|
|
611
|
+
}
|
|
612
|
+
function mergePendingWithGit(pending, opts) {
|
|
613
|
+
const byPath = new Map();
|
|
614
|
+
for (const p of pending) {
|
|
615
|
+
if (shouldIgnoreDbFilePath(p.file_path))
|
|
616
|
+
continue;
|
|
617
|
+
byPath.set(p.file_path, { ...p, source: p.source ?? "watcher" });
|
|
618
|
+
}
|
|
619
|
+
const gitRows = collectGitPendingChanges(Math.max(500, opts.offset + opts.limit * 4));
|
|
620
|
+
for (const g of gitRows) {
|
|
621
|
+
const latestSyncedHash = getLatestSyncedFileHash(g.file_path);
|
|
622
|
+
if (latestSyncedHash && g.file_state_hash && latestSyncedHash === g.file_state_hash)
|
|
623
|
+
continue;
|
|
624
|
+
const existing = byPath.get(g.file_path);
|
|
625
|
+
if (!existing) {
|
|
626
|
+
byPath.set(g.file_path, g);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
byPath.set(g.file_path, {
|
|
630
|
+
...existing,
|
|
631
|
+
source: existing.source === "watcher" ? "watcher" : g.source,
|
|
632
|
+
git_status: g.git_status,
|
|
633
|
+
file_state_hash: g.file_state_hash,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
const all = Array.from(byPath.values()).sort((a, b) => {
|
|
637
|
+
const at = Date.parse(a.updated_at) || 0;
|
|
638
|
+
const bt = Date.parse(b.updated_at) || 0;
|
|
639
|
+
if (bt !== at)
|
|
640
|
+
return bt - at;
|
|
641
|
+
return a.file_path.localeCompare(b.file_path);
|
|
642
|
+
});
|
|
643
|
+
const page = all.slice(opts.offset, opts.offset + opts.limit);
|
|
644
|
+
return { total: all.length, page, truncated: all.length > opts.offset + opts.limit };
|
|
645
|
+
}
|
|
559
646
|
function pruneIgnoredPendingChanges() {
|
|
560
647
|
if (!db)
|
|
561
648
|
return;
|
|
@@ -1249,6 +1336,22 @@ const UpsertConventionArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1249
1336
|
content: z.string().min(1),
|
|
1250
1337
|
tags: z.array(z.string().min(1)).optional(),
|
|
1251
1338
|
}));
|
|
1339
|
+
const UpsertDecisionArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1340
|
+
key: z.string().min(1),
|
|
1341
|
+
title: z.string().optional().default(""),
|
|
1342
|
+
content: z.string().min(1),
|
|
1343
|
+
tags: z.array(z.string().min(1)).optional(),
|
|
1344
|
+
supersedes_req_ids: z.array(z.number().int().positive()).optional(),
|
|
1345
|
+
supersedes_memory_ids: z.array(z.number().int().positive()).optional(),
|
|
1346
|
+
related_files: z.array(z.string().min(1)).optional(),
|
|
1347
|
+
}));
|
|
1348
|
+
const SupersedeMemoryArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1349
|
+
superseded_req_ids: z.array(z.number().int().positive()).optional(),
|
|
1350
|
+
superseded_memory_ids: z.array(z.number().int().positive()).optional(),
|
|
1351
|
+
replacement_req_id: z.number().int().positive().optional(),
|
|
1352
|
+
replacement_memory_id: z.number().int().positive().optional(),
|
|
1353
|
+
reason: z.string().min(1),
|
|
1354
|
+
}));
|
|
1252
1355
|
const DEFAULT_PENDING_LIMIT = 10;
|
|
1253
1356
|
const MAX_PENDING_LIMIT = 2000;
|
|
1254
1357
|
const PendingPagingSchema = z.object({
|
|
@@ -1267,11 +1370,14 @@ const DEFAULT_RECENT_REQUIREMENTS = 2;
|
|
|
1267
1370
|
const DEFAULT_RECENT_CHANGES_PER_REQ = 3;
|
|
1268
1371
|
const DEFAULT_RECENT_NOTES = 3;
|
|
1269
1372
|
const DEFAULT_CONVENTIONS_LIMIT = 0;
|
|
1373
|
+
const DEFAULT_DECISIONS_LIMIT = 5;
|
|
1374
|
+
const MAX_DECISIONS_LIMIT = 50;
|
|
1270
1375
|
const BrainDumpLimitsSchema = z.object({
|
|
1271
1376
|
requirements_limit: z.number().int().min(1).max(20).optional().default(DEFAULT_RECENT_REQUIREMENTS),
|
|
1272
1377
|
changes_limit: z.number().int().min(1).max(100).optional().default(DEFAULT_RECENT_CHANGES_PER_REQ),
|
|
1273
1378
|
notes_limit: z.number().int().min(0).max(50).optional().default(DEFAULT_RECENT_NOTES),
|
|
1274
1379
|
conventions_limit: z.number().int().min(0).max(200).optional().default(DEFAULT_CONVENTIONS_LIMIT),
|
|
1380
|
+
decisions_limit: z.number().int().min(0).max(MAX_DECISIONS_LIMIT).optional().default(DEFAULT_DECISIONS_LIMIT),
|
|
1275
1381
|
});
|
|
1276
1382
|
const GetPendingChangesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1277
1383
|
offset: z.number().int().min(0).optional().default(0),
|
|
@@ -1329,7 +1435,7 @@ const InstallRtkArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1329
1435
|
dry_run: z.boolean().optional().default(true),
|
|
1330
1436
|
method: z.enum(["auto", "cargo", "brew", "shell_script"]).optional().default("auto"),
|
|
1331
1437
|
init: z
|
|
1332
|
-
.enum(["none", "global_no_patch", "global_auto_patch", "global_hook_only", "local"])
|
|
1438
|
+
.enum(["none", "global_no_patch", "global_auto_patch", "global_hook_only", "local", "codex_global", "codex_local"])
|
|
1333
1439
|
.optional()
|
|
1334
1440
|
.default("none"),
|
|
1335
1441
|
uninstall_wrong_cargo_rtk: z.boolean().optional().default(false),
|
|
@@ -1346,6 +1452,28 @@ function escapeLike(pattern) {
|
|
|
1346
1452
|
function sha256Hex(input) {
|
|
1347
1453
|
return crypto.createHash("sha256").update(input).digest("hex");
|
|
1348
1454
|
}
|
|
1455
|
+
function getFileStateHash(dbOrAbsPath) {
|
|
1456
|
+
try {
|
|
1457
|
+
const abs = path.isAbsolute(dbOrAbsPath) ? dbOrAbsPath : path.join(projectRoot, dbOrAbsPath);
|
|
1458
|
+
const st = fs.statSync(abs);
|
|
1459
|
+
if (!st.isFile())
|
|
1460
|
+
return sha256Hex(`non-file:${st.mtimeMs}:${st.size}`);
|
|
1461
|
+
if (st.size <= 5_000_000) {
|
|
1462
|
+
return crypto.createHash("sha256").update(fs.readFileSync(abs)).digest("hex");
|
|
1463
|
+
}
|
|
1464
|
+
return sha256Hex(`large:${st.size}:${Math.floor(st.mtimeMs)}`);
|
|
1465
|
+
}
|
|
1466
|
+
catch {
|
|
1467
|
+
return sha256Hex("missing");
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function getLatestSyncedFileHash(dbFilePath) {
|
|
1471
|
+
const row = getLatestChangeIntentForFileStmt?.get(dbFilePath);
|
|
1472
|
+
if (!row)
|
|
1473
|
+
return null;
|
|
1474
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
1475
|
+
return typeof meta.file_state_hash === "string" ? meta.file_state_hash : null;
|
|
1476
|
+
}
|
|
1349
1477
|
function safeJson(value) {
|
|
1350
1478
|
if (value === undefined)
|
|
1351
1479
|
return null;
|
|
@@ -1409,7 +1537,9 @@ function compactChangeLabel(change) {
|
|
|
1409
1537
|
return `change#${change.id} ${change.file_path}: ${oneLine(change.intent_preview, 120)}`;
|
|
1410
1538
|
}
|
|
1411
1539
|
function compactPendingLabel(p) {
|
|
1412
|
-
|
|
1540
|
+
const source = "source" in p && p.source === "git" ? " git" : "";
|
|
1541
|
+
const status = "git_status" in p && p.git_status ? ` ${p.git_status}` : "";
|
|
1542
|
+
return `${p.last_event}${source}${status} ${p.file_path}`;
|
|
1413
1543
|
}
|
|
1414
1544
|
function compactSemanticSearchText(data) {
|
|
1415
1545
|
const lines = [
|
|
@@ -1480,6 +1610,11 @@ function compactBootstrapText(data) {
|
|
|
1480
1610
|
lines.push(`ok ctx ${data.root_source} watcher=${data.watcher_enabled ? (data.watcher_ready ? "ready" : "starting") : "off"} root=${data.project_root}`);
|
|
1481
1611
|
if (data.project_summary)
|
|
1482
1612
|
lines.push(`summary ${compactMemoryLabel(data.project_summary, 140)}`);
|
|
1613
|
+
if (data.decisions.length) {
|
|
1614
|
+
lines.push("current decisions:");
|
|
1615
|
+
for (const d of data.decisions.slice(0, 5))
|
|
1616
|
+
lines.push(`- ${compactMemoryLabel(d, 160)}`);
|
|
1617
|
+
}
|
|
1483
1618
|
if (data.pending_total) {
|
|
1484
1619
|
lines.push(`pending ${data.pending_changes.length}/${data.pending_total}${data.pending_truncated ? " truncated" : ""}: ${data.pending_changes
|
|
1485
1620
|
.slice(0, 8)
|
|
@@ -1523,42 +1658,105 @@ function compactBootstrapText(data) {
|
|
|
1523
1658
|
function compactBrainDumpText(data) {
|
|
1524
1659
|
return compactBootstrapText(data);
|
|
1525
1660
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
1528
|
-
|
|
1661
|
+
function shellQuoteArg(arg) {
|
|
1662
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg))
|
|
1663
|
+
return arg;
|
|
1664
|
+
if (process.platform === "win32")
|
|
1665
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
1666
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
1667
|
+
}
|
|
1668
|
+
function getPackageRtkShimPath() {
|
|
1669
|
+
try {
|
|
1670
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
1671
|
+
const candidate = path.join(currentDir, "rtk-shim.js");
|
|
1672
|
+
if (fs.existsSync(candidate))
|
|
1673
|
+
return candidate;
|
|
1674
|
+
}
|
|
1675
|
+
catch {
|
|
1676
|
+
// import.meta.url may be unavailable only in unexpected runtimes.
|
|
1677
|
+
}
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
function runRtkProbe(spec) {
|
|
1681
|
+
const argsPrefix = spec.execArgsPrefix ?? [];
|
|
1682
|
+
const result = spawnSync(spec.execCommand, [...argsPrefix, "--version"], {
|
|
1529
1683
|
encoding: "utf8",
|
|
1530
|
-
timeout:
|
|
1684
|
+
timeout: 120_000,
|
|
1531
1685
|
windowsHide: true,
|
|
1686
|
+
shell: spec.execShell ?? false,
|
|
1532
1687
|
});
|
|
1533
1688
|
if (result.status === 0) {
|
|
1534
|
-
const gain = spawnSync(
|
|
1535
|
-
encoding: "utf8",
|
|
1536
|
-
timeout: 5000,
|
|
1537
|
-
windowsHide: true,
|
|
1538
|
-
});
|
|
1539
|
-
const whereCommand = process.platform === "win32" ? "where.exe" : "which";
|
|
1540
|
-
const whereResult = spawnSync(whereCommand, [process.platform === "win32" ? "rtk.exe" : "rtk"], {
|
|
1689
|
+
const gain = spawnSync(spec.execCommand, [...argsPrefix, "gain"], {
|
|
1541
1690
|
encoding: "utf8",
|
|
1542
|
-
timeout:
|
|
1691
|
+
timeout: 120_000,
|
|
1543
1692
|
windowsHide: true,
|
|
1693
|
+
shell: spec.execShell ?? false,
|
|
1544
1694
|
});
|
|
1695
|
+
let resolvedPath = spec.path;
|
|
1696
|
+
if (spec.source === "path") {
|
|
1697
|
+
const whereCommand = process.platform === "win32" ? "where.exe" : "which";
|
|
1698
|
+
const whereResult = spawnSync(whereCommand, ["rtk"], {
|
|
1699
|
+
encoding: "utf8",
|
|
1700
|
+
timeout: 2000,
|
|
1701
|
+
windowsHide: true,
|
|
1702
|
+
});
|
|
1703
|
+
resolvedPath = whereResult.status === 0 ? oneLine(whereResult.stdout, 240) : resolvedPath;
|
|
1704
|
+
}
|
|
1545
1705
|
const gainText = `${gain.stdout}${gain.stderr}`.trim();
|
|
1546
1706
|
return {
|
|
1547
1707
|
available: gain.status === 0,
|
|
1548
|
-
command,
|
|
1708
|
+
command: spec.displayCommand,
|
|
1549
1709
|
version: `${result.stdout}${result.stderr}`.trim(),
|
|
1550
1710
|
gain_ok: gain.status === 0,
|
|
1551
1711
|
gain_preview: oneLine(gainText, 240),
|
|
1552
|
-
path:
|
|
1712
|
+
path: resolvedPath,
|
|
1713
|
+
source: spec.source,
|
|
1714
|
+
exec_command: spec.execCommand,
|
|
1715
|
+
exec_args_prefix: argsPrefix,
|
|
1716
|
+
exec_shell: spec.execShell ?? false,
|
|
1553
1717
|
note: gain.status === 0
|
|
1554
|
-
?
|
|
1555
|
-
|
|
1718
|
+
? spec.source === "package_shim"
|
|
1719
|
+
? `Prefer prefixing shell commands with ${spec.displayCommand} for compact outputs. This is VectorMind's bundled RTK shim; first run auto-installs/caches rtk-ai/rtk if needed.`
|
|
1720
|
+
: "Prefer prefixing shell commands with rtk for compact outputs, e.g. rtk git status / rtk npm run build / rtk rg pattern ."
|
|
1721
|
+
: spec.source === "package_shim"
|
|
1722
|
+
? "VectorMind's bundled RTK shim exists, but `gain` failed. Check network/cache or set VECTORMIND_RTK_REAL to an existing rtk-ai/rtk binary."
|
|
1723
|
+
: "An rtk binary exists, but `rtk gain` failed. This may be the wrong rtk project. Use install_rtk with uninstall_wrong_cargo_rtk=true only after confirming it is safe.",
|
|
1556
1724
|
};
|
|
1557
1725
|
}
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
function detectRtk() {
|
|
1729
|
+
const pathProbe = runRtkProbe({
|
|
1730
|
+
source: "path",
|
|
1731
|
+
displayCommand: "rtk",
|
|
1732
|
+
execCommand: "rtk",
|
|
1733
|
+
execShell: process.platform === "win32",
|
|
1734
|
+
});
|
|
1735
|
+
if (pathProbe?.available)
|
|
1736
|
+
return pathProbe;
|
|
1737
|
+
const shimPath = getPackageRtkShimPath();
|
|
1738
|
+
if (shimPath) {
|
|
1739
|
+
const displayCommand = `node ${shellQuoteArg(shimPath)}`;
|
|
1740
|
+
const shimProbe = runRtkProbe({
|
|
1741
|
+
source: "package_shim",
|
|
1742
|
+
displayCommand,
|
|
1743
|
+
execCommand: process.execPath,
|
|
1744
|
+
execArgsPrefix: [shimPath],
|
|
1745
|
+
path: shimPath,
|
|
1746
|
+
});
|
|
1747
|
+
if (shimProbe)
|
|
1748
|
+
return shimProbe;
|
|
1749
|
+
}
|
|
1750
|
+
if (pathProbe)
|
|
1751
|
+
return pathProbe;
|
|
1558
1752
|
return {
|
|
1559
1753
|
available: false,
|
|
1560
|
-
command,
|
|
1561
|
-
|
|
1754
|
+
command: shimPath ? `node ${shellQuoteArg(shimPath)}` : "rtk",
|
|
1755
|
+
path: shimPath ?? undefined,
|
|
1756
|
+
source: shimPath ? "package_shim" : undefined,
|
|
1757
|
+
note: shimPath
|
|
1758
|
+
? "rtk was not found on PATH, and VectorMind's bundled RTK shim could not verify rtk gain. VectorMind compact MCP output still works; check network/cache or set VECTORMIND_RTK_REAL."
|
|
1759
|
+
: "rtk was not found on PATH and the package RTK shim is unavailable. VectorMind compact MCP output still works; install rtk to compact shell command output too.",
|
|
1562
1760
|
};
|
|
1563
1761
|
}
|
|
1564
1762
|
function commandExists(command) {
|
|
@@ -1581,6 +1779,40 @@ function runInstallStep(command, args, timeoutMs) {
|
|
|
1581
1779
|
output: oneLine(output, 1200),
|
|
1582
1780
|
};
|
|
1583
1781
|
}
|
|
1782
|
+
function runDetectedRtkStep(detected, args, timeoutMs) {
|
|
1783
|
+
const execCommand = detected.exec_command ?? "rtk";
|
|
1784
|
+
const argsPrefix = detected.exec_args_prefix ?? [];
|
|
1785
|
+
const result = spawnSync(execCommand, [...argsPrefix, ...args], {
|
|
1786
|
+
encoding: "utf8",
|
|
1787
|
+
timeout: timeoutMs,
|
|
1788
|
+
windowsHide: true,
|
|
1789
|
+
shell: detected.exec_shell ?? (execCommand === "rtk" && process.platform === "win32"),
|
|
1790
|
+
});
|
|
1791
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1792
|
+
return {
|
|
1793
|
+
command: [detected.command, ...args].join(" "),
|
|
1794
|
+
status: result.status,
|
|
1795
|
+
ok: result.status === 0,
|
|
1796
|
+
output: oneLine(output, 1200),
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
function appendRtkInitStep(steps, detected, init, timeoutMs) {
|
|
1800
|
+
if (init === "none")
|
|
1801
|
+
return;
|
|
1802
|
+
if (init === "global_no_patch")
|
|
1803
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--no-patch"], timeoutMs));
|
|
1804
|
+
if (init === "global_auto_patch")
|
|
1805
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--auto-patch"], timeoutMs));
|
|
1806
|
+
if (init === "global_hook_only") {
|
|
1807
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--hook-only", "--no-patch"], timeoutMs));
|
|
1808
|
+
}
|
|
1809
|
+
if (init === "local")
|
|
1810
|
+
steps.push(runDetectedRtkStep(detected, ["init"], timeoutMs));
|
|
1811
|
+
if (init === "codex_global")
|
|
1812
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--codex"], timeoutMs));
|
|
1813
|
+
if (init === "codex_local")
|
|
1814
|
+
steps.push(runDetectedRtkStep(detected, ["init", "--codex"], timeoutMs));
|
|
1815
|
+
}
|
|
1584
1816
|
function chooseRtkInstallMethod(method) {
|
|
1585
1817
|
if (method !== "auto")
|
|
1586
1818
|
return method;
|
|
@@ -1623,6 +1855,10 @@ function buildRtkInstallPlan(args) {
|
|
|
1623
1855
|
commands.push("rtk init -g --hook-only --no-patch");
|
|
1624
1856
|
if (args.init === "local")
|
|
1625
1857
|
commands.push("rtk init");
|
|
1858
|
+
if (args.init === "codex_global")
|
|
1859
|
+
commands.push("rtk init -g --codex");
|
|
1860
|
+
if (args.init === "codex_local")
|
|
1861
|
+
commands.push("rtk init --codex");
|
|
1626
1862
|
if (args.init !== "none") {
|
|
1627
1863
|
notes.push("rtk init may modify Claude/RTK configuration. Use init=none for binary-only installation.");
|
|
1628
1864
|
}
|
|
@@ -1635,6 +1871,9 @@ function installRtk(args) {
|
|
|
1635
1871
|
const notes = [...plan.notes];
|
|
1636
1872
|
if (detectedBefore.available) {
|
|
1637
1873
|
notes.push("rtk is already installed and verified with `rtk gain`; installation skipped.");
|
|
1874
|
+
if (!args.dry_run && args.init !== "none") {
|
|
1875
|
+
appendRtkInitStep(steps, detectedBefore, args.init, args.timeout_ms);
|
|
1876
|
+
}
|
|
1638
1877
|
return {
|
|
1639
1878
|
ok: true,
|
|
1640
1879
|
dry_run: args.dry_run,
|
|
@@ -1682,15 +1921,7 @@ function installRtk(args) {
|
|
|
1682
1921
|
}
|
|
1683
1922
|
const detectedAfterInstall = detectRtk();
|
|
1684
1923
|
if (detectedAfterInstall.available && args.init !== "none") {
|
|
1685
|
-
|
|
1686
|
-
steps.push(runInstallStep("rtk", ["init", "-g", "--no-patch"], args.timeout_ms));
|
|
1687
|
-
if (args.init === "global_auto_patch")
|
|
1688
|
-
steps.push(runInstallStep("rtk", ["init", "-g", "--auto-patch"], args.timeout_ms));
|
|
1689
|
-
if (args.init === "global_hook_only") {
|
|
1690
|
-
steps.push(runInstallStep("rtk", ["init", "-g", "--hook-only", "--no-patch"], args.timeout_ms));
|
|
1691
|
-
}
|
|
1692
|
-
if (args.init === "local")
|
|
1693
|
-
steps.push(runInstallStep("rtk", ["init"], args.timeout_ms));
|
|
1924
|
+
appendRtkInitStep(steps, detectedAfterInstall, args.init, args.timeout_ms);
|
|
1694
1925
|
}
|
|
1695
1926
|
const detectedAfter = detectRtk();
|
|
1696
1927
|
return {
|
|
@@ -1898,6 +2129,88 @@ function dotProduct(a, b) {
|
|
|
1898
2129
|
s += a[i] * b[i];
|
|
1899
2130
|
return s;
|
|
1900
2131
|
}
|
|
2132
|
+
function parseMetadataJson(metadata) {
|
|
2133
|
+
if (!metadata)
|
|
2134
|
+
return {};
|
|
2135
|
+
try {
|
|
2136
|
+
const parsed = JSON.parse(metadata);
|
|
2137
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
2138
|
+
? parsed
|
|
2139
|
+
: {};
|
|
2140
|
+
}
|
|
2141
|
+
catch {
|
|
2142
|
+
return {};
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
function metadataStatus(row) {
|
|
2146
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
2147
|
+
return typeof meta.status === "string" ? meta.status : "";
|
|
2148
|
+
}
|
|
2149
|
+
function isSupersededMemory(row) {
|
|
2150
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
2151
|
+
return meta.superseded === true || meta.status === "superseded";
|
|
2152
|
+
}
|
|
2153
|
+
function semanticRecencyWeight(updatedAt) {
|
|
2154
|
+
if (!updatedAt)
|
|
2155
|
+
return 0;
|
|
2156
|
+
const t = Date.parse(updatedAt.endsWith("Z") ? updatedAt : `${updatedAt}Z`);
|
|
2157
|
+
if (!Number.isFinite(t))
|
|
2158
|
+
return 0;
|
|
2159
|
+
const ageDays = Math.max(0, (Date.now() - t) / 86_400_000);
|
|
2160
|
+
if (ageDays <= 1)
|
|
2161
|
+
return 0.8;
|
|
2162
|
+
if (ageDays <= 7)
|
|
2163
|
+
return 0.45;
|
|
2164
|
+
if (ageDays <= 30)
|
|
2165
|
+
return 0.2;
|
|
2166
|
+
return 0;
|
|
2167
|
+
}
|
|
2168
|
+
function semanticKindWeight(kind) {
|
|
2169
|
+
switch (kind) {
|
|
2170
|
+
case "decision":
|
|
2171
|
+
return 3.5;
|
|
2172
|
+
case "convention":
|
|
2173
|
+
return 2.6;
|
|
2174
|
+
case "project_summary":
|
|
2175
|
+
return 2.2;
|
|
2176
|
+
case "note":
|
|
2177
|
+
return 1.1;
|
|
2178
|
+
case "requirement":
|
|
2179
|
+
return 0.4;
|
|
2180
|
+
case "change_intent":
|
|
2181
|
+
return 0.2;
|
|
2182
|
+
default:
|
|
2183
|
+
return 0;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
function adjustSemanticScore(row, rawScore) {
|
|
2187
|
+
if (isSupersededMemory(row))
|
|
2188
|
+
return rawScore - 1000;
|
|
2189
|
+
let score = rawScore + semanticKindWeight(row.kind) + semanticRecencyWeight(row.updated_at);
|
|
2190
|
+
const status = metadataStatus(row);
|
|
2191
|
+
if (status === "active" || status === "current")
|
|
2192
|
+
score += 1.2;
|
|
2193
|
+
if (row.kind === "change_intent" && row.file_path && shouldIgnoreDbFilePath(row.file_path)) {
|
|
2194
|
+
// Human-synced intent for generated/build/runtime files is often the only durable
|
|
2195
|
+
// "why" for that change. Do not let built-in path ignores hide the decision trail.
|
|
2196
|
+
score += 0.4;
|
|
2197
|
+
}
|
|
2198
|
+
return score;
|
|
2199
|
+
}
|
|
2200
|
+
function filterAndRankSemanticRows(rows, scoreOf, opts) {
|
|
2201
|
+
return rows
|
|
2202
|
+
.map((r) => ({ row: r, score: adjustSemanticScore(r, scoreOf(r)) }))
|
|
2203
|
+
.filter(({ row }) => {
|
|
2204
|
+
if (isSupersededMemory(row))
|
|
2205
|
+
return false;
|
|
2206
|
+
if (shouldIgnoreDbFilePath(row.file_path) && row.kind !== "change_intent")
|
|
2207
|
+
return false;
|
|
2208
|
+
return true;
|
|
2209
|
+
})
|
|
2210
|
+
.sort((a, b) => b.score - a.score)
|
|
2211
|
+
.slice(0, opts.topK)
|
|
2212
|
+
.map(({ row, score }) => toSemanticMatch(row, score, opts.includeContent, opts.previewChars, opts.contentMaxChars));
|
|
2213
|
+
}
|
|
1901
2214
|
function makePreviewText(content, max) {
|
|
1902
2215
|
if (max <= 0)
|
|
1903
2216
|
return "";
|
|
@@ -1972,6 +2285,15 @@ function getConventionPreviews(conventionsLimit, previewChars, contentMaxChars)
|
|
|
1972
2285
|
const stored = listConventionsStmt.all(remaining).map((c) => toMemoryItemPreview(c, false, previewChars, contentMaxChars));
|
|
1973
2286
|
return [...builtin, ...stored];
|
|
1974
2287
|
}
|
|
2288
|
+
function getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars) {
|
|
2289
|
+
if (decisionsLimit <= 0)
|
|
2290
|
+
return [];
|
|
2291
|
+
const rows = listCurrentDecisionsStmt.all(Math.min(MAX_DECISIONS_LIMIT * 4, Math.max(decisionsLimit, decisionsLimit * 4)));
|
|
2292
|
+
return rows
|
|
2293
|
+
.filter((d) => !isSupersededMemory(d))
|
|
2294
|
+
.slice(0, decisionsLimit)
|
|
2295
|
+
.map((d) => toMemoryItemPreview(d, false, previewChars, contentMaxChars));
|
|
2296
|
+
}
|
|
1975
2297
|
function toRequirementPreview(req, includeContent, previewChars, contentMaxChars) {
|
|
1976
2298
|
const context = req.context_data ?? null;
|
|
1977
2299
|
const contextPreview = context ? makePreviewText(context, previewChars) : null;
|
|
@@ -2016,6 +2338,46 @@ function completeAllActiveRequirementMemoryItems() {
|
|
|
2016
2338
|
console.error("[vectormind] failed to complete all active requirement memory items:", err);
|
|
2017
2339
|
}
|
|
2018
2340
|
}
|
|
2341
|
+
function patchMemoryItemMetadata(id, patch) {
|
|
2342
|
+
const row = getMemoryItemByIdStmt.get(id);
|
|
2343
|
+
if (!row)
|
|
2344
|
+
return;
|
|
2345
|
+
const meta = { ...parseMetadataJson(row.metadata_json), ...patch };
|
|
2346
|
+
db.prepare(`UPDATE memory_items SET metadata_json = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(safeJson(meta), id);
|
|
2347
|
+
}
|
|
2348
|
+
function supersedeMemoryItemIds(ids, replacement) {
|
|
2349
|
+
const updated = [];
|
|
2350
|
+
for (const id of Array.from(new Set(ids)).filter((n) => Number.isFinite(n) && n > 0)) {
|
|
2351
|
+
const row = getMemoryItemByIdStmt.get(id);
|
|
2352
|
+
if (!row)
|
|
2353
|
+
continue;
|
|
2354
|
+
patchMemoryItemMetadata(id, {
|
|
2355
|
+
...parseMetadataJson(row.metadata_json),
|
|
2356
|
+
status: "superseded",
|
|
2357
|
+
superseded: true,
|
|
2358
|
+
superseded_at: new Date().toISOString(),
|
|
2359
|
+
superseded_reason: replacement.reason,
|
|
2360
|
+
superseded_by_req_id: replacement.req_id ?? null,
|
|
2361
|
+
superseded_by_memory_id: replacement.memory_id ?? null,
|
|
2362
|
+
superseded_by_decision_id: replacement.decision_id ?? null,
|
|
2363
|
+
});
|
|
2364
|
+
updated.push(id);
|
|
2365
|
+
}
|
|
2366
|
+
return updated;
|
|
2367
|
+
}
|
|
2368
|
+
function supersedeRequirementIds(reqIds, replacement) {
|
|
2369
|
+
const updatedReqs = [];
|
|
2370
|
+
for (const reqId of Array.from(new Set(reqIds)).filter((n) => Number.isFinite(n) && n > 0)) {
|
|
2371
|
+
const info = db.prepare(`UPDATE requirements SET status = 'superseded' WHERE id = ?`).run(reqId);
|
|
2372
|
+
if (info.changes > 0)
|
|
2373
|
+
updatedReqs.push(reqId);
|
|
2374
|
+
const rows = db
|
|
2375
|
+
.prepare(`SELECT id FROM memory_items WHERE req_id = ? OR (kind = 'requirement' AND req_id = ?)`)
|
|
2376
|
+
.all(reqId, reqId);
|
|
2377
|
+
supersedeMemoryItemIds(rows.map((r) => r.id), replacement);
|
|
2378
|
+
}
|
|
2379
|
+
return updatedReqs;
|
|
2380
|
+
}
|
|
2019
2381
|
async function semanticSearchInternal(opts) {
|
|
2020
2382
|
if (!embeddingsEnabled) {
|
|
2021
2383
|
throw new Error("Embeddings are disabled");
|
|
@@ -2067,7 +2429,31 @@ async function semanticSearchInternal(opts) {
|
|
|
2067
2429
|
return toSemanticMatch(item, t.score, opts.includeContent, opts.previewChars, opts.contentMaxChars);
|
|
2068
2430
|
})
|
|
2069
2431
|
.filter(Boolean);
|
|
2070
|
-
const filtered = matches
|
|
2432
|
+
const filtered = matches
|
|
2433
|
+
.filter((m) => {
|
|
2434
|
+
if (isSupersededMemory({ metadata_json: m.item.metadata_json }))
|
|
2435
|
+
return false;
|
|
2436
|
+
if (shouldIgnoreDbFilePath(m.item.file_path) && m.item.kind !== "change_intent")
|
|
2437
|
+
return false;
|
|
2438
|
+
return true;
|
|
2439
|
+
})
|
|
2440
|
+
.map((m) => ({
|
|
2441
|
+
...m,
|
|
2442
|
+
score: adjustSemanticScore({
|
|
2443
|
+
id: m.item.id,
|
|
2444
|
+
kind: m.item.kind,
|
|
2445
|
+
title: m.item.title,
|
|
2446
|
+
content: m.item.content ?? m.item.preview,
|
|
2447
|
+
file_path: m.item.file_path,
|
|
2448
|
+
start_line: m.item.start_line,
|
|
2449
|
+
end_line: m.item.end_line,
|
|
2450
|
+
req_id: m.item.req_id,
|
|
2451
|
+
metadata_json: m.item.metadata_json,
|
|
2452
|
+
updated_at: m.item.updated_at,
|
|
2453
|
+
}, m.score),
|
|
2454
|
+
}))
|
|
2455
|
+
.sort((a, b) => b.score - a.score)
|
|
2456
|
+
.slice(0, opts.topK);
|
|
2071
2457
|
return { query: q, top_k: opts.topK, mode: "embeddings", matches: filtered };
|
|
2072
2458
|
}
|
|
2073
2459
|
function buildFtsMatchQuery(raw) {
|
|
@@ -2136,10 +2522,7 @@ function ftsSearchInternal(opts) {
|
|
|
2136
2522
|
`);
|
|
2137
2523
|
return stmt.all(matchQuery, rawLimit);
|
|
2138
2524
|
})();
|
|
2139
|
-
const matches = rows
|
|
2140
|
-
.map((r) => toSemanticMatch(r, -Number(r.rank), opts.includeContent, opts.previewChars, opts.contentMaxChars))
|
|
2141
|
-
.filter((m) => !shouldIgnoreDbFilePath(m.item.file_path))
|
|
2142
|
-
.slice(0, opts.topK);
|
|
2525
|
+
const matches = filterAndRankSemanticRows(rows, (r) => -Number(r.rank), opts);
|
|
2143
2526
|
return { query: q, top_k: opts.topK, mode: "fts", matches };
|
|
2144
2527
|
}
|
|
2145
2528
|
function likeSearchInternal(opts) {
|
|
@@ -2205,10 +2588,7 @@ function likeSearchInternal(opts) {
|
|
|
2205
2588
|
`);
|
|
2206
2589
|
return stmt.all(like, like, like, like, like, rawLimit);
|
|
2207
2590
|
})();
|
|
2208
|
-
const matches = rows
|
|
2209
|
-
.map((r) => toSemanticMatch(r, Number(r.score), opts.includeContent, opts.previewChars, opts.contentMaxChars))
|
|
2210
|
-
.filter((m) => !shouldIgnoreDbFilePath(m.item.file_path))
|
|
2211
|
-
.slice(0, opts.topK);
|
|
2591
|
+
const matches = filterAndRankSemanticRows(rows, (r) => Number(r.score), opts);
|
|
2212
2592
|
return { query: q, top_k: opts.topK, mode: "like", matches };
|
|
2213
2593
|
}
|
|
2214
2594
|
async function semanticSearchHybridInternal(opts) {
|
|
@@ -2969,10 +3349,10 @@ function buildServerInstructions() {
|
|
|
2969
3349
|
"- Tool outputs are compact by default. Pass format=json only when you need full structured data.",
|
|
2970
3350
|
"- On every new conversation/session for analysis/design/development work: call bootstrap_context({ query: <current goal> }) first (or at least get_brain_dump()) to restore compact context and retrieve relevant matches from the local memory store (vector if enabled; otherwise FTS/LIKE).",
|
|
2971
3351
|
" - Output is compact by default. Use include_content=true only when you truly need full text (it increases tokens).",
|
|
2972
|
-
" - Tune output size with: requirements_limit/changes_limit/notes_limit, preview_chars, pending_limit/pending_offset.",
|
|
3352
|
+
" - Tune output size with: requirements_limit/changes_limit/notes_limit/decisions_limit, preview_chars, pending_limit/pending_offset.",
|
|
2973
3353
|
" - Prefer read_memory_item(id, offset, limit) to fetch full text on demand instead of returning large content in other tool outputs.",
|
|
2974
3354
|
"- For pure execution-first tasks with explicit targets (for example compile/build/run/launch/package/publish/test rerun), you may skip retrieval and go straight to the minimum necessary shell or host tools unless code/context lookup is actually needed to unblock execution.",
|
|
2975
|
-
"- If rtk is installed
|
|
3355
|
+
"- If rtk is installed or VectorMind's bundled RTK shim is verified (detect_rtk with gain_ok=true), prefix shell commands with the command returned by detect_rtk. Usually this is rtk (rtk git status, rtk npm run build, rtk rg ...); in npx/MCP-only installs it may be a package shim command such as node <...>/rtk-shim.js.",
|
|
2976
3356
|
"- If rtk is missing and the user asks to install it, use install_rtk first with dry_run=true to show the exact commands; execute with dry_run=false only after the user clearly approves installation/init choices.",
|
|
2977
3357
|
"- To read local Codex skill/prompt/rule files (for example SKILL.md under CODEX_HOME or AGENTS_HOME), prefer read_codex_text_file({ path }) instead of assuming a filesystem MCP resource server exists.",
|
|
2978
3358
|
"- For project file/directory browsing, prefer list_project_files({ path, recursive?, max_depth? }) over shelling out to Get-ChildItem/ls. It respects ignore rules and keeps output bounded.",
|
|
@@ -2987,6 +3367,7 @@ function buildServerInstructions() {
|
|
|
2987
3367
|
"- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
|
|
2988
3368
|
"- AFTER editing + saving: call get_pending_changes() to see unsynced files, then call sync_change_intent(intent, files). (You can omit files to auto-link all pending changes.)",
|
|
2989
3369
|
"- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
|
|
3370
|
+
"- When a requirement or user decision changes/reverses an older behavior, call upsert_decision(key, title, content, supersedes_req_ids?/supersedes_memory_ids?) and/or supersede_memory(...). Current decisions are shown in bootstrap_context/get_brain_dump and superseded memories are hidden from default semantic recall so stale requirements do not override newer facts.",
|
|
2990
3371
|
"- If the user states a durable project convention (build commands, frameworks, naming rules, output paths): call upsert_convention(key, content, tags) so it is applied in future sessions.",
|
|
2991
3372
|
"- When you need full text for a specific note/summary/match: call read_memory_item(id, offset, limit) and page through it.",
|
|
2992
3373
|
"- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
|
|
@@ -3211,6 +3592,9 @@ function initDatabase() {
|
|
|
3211
3592
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_convention_key
|
|
3212
3593
|
ON memory_items(kind, title) WHERE kind = 'convention';
|
|
3213
3594
|
|
|
3595
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_decision_key
|
|
3596
|
+
ON memory_items(kind, title) WHERE kind = 'decision';
|
|
3597
|
+
|
|
3214
3598
|
CREATE INDEX IF NOT EXISTS idx_memory_items_kind_updated_at
|
|
3215
3599
|
ON memory_items(kind, updated_at DESC);
|
|
3216
3600
|
|
|
@@ -3307,6 +3691,23 @@ function initDatabase() {
|
|
|
3307
3691
|
WHERE kind = 'convention'
|
|
3308
3692
|
ORDER BY updated_at DESC, id DESC
|
|
3309
3693
|
LIMIT ?`);
|
|
3694
|
+
upsertDecisionStmt = db.prepare(`INSERT INTO memory_items (kind, title, content, metadata_json, content_hash)
|
|
3695
|
+
VALUES ('decision', ?, ?, ?, ?)
|
|
3696
|
+
ON CONFLICT DO UPDATE SET
|
|
3697
|
+
content = excluded.content,
|
|
3698
|
+
metadata_json = excluded.metadata_json,
|
|
3699
|
+
content_hash = excluded.content_hash,
|
|
3700
|
+
updated_at = CURRENT_TIMESTAMP`);
|
|
3701
|
+
getDecisionByKeyStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3702
|
+
FROM memory_items
|
|
3703
|
+
WHERE kind = 'decision' AND title = ?
|
|
3704
|
+
ORDER BY updated_at DESC, id DESC
|
|
3705
|
+
LIMIT 1`);
|
|
3706
|
+
listCurrentDecisionsStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3707
|
+
FROM memory_items
|
|
3708
|
+
WHERE kind = 'decision'
|
|
3709
|
+
ORDER BY updated_at DESC, id DESC
|
|
3710
|
+
LIMIT ?`);
|
|
3310
3711
|
getRequirementMemoryItemIdStmt = db.prepare(`SELECT id
|
|
3311
3712
|
FROM memory_items
|
|
3312
3713
|
WHERE kind = 'requirement' AND req_id = ?
|
|
@@ -3329,6 +3730,11 @@ function initDatabase() {
|
|
|
3329
3730
|
WHERE kind = 'note'
|
|
3330
3731
|
ORDER BY updated_at DESC, id DESC
|
|
3331
3732
|
LIMIT ?`);
|
|
3733
|
+
getLatestChangeIntentForFileStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3734
|
+
FROM memory_items
|
|
3735
|
+
WHERE kind = 'change_intent' AND file_path = ?
|
|
3736
|
+
ORDER BY updated_at DESC, id DESC
|
|
3737
|
+
LIMIT 1`);
|
|
3332
3738
|
deleteFileChunkItemsStmt = db.prepare(`DELETE FROM memory_items
|
|
3333
3739
|
WHERE file_path = ?
|
|
3334
3740
|
AND (kind = 'code_chunk' OR kind = 'doc_chunk')`);
|
|
@@ -3598,7 +4004,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
3598
4004
|
},
|
|
3599
4005
|
{
|
|
3600
4006
|
name: "detect_rtk",
|
|
3601
|
-
description: "Detect whether rtk is available on PATH. When available, prefer
|
|
4007
|
+
description: "Detect whether rtk is available on PATH or via VectorMind's bundled RTK shim. When available, prefer the returned command as a shell prefix to reduce command-output tokens.",
|
|
3602
4008
|
inputSchema: toJsonSchemaCompat(DetectRtkArgsSchema),
|
|
3603
4009
|
},
|
|
3604
4010
|
{
|
|
@@ -3651,6 +4057,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
3651
4057
|
description: "Save a durable project note (decision, constraint, TODO, architecture detail). Use this to persist important context locally instead of relying on chat memory.",
|
|
3652
4058
|
inputSchema: toJsonSchemaCompat(AddNoteArgsSchema),
|
|
3653
4059
|
},
|
|
4060
|
+
{
|
|
4061
|
+
name: "upsert_decision",
|
|
4062
|
+
description: "Save/update the current authoritative project decision for a key. Use it when requirements change, reverse, or supersede older behavior so future sessions prefer the latest decision over old history.",
|
|
4063
|
+
inputSchema: toJsonSchemaCompat(UpsertDecisionArgsSchema),
|
|
4064
|
+
},
|
|
4065
|
+
{
|
|
4066
|
+
name: "supersede_memory",
|
|
4067
|
+
description: "Mark old requirements or memory items as superseded by a newer requirement/decision. Superseded items are hidden from default semantic recall to avoid reverting to stale behavior.",
|
|
4068
|
+
inputSchema: toJsonSchemaCompat(SupersedeMemoryArgsSchema),
|
|
4069
|
+
},
|
|
3654
4070
|
{
|
|
3655
4071
|
name: "upsert_convention",
|
|
3656
4072
|
description: "Save/update a project convention (framework choice, build command, naming rules, etc). Conventions are durable and should be applied automatically in future sessions.",
|
|
@@ -3882,24 +4298,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3882
4298
|
}
|
|
3883
4299
|
else {
|
|
3884
4300
|
const pendingAll = listPendingChangesStmt.all();
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
for (const p of pending) {
|
|
3889
|
-
targets.push({
|
|
3890
|
-
rawFile: p.file_path,
|
|
3891
|
-
dbFilePath: p.file_path,
|
|
3892
|
-
event: p.last_event,
|
|
3893
|
-
source: "pending",
|
|
3894
|
-
});
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
else {
|
|
4301
|
+
const merged = mergePendingWithGit(pendingAll, { offset: 0, limit: MAX_PENDING_LIMIT });
|
|
4302
|
+
if (merged.page.length) {
|
|
4303
|
+
for (const p of merged.page) {
|
|
3898
4304
|
targets.push({
|
|
3899
|
-
rawFile:
|
|
3900
|
-
dbFilePath:
|
|
3901
|
-
event:
|
|
3902
|
-
source: "
|
|
4305
|
+
rawFile: p.file_path,
|
|
4306
|
+
dbFilePath: p.file_path,
|
|
4307
|
+
event: p.last_event,
|
|
4308
|
+
source: p.source === "git" ? "pending" : "pending",
|
|
3903
4309
|
});
|
|
3904
4310
|
}
|
|
3905
4311
|
deleteAllPendingChangesStmt.run();
|
|
@@ -3917,7 +4323,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3917
4323
|
const isUnspecified = t.dbFilePath === "(unspecified)";
|
|
3918
4324
|
const changeInfo = insertChangeLogStmt.run(active.id, t.dbFilePath, args.intent);
|
|
3919
4325
|
const change_log_id = Number(changeInfo.lastInsertRowid);
|
|
3920
|
-
const memoryInfo = insertMemoryItemStmt.run("change_intent", active.title, args.intent, isUnspecified ? null : t.dbFilePath, null, null, active.id, safeJson({
|
|
4326
|
+
const memoryInfo = insertMemoryItemStmt.run("change_intent", active.title, args.intent, isUnspecified ? null : t.dbFilePath, null, null, active.id, safeJson({
|
|
4327
|
+
change_log_id,
|
|
4328
|
+
event: t.event,
|
|
4329
|
+
source: t.source,
|
|
4330
|
+
file_state_hash: isUnspecified ? null : getFileStateHash(t.rawFile),
|
|
4331
|
+
}), sha256Hex(args.intent));
|
|
3921
4332
|
const memory_item_id = Number(memoryInfo.lastInsertRowid);
|
|
3922
4333
|
enqueueEmbedding(memory_item_id);
|
|
3923
4334
|
synced_files.push({ file_path: t.dbFilePath, event: t.event, source: t.source });
|
|
@@ -3968,6 +4379,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3968
4379
|
const changesLimit = args.changes_limit;
|
|
3969
4380
|
const notesLimit = args.notes_limit;
|
|
3970
4381
|
const conventionsLimit = args.conventions_limit;
|
|
4382
|
+
const decisionsLimit = args.decisions_limit;
|
|
3971
4383
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
3972
4384
|
const items = recent.map((req) => {
|
|
3973
4385
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -3981,12 +4393,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3981
4393
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
3982
4394
|
: null;
|
|
3983
4395
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
4396
|
+
const decisions = getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars);
|
|
3984
4397
|
const conventions = getConventionPreviews(conventionsLimit, previewChars, contentMaxChars);
|
|
3985
|
-
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
3986
4398
|
const pending_offset = args.pending_offset;
|
|
3987
4399
|
const pending_limit = args.pending_limit;
|
|
3988
|
-
const
|
|
3989
|
-
const
|
|
4400
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4401
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset: pending_offset, limit: pending_limit });
|
|
4402
|
+
const pending_total = mergedPending.total;
|
|
4403
|
+
const pending_truncated = mergedPending.truncated;
|
|
4404
|
+
const pending_changes = mergedPending.page;
|
|
3990
4405
|
const q = args.query?.trim() ?? "";
|
|
3991
4406
|
const semantic = q
|
|
3992
4407
|
? await Promise.race([
|
|
@@ -4009,6 +4424,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4009
4424
|
pending_total,
|
|
4010
4425
|
pending_returned: pending_changes.length,
|
|
4011
4426
|
requirements_returned: items.length,
|
|
4427
|
+
decisions_returned: decisions.length,
|
|
4012
4428
|
conventions_returned: conventions.length,
|
|
4013
4429
|
semantic_mode: semantic?.mode ?? null,
|
|
4014
4430
|
semantic_matches: semantic?.matches?.length ?? 0,
|
|
@@ -4034,9 +4450,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4034
4450
|
requirements_limit: requirementsLimit,
|
|
4035
4451
|
changes_limit: changesLimit,
|
|
4036
4452
|
notes_limit: notesLimit,
|
|
4453
|
+
decisions_limit: decisionsLimit,
|
|
4037
4454
|
conventions_limit: conventionsLimit,
|
|
4038
4455
|
},
|
|
4039
4456
|
project_summary,
|
|
4457
|
+
decisions,
|
|
4040
4458
|
conventions,
|
|
4041
4459
|
recent_notes,
|
|
4042
4460
|
pending_total,
|
|
@@ -4066,6 +4484,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4066
4484
|
const changesLimit = args.changes_limit;
|
|
4067
4485
|
const notesLimit = args.notes_limit;
|
|
4068
4486
|
const conventionsLimit = args.conventions_limit;
|
|
4487
|
+
const decisionsLimit = args.decisions_limit;
|
|
4069
4488
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
4070
4489
|
const items = recent.map((req) => {
|
|
4071
4490
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -4079,17 +4498,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4079
4498
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
4080
4499
|
: null;
|
|
4081
4500
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
4501
|
+
const decisions = getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars);
|
|
4082
4502
|
const conventions = getConventionPreviews(conventionsLimit, previewChars, contentMaxChars);
|
|
4083
|
-
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
4084
4503
|
const pending_offset = args.pending_offset;
|
|
4085
4504
|
const pending_limit = args.pending_limit;
|
|
4086
|
-
const
|
|
4087
|
-
const
|
|
4505
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4506
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset: pending_offset, limit: pending_limit });
|
|
4507
|
+
const pending_total = mergedPending.total;
|
|
4508
|
+
const pending_truncated = mergedPending.truncated;
|
|
4509
|
+
const pending_changes = mergedPending.page;
|
|
4088
4510
|
logActivity("get_brain_dump", {
|
|
4089
4511
|
pending_total,
|
|
4090
4512
|
pending_returned: pending_changes.length,
|
|
4091
4513
|
requirements_returned: items.length,
|
|
4092
4514
|
notes_returned: recent_notes.length,
|
|
4515
|
+
decisions_returned: decisions.length,
|
|
4093
4516
|
conventions_returned: conventions.length,
|
|
4094
4517
|
});
|
|
4095
4518
|
const outputValue = {
|
|
@@ -4113,9 +4536,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4113
4536
|
requirements_limit: requirementsLimit,
|
|
4114
4537
|
changes_limit: changesLimit,
|
|
4115
4538
|
notes_limit: notesLimit,
|
|
4539
|
+
decisions_limit: decisionsLimit,
|
|
4116
4540
|
conventions_limit: conventionsLimit,
|
|
4117
4541
|
},
|
|
4118
4542
|
project_summary,
|
|
4543
|
+
decisions,
|
|
4119
4544
|
conventions,
|
|
4120
4545
|
recent_notes,
|
|
4121
4546
|
pending_total,
|
|
@@ -4138,11 +4563,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4138
4563
|
if (toolName === "get_pending_changes") {
|
|
4139
4564
|
const args = GetPendingChangesArgsSchema.parse(rawArgs);
|
|
4140
4565
|
flushPendingChangeBuffer();
|
|
4141
|
-
const total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
4142
4566
|
const offset = args.offset;
|
|
4143
4567
|
const limit = args.limit;
|
|
4144
|
-
const
|
|
4145
|
-
const
|
|
4568
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4569
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset, limit });
|
|
4570
|
+
const total = mergedPending.total;
|
|
4571
|
+
const truncated = mergedPending.truncated;
|
|
4572
|
+
const pending = mergedPending.page;
|
|
4146
4573
|
logActivity("get_pending_changes", {
|
|
4147
4574
|
total,
|
|
4148
4575
|
offset,
|
|
@@ -4315,8 +4742,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4315
4742
|
DetectRtkArgsSchema.parse(rawArgs);
|
|
4316
4743
|
const result = detectRtk();
|
|
4317
4744
|
const text = result.available
|
|
4318
|
-
? `rtk available: ${result.version ?? result.command}\
|
|
4319
|
-
: `rtk unavailable: ${result.command}\
|
|
4745
|
+
? `rtk available: ${result.version ?? result.command}\ncommand=${result.command} source=${result.source ?? "unknown"} gain_ok=${result.gain_ok ?? false}${result.path ? ` path=${result.path}` : ""}\n${result.note}`
|
|
4746
|
+
: `rtk unavailable: ${result.command}\nsource=${result.source ?? "none"} gain_ok=${result.gain_ok ?? false}${result.version ? ` version=${result.version}` : ""}${result.path ? ` path=${result.path}` : ""}\n${result.note}`;
|
|
4320
4747
|
return { content: [{ type: "text", text }] };
|
|
4321
4748
|
}
|
|
4322
4749
|
if (toolName === "install_rtk") {
|
|
@@ -4812,6 +5239,95 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4812
5239
|
],
|
|
4813
5240
|
};
|
|
4814
5241
|
}
|
|
5242
|
+
if (toolName === "upsert_decision") {
|
|
5243
|
+
const args = UpsertDecisionArgsSchema.parse(rawArgs);
|
|
5244
|
+
const key = args.key.trim();
|
|
5245
|
+
const title = args.title.trim() || key;
|
|
5246
|
+
const content = args.content.trim();
|
|
5247
|
+
const meta = {
|
|
5248
|
+
status: "current",
|
|
5249
|
+
key,
|
|
5250
|
+
title,
|
|
5251
|
+
tags: args.tags ?? [],
|
|
5252
|
+
supersedes_req_ids: args.supersedes_req_ids ?? [],
|
|
5253
|
+
supersedes_memory_ids: args.supersedes_memory_ids ?? [],
|
|
5254
|
+
related_files: (args.related_files ?? []).map((f) => normalizeToDbPath(f)),
|
|
5255
|
+
};
|
|
5256
|
+
upsertDecisionStmt.run(key, `${title}\n\n${content}`, safeJson(meta), sha256Hex(`${title}\n\n${content}`));
|
|
5257
|
+
const row = getDecisionByKeyStmt.get(key);
|
|
5258
|
+
if (row)
|
|
5259
|
+
enqueueEmbedding(row.id);
|
|
5260
|
+
const superseded_requirements = supersedeRequirementIds(args.supersedes_req_ids ?? [], {
|
|
5261
|
+
decision_id: row?.id,
|
|
5262
|
+
reason: `Superseded by decision ${key}: ${title}`,
|
|
5263
|
+
});
|
|
5264
|
+
const superseded_memory_items = supersedeMemoryItemIds(args.supersedes_memory_ids ?? [], {
|
|
5265
|
+
decision_id: row?.id,
|
|
5266
|
+
reason: `Superseded by decision ${key}: ${title}`,
|
|
5267
|
+
});
|
|
5268
|
+
logActivity("upsert_decision", {
|
|
5269
|
+
key,
|
|
5270
|
+
decision_id: row?.id ?? null,
|
|
5271
|
+
superseded_requirements,
|
|
5272
|
+
superseded_memory_items,
|
|
5273
|
+
});
|
|
5274
|
+
return {
|
|
5275
|
+
content: [
|
|
5276
|
+
{
|
|
5277
|
+
type: "text",
|
|
5278
|
+
text: toolJson({
|
|
5279
|
+
ok: true,
|
|
5280
|
+
decision: row ? { id: row.id, key, updated_at: row.updated_at } : null,
|
|
5281
|
+
superseded_requirements,
|
|
5282
|
+
superseded_memory_items,
|
|
5283
|
+
}),
|
|
5284
|
+
},
|
|
5285
|
+
],
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
if (toolName === "supersede_memory") {
|
|
5289
|
+
const args = SupersedeMemoryArgsSchema.parse(rawArgs);
|
|
5290
|
+
const supersededReqIds = args.superseded_req_ids ?? [];
|
|
5291
|
+
const supersededMemoryIds = args.superseded_memory_ids ?? [];
|
|
5292
|
+
if (!supersededReqIds.length && !supersededMemoryIds.length) {
|
|
5293
|
+
return {
|
|
5294
|
+
isError: true,
|
|
5295
|
+
content: [
|
|
5296
|
+
{
|
|
5297
|
+
type: "text",
|
|
5298
|
+
text: toolJson({
|
|
5299
|
+
ok: false,
|
|
5300
|
+
error: "Provide superseded_req_ids and/or superseded_memory_ids.",
|
|
5301
|
+
}),
|
|
5302
|
+
},
|
|
5303
|
+
],
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
const superseded_requirements = supersedeRequirementIds(supersededReqIds, {
|
|
5307
|
+
req_id: args.replacement_req_id,
|
|
5308
|
+
memory_id: args.replacement_memory_id,
|
|
5309
|
+
reason: args.reason,
|
|
5310
|
+
});
|
|
5311
|
+
const superseded_memory_items = supersedeMemoryItemIds(supersededMemoryIds, {
|
|
5312
|
+
req_id: args.replacement_req_id,
|
|
5313
|
+
memory_id: args.replacement_memory_id,
|
|
5314
|
+
reason: args.reason,
|
|
5315
|
+
});
|
|
5316
|
+
logActivity("supersede_memory", {
|
|
5317
|
+
superseded_requirements,
|
|
5318
|
+
superseded_memory_items,
|
|
5319
|
+
replacement_req_id: args.replacement_req_id ?? null,
|
|
5320
|
+
replacement_memory_id: args.replacement_memory_id ?? null,
|
|
5321
|
+
});
|
|
5322
|
+
return {
|
|
5323
|
+
content: [
|
|
5324
|
+
{
|
|
5325
|
+
type: "text",
|
|
5326
|
+
text: toolJson({ ok: true, superseded_requirements, superseded_memory_items }),
|
|
5327
|
+
},
|
|
5328
|
+
],
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
4815
5331
|
if (toolName === "upsert_convention") {
|
|
4816
5332
|
const args = UpsertConventionArgsSchema.parse(rawArgs);
|
|
4817
5333
|
const key = args.key.trim();
|