@coreyuan/vector-mind 1.0.39 → 1.0.42
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 +301 -283
- package/dist/builtin-conventions.js +26 -135
- package/dist/builtin-conventions.js.map +1 -1
- package/dist/builtin-instructions.d.ts +9 -9
- package/dist/builtin-instructions.js +10 -13
- package/dist/builtin-instructions.js.map +1 -1
- package/dist/index.js +598 -89
- 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.42";
|
|
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) {
|
|
@@ -2933,65 +3313,59 @@ function listProjectFilesInternal(opts) {
|
|
|
2933
3313
|
function buildServerInstructions() {
|
|
2934
3314
|
return [
|
|
2935
3315
|
"VectorMind MCP is available in this session. Use it to avoid guessing project context.",
|
|
2936
|
-
"
|
|
2937
|
-
"The write-operation rules below are strict workflow constraints. Do not claim that the environment has real git branch locks, checkout APIs, or file-lock tools unless such tools are actually available in the current client/runtime. If such tools are absent, you must still enforce the same exclusivity semantics through explicit coordination and serialized same-file edits.",
|
|
3316
|
+
"Development guideline scope: VectorMind instructions define development conventions, project-memory conventions, code-organization conventions, and delivery-quality expectations only. They are unrelated to AI access permissions, runtime permissions, command permissions, filesystem/network permissions, approval mechanisms, or sandbox behavior.",
|
|
2938
3317
|
"Project root resolution order: tool argument project_root (recommended for clients without roots/list), then VECTORMIND_ROOT (avoid hardcoding in global config), then MCP roots/list (best-effort; falls back quickly if unsupported), then process.cwd() (so start your MCP client in the project directory for per-project isolation).",
|
|
2939
3318
|
"If root_source is fallback, file watching/indexing is disabled (pass project_root to enable per-project tracking).",
|
|
2940
3319
|
"",
|
|
2941
|
-
"Built-in write-operation policy:",
|
|
3320
|
+
"Built-in write-operation quality policy:",
|
|
2942
3321
|
BUILTIN_WRITE_POLICY_INSTRUCTIONS,
|
|
2943
3322
|
"",
|
|
2944
|
-
"Built-in task-list / Plan-Lite policy:",
|
|
3323
|
+
"Built-in task-list / Plan-Lite quality policy:",
|
|
2945
3324
|
BUILTIN_PLAN_LITE_INSTRUCTIONS,
|
|
2946
3325
|
"",
|
|
2947
|
-
"Built-in destructive-operation guard
|
|
3326
|
+
"Built-in destructive-operation quality guard:",
|
|
2948
3327
|
BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS,
|
|
2949
3328
|
"",
|
|
2950
|
-
"Built-in architecture and code-organization policy:",
|
|
3329
|
+
"Built-in architecture and code-organization quality policy:",
|
|
2951
3330
|
BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS,
|
|
2952
3331
|
"",
|
|
2953
|
-
"Built-in frontend output-purity policy:",
|
|
3332
|
+
"Built-in frontend output-purity quality policy:",
|
|
2954
3333
|
BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS,
|
|
2955
3334
|
"",
|
|
2956
|
-
"Built-in git commit summary policy:",
|
|
3335
|
+
"Built-in git commit summary quality policy:",
|
|
2957
3336
|
BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS,
|
|
2958
3337
|
"",
|
|
2959
|
-
"Built-in low-overhead execution and heavy-thread policy:",
|
|
3338
|
+
"Built-in low-overhead execution and heavy-thread quality policy:",
|
|
2960
3339
|
BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS,
|
|
2961
3340
|
"",
|
|
2962
|
-
"Built-in payload / oversized-thread guard
|
|
3341
|
+
"Built-in payload / oversized-thread quality guard:",
|
|
2963
3342
|
BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS,
|
|
2964
3343
|
"",
|
|
2965
|
-
"Built-in thread handoff / switch-gate policy:",
|
|
3344
|
+
"Built-in thread handoff / switch-gate quality policy:",
|
|
2966
3345
|
BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS,
|
|
2967
3346
|
"",
|
|
2968
|
-
"
|
|
3347
|
+
"VectorMind workflow:",
|
|
2969
3348
|
"- Tool outputs are compact by default. Pass format=json only when you need full structured data.",
|
|
2970
3349
|
"- 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
3350
|
" - 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.",
|
|
3351
|
+
" - Tune output size with: requirements_limit/changes_limit/notes_limit/decisions_limit, preview_chars, pending_limit/pending_offset.",
|
|
2973
3352
|
" - Prefer read_memory_item(id, offset, limit) to fetch full text on demand instead of returning large content in other tool outputs.",
|
|
2974
3353
|
"- 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
|
|
3354
|
+
"- 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
3355
|
"- 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
3356
|
"- 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
3357
|
"- 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.",
|
|
2979
3358
|
"- For small/medium raw file reads, prefer read_file_text({ path, offset?, max_chars? }) over Get-Content -Raw. Use read_file_lines(...) when you need deterministic line ranges or the file may be large.",
|
|
2980
3359
|
"- For raw repo text search with exact file+line+col matches, prefer grep({ query: <pattern> }). It uses ripgrep against real project files when available, applies built-in noise filters, and only falls back to indexed search if ripgrep is unavailable.",
|
|
2981
3360
|
"- To read a bounded segment of a file, prefer read_file_lines({ path: <file>, from_line/to_line or total_count }) over unbounded file reads.",
|
|
2982
|
-
"- If the current thread is heavy, recently compacted, has become slow, or has already hit a 413 / Payload Too Large style error, switch to payload guard mode: avoid unbounded shell dumps, prefer bounded MCP tools, and summarize outputs instead of pasting large raw blocks.",
|
|
2983
|
-
"- In payload guard mode, do not use full-repo recursive listings, whole-file dumps, or broad raw match echo unless the user explicitly requests that raw output and accepts the size risk.",
|
|
2984
|
-
"- Thread-switch judgment must not rely on a fixed token threshold; use observable signals plus the weight of the upcoming work. If the current thread is heavy, repeatedly compacting, slow, or has already hit a 413 / Payload Too Large style error and the next work still needs broad analysis, cross-module investigation, release validation, or other substantial continuation, pause once and ask whether to switch to a fresh thread before continuing.",
|
|
2985
|
-
"- If the user declines that switch, continue in the current thread and do not raise the thread-switch reminder again in the same session.",
|
|
2986
|
-
"- If the user accepts, or explicitly asks you to pack the current conversation for a new thread, do not attempt to create a new thread on the user's behalf and do not claim that you already created one. Use add_note(...) to persist a concise handoff note, include other relevant existing note ids only when they are truly needed, then reply briefly with the new handoff note id and tell the user that in the new thread they can say '读取 note <id> [和 note <id>] 继续'.",
|
|
2987
3361
|
"- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
|
|
2988
3362
|
"- 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
3363
|
"- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
|
|
3364
|
+
"- 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
3365
|
"- 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
3366
|
"- When you need full text for a specific note/summary/match: call read_memory_item(id, offset, limit) and page through it.",
|
|
2992
3367
|
"- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
|
|
2993
3368
|
"- When you need to recall relevant context from history/code/docs: call semantic_search(query, ...) instead of guessing.",
|
|
2994
|
-
"- If the current thread is already heavy or the user reports it has become slow, switch to a lighter workflow: avoid redundant retrieval, keep outputs compact, and if the user refuses thread switching, continue in light mode without repeating the switch reminder in that same session.",
|
|
2995
3369
|
"- Use get_token_savings({ format: 'compact' }) when you need to verify how many tokens VectorMind compact outputs saved.",
|
|
2996
3370
|
"",
|
|
2997
3371
|
"If tool output conflicts with assumptions, trust the tool output.",
|
|
@@ -3211,6 +3585,9 @@ function initDatabase() {
|
|
|
3211
3585
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_convention_key
|
|
3212
3586
|
ON memory_items(kind, title) WHERE kind = 'convention';
|
|
3213
3587
|
|
|
3588
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_decision_key
|
|
3589
|
+
ON memory_items(kind, title) WHERE kind = 'decision';
|
|
3590
|
+
|
|
3214
3591
|
CREATE INDEX IF NOT EXISTS idx_memory_items_kind_updated_at
|
|
3215
3592
|
ON memory_items(kind, updated_at DESC);
|
|
3216
3593
|
|
|
@@ -3307,6 +3684,23 @@ function initDatabase() {
|
|
|
3307
3684
|
WHERE kind = 'convention'
|
|
3308
3685
|
ORDER BY updated_at DESC, id DESC
|
|
3309
3686
|
LIMIT ?`);
|
|
3687
|
+
upsertDecisionStmt = db.prepare(`INSERT INTO memory_items (kind, title, content, metadata_json, content_hash)
|
|
3688
|
+
VALUES ('decision', ?, ?, ?, ?)
|
|
3689
|
+
ON CONFLICT DO UPDATE SET
|
|
3690
|
+
content = excluded.content,
|
|
3691
|
+
metadata_json = excluded.metadata_json,
|
|
3692
|
+
content_hash = excluded.content_hash,
|
|
3693
|
+
updated_at = CURRENT_TIMESTAMP`);
|
|
3694
|
+
getDecisionByKeyStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3695
|
+
FROM memory_items
|
|
3696
|
+
WHERE kind = 'decision' AND title = ?
|
|
3697
|
+
ORDER BY updated_at DESC, id DESC
|
|
3698
|
+
LIMIT 1`);
|
|
3699
|
+
listCurrentDecisionsStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3700
|
+
FROM memory_items
|
|
3701
|
+
WHERE kind = 'decision'
|
|
3702
|
+
ORDER BY updated_at DESC, id DESC
|
|
3703
|
+
LIMIT ?`);
|
|
3310
3704
|
getRequirementMemoryItemIdStmt = db.prepare(`SELECT id
|
|
3311
3705
|
FROM memory_items
|
|
3312
3706
|
WHERE kind = 'requirement' AND req_id = ?
|
|
@@ -3329,6 +3723,11 @@ function initDatabase() {
|
|
|
3329
3723
|
WHERE kind = 'note'
|
|
3330
3724
|
ORDER BY updated_at DESC, id DESC
|
|
3331
3725
|
LIMIT ?`);
|
|
3726
|
+
getLatestChangeIntentForFileStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3727
|
+
FROM memory_items
|
|
3728
|
+
WHERE kind = 'change_intent' AND file_path = ?
|
|
3729
|
+
ORDER BY updated_at DESC, id DESC
|
|
3730
|
+
LIMIT 1`);
|
|
3332
3731
|
deleteFileChunkItemsStmt = db.prepare(`DELETE FROM memory_items
|
|
3333
3732
|
WHERE file_path = ?
|
|
3334
3733
|
AND (kind = 'code_chunk' OR kind = 'doc_chunk')`);
|
|
@@ -3598,7 +3997,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
3598
3997
|
},
|
|
3599
3998
|
{
|
|
3600
3999
|
name: "detect_rtk",
|
|
3601
|
-
description: "Detect whether rtk is available on PATH. When available, prefer
|
|
4000
|
+
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
4001
|
inputSchema: toJsonSchemaCompat(DetectRtkArgsSchema),
|
|
3603
4002
|
},
|
|
3604
4003
|
{
|
|
@@ -3651,6 +4050,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
3651
4050
|
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
4051
|
inputSchema: toJsonSchemaCompat(AddNoteArgsSchema),
|
|
3653
4052
|
},
|
|
4053
|
+
{
|
|
4054
|
+
name: "upsert_decision",
|
|
4055
|
+
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.",
|
|
4056
|
+
inputSchema: toJsonSchemaCompat(UpsertDecisionArgsSchema),
|
|
4057
|
+
},
|
|
4058
|
+
{
|
|
4059
|
+
name: "supersede_memory",
|
|
4060
|
+
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.",
|
|
4061
|
+
inputSchema: toJsonSchemaCompat(SupersedeMemoryArgsSchema),
|
|
4062
|
+
},
|
|
3654
4063
|
{
|
|
3655
4064
|
name: "upsert_convention",
|
|
3656
4065
|
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 +4291,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3882
4291
|
}
|
|
3883
4292
|
else {
|
|
3884
4293
|
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 {
|
|
4294
|
+
const merged = mergePendingWithGit(pendingAll, { offset: 0, limit: MAX_PENDING_LIMIT });
|
|
4295
|
+
if (merged.page.length) {
|
|
4296
|
+
for (const p of merged.page) {
|
|
3898
4297
|
targets.push({
|
|
3899
|
-
rawFile:
|
|
3900
|
-
dbFilePath:
|
|
3901
|
-
event:
|
|
3902
|
-
source: "
|
|
4298
|
+
rawFile: p.file_path,
|
|
4299
|
+
dbFilePath: p.file_path,
|
|
4300
|
+
event: p.last_event,
|
|
4301
|
+
source: p.source === "git" ? "pending" : "pending",
|
|
3903
4302
|
});
|
|
3904
4303
|
}
|
|
3905
4304
|
deleteAllPendingChangesStmt.run();
|
|
@@ -3917,7 +4316,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3917
4316
|
const isUnspecified = t.dbFilePath === "(unspecified)";
|
|
3918
4317
|
const changeInfo = insertChangeLogStmt.run(active.id, t.dbFilePath, args.intent);
|
|
3919
4318
|
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({
|
|
4319
|
+
const memoryInfo = insertMemoryItemStmt.run("change_intent", active.title, args.intent, isUnspecified ? null : t.dbFilePath, null, null, active.id, safeJson({
|
|
4320
|
+
change_log_id,
|
|
4321
|
+
event: t.event,
|
|
4322
|
+
source: t.source,
|
|
4323
|
+
file_state_hash: isUnspecified ? null : getFileStateHash(t.rawFile),
|
|
4324
|
+
}), sha256Hex(args.intent));
|
|
3921
4325
|
const memory_item_id = Number(memoryInfo.lastInsertRowid);
|
|
3922
4326
|
enqueueEmbedding(memory_item_id);
|
|
3923
4327
|
synced_files.push({ file_path: t.dbFilePath, event: t.event, source: t.source });
|
|
@@ -3968,6 +4372,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3968
4372
|
const changesLimit = args.changes_limit;
|
|
3969
4373
|
const notesLimit = args.notes_limit;
|
|
3970
4374
|
const conventionsLimit = args.conventions_limit;
|
|
4375
|
+
const decisionsLimit = args.decisions_limit;
|
|
3971
4376
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
3972
4377
|
const items = recent.map((req) => {
|
|
3973
4378
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -3981,12 +4386,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3981
4386
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
3982
4387
|
: null;
|
|
3983
4388
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
4389
|
+
const decisions = getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars);
|
|
3984
4390
|
const conventions = getConventionPreviews(conventionsLimit, previewChars, contentMaxChars);
|
|
3985
|
-
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
3986
4391
|
const pending_offset = args.pending_offset;
|
|
3987
4392
|
const pending_limit = args.pending_limit;
|
|
3988
|
-
const
|
|
3989
|
-
const
|
|
4393
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4394
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset: pending_offset, limit: pending_limit });
|
|
4395
|
+
const pending_total = mergedPending.total;
|
|
4396
|
+
const pending_truncated = mergedPending.truncated;
|
|
4397
|
+
const pending_changes = mergedPending.page;
|
|
3990
4398
|
const q = args.query?.trim() ?? "";
|
|
3991
4399
|
const semantic = q
|
|
3992
4400
|
? await Promise.race([
|
|
@@ -4009,6 +4417,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4009
4417
|
pending_total,
|
|
4010
4418
|
pending_returned: pending_changes.length,
|
|
4011
4419
|
requirements_returned: items.length,
|
|
4420
|
+
decisions_returned: decisions.length,
|
|
4012
4421
|
conventions_returned: conventions.length,
|
|
4013
4422
|
semantic_mode: semantic?.mode ?? null,
|
|
4014
4423
|
semantic_matches: semantic?.matches?.length ?? 0,
|
|
@@ -4034,9 +4443,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4034
4443
|
requirements_limit: requirementsLimit,
|
|
4035
4444
|
changes_limit: changesLimit,
|
|
4036
4445
|
notes_limit: notesLimit,
|
|
4446
|
+
decisions_limit: decisionsLimit,
|
|
4037
4447
|
conventions_limit: conventionsLimit,
|
|
4038
4448
|
},
|
|
4039
4449
|
project_summary,
|
|
4450
|
+
decisions,
|
|
4040
4451
|
conventions,
|
|
4041
4452
|
recent_notes,
|
|
4042
4453
|
pending_total,
|
|
@@ -4066,6 +4477,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4066
4477
|
const changesLimit = args.changes_limit;
|
|
4067
4478
|
const notesLimit = args.notes_limit;
|
|
4068
4479
|
const conventionsLimit = args.conventions_limit;
|
|
4480
|
+
const decisionsLimit = args.decisions_limit;
|
|
4069
4481
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
4070
4482
|
const items = recent.map((req) => {
|
|
4071
4483
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -4079,17 +4491,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4079
4491
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
4080
4492
|
: null;
|
|
4081
4493
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
4494
|
+
const decisions = getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars);
|
|
4082
4495
|
const conventions = getConventionPreviews(conventionsLimit, previewChars, contentMaxChars);
|
|
4083
|
-
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
4084
4496
|
const pending_offset = args.pending_offset;
|
|
4085
4497
|
const pending_limit = args.pending_limit;
|
|
4086
|
-
const
|
|
4087
|
-
const
|
|
4498
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4499
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset: pending_offset, limit: pending_limit });
|
|
4500
|
+
const pending_total = mergedPending.total;
|
|
4501
|
+
const pending_truncated = mergedPending.truncated;
|
|
4502
|
+
const pending_changes = mergedPending.page;
|
|
4088
4503
|
logActivity("get_brain_dump", {
|
|
4089
4504
|
pending_total,
|
|
4090
4505
|
pending_returned: pending_changes.length,
|
|
4091
4506
|
requirements_returned: items.length,
|
|
4092
4507
|
notes_returned: recent_notes.length,
|
|
4508
|
+
decisions_returned: decisions.length,
|
|
4093
4509
|
conventions_returned: conventions.length,
|
|
4094
4510
|
});
|
|
4095
4511
|
const outputValue = {
|
|
@@ -4113,9 +4529,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4113
4529
|
requirements_limit: requirementsLimit,
|
|
4114
4530
|
changes_limit: changesLimit,
|
|
4115
4531
|
notes_limit: notesLimit,
|
|
4532
|
+
decisions_limit: decisionsLimit,
|
|
4116
4533
|
conventions_limit: conventionsLimit,
|
|
4117
4534
|
},
|
|
4118
4535
|
project_summary,
|
|
4536
|
+
decisions,
|
|
4119
4537
|
conventions,
|
|
4120
4538
|
recent_notes,
|
|
4121
4539
|
pending_total,
|
|
@@ -4138,11 +4556,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4138
4556
|
if (toolName === "get_pending_changes") {
|
|
4139
4557
|
const args = GetPendingChangesArgsSchema.parse(rawArgs);
|
|
4140
4558
|
flushPendingChangeBuffer();
|
|
4141
|
-
const total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
4142
4559
|
const offset = args.offset;
|
|
4143
4560
|
const limit = args.limit;
|
|
4144
|
-
const
|
|
4145
|
-
const
|
|
4561
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4562
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset, limit });
|
|
4563
|
+
const total = mergedPending.total;
|
|
4564
|
+
const truncated = mergedPending.truncated;
|
|
4565
|
+
const pending = mergedPending.page;
|
|
4146
4566
|
logActivity("get_pending_changes", {
|
|
4147
4567
|
total,
|
|
4148
4568
|
offset,
|
|
@@ -4315,8 +4735,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4315
4735
|
DetectRtkArgsSchema.parse(rawArgs);
|
|
4316
4736
|
const result = detectRtk();
|
|
4317
4737
|
const text = result.available
|
|
4318
|
-
? `rtk available: ${result.version ?? result.command}\
|
|
4319
|
-
: `rtk unavailable: ${result.command}\
|
|
4738
|
+
? `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}`
|
|
4739
|
+
: `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
4740
|
return { content: [{ type: "text", text }] };
|
|
4321
4741
|
}
|
|
4322
4742
|
if (toolName === "install_rtk") {
|
|
@@ -4812,6 +5232,95 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4812
5232
|
],
|
|
4813
5233
|
};
|
|
4814
5234
|
}
|
|
5235
|
+
if (toolName === "upsert_decision") {
|
|
5236
|
+
const args = UpsertDecisionArgsSchema.parse(rawArgs);
|
|
5237
|
+
const key = args.key.trim();
|
|
5238
|
+
const title = args.title.trim() || key;
|
|
5239
|
+
const content = args.content.trim();
|
|
5240
|
+
const meta = {
|
|
5241
|
+
status: "current",
|
|
5242
|
+
key,
|
|
5243
|
+
title,
|
|
5244
|
+
tags: args.tags ?? [],
|
|
5245
|
+
supersedes_req_ids: args.supersedes_req_ids ?? [],
|
|
5246
|
+
supersedes_memory_ids: args.supersedes_memory_ids ?? [],
|
|
5247
|
+
related_files: (args.related_files ?? []).map((f) => normalizeToDbPath(f)),
|
|
5248
|
+
};
|
|
5249
|
+
upsertDecisionStmt.run(key, `${title}\n\n${content}`, safeJson(meta), sha256Hex(`${title}\n\n${content}`));
|
|
5250
|
+
const row = getDecisionByKeyStmt.get(key);
|
|
5251
|
+
if (row)
|
|
5252
|
+
enqueueEmbedding(row.id);
|
|
5253
|
+
const superseded_requirements = supersedeRequirementIds(args.supersedes_req_ids ?? [], {
|
|
5254
|
+
decision_id: row?.id,
|
|
5255
|
+
reason: `Superseded by decision ${key}: ${title}`,
|
|
5256
|
+
});
|
|
5257
|
+
const superseded_memory_items = supersedeMemoryItemIds(args.supersedes_memory_ids ?? [], {
|
|
5258
|
+
decision_id: row?.id,
|
|
5259
|
+
reason: `Superseded by decision ${key}: ${title}`,
|
|
5260
|
+
});
|
|
5261
|
+
logActivity("upsert_decision", {
|
|
5262
|
+
key,
|
|
5263
|
+
decision_id: row?.id ?? null,
|
|
5264
|
+
superseded_requirements,
|
|
5265
|
+
superseded_memory_items,
|
|
5266
|
+
});
|
|
5267
|
+
return {
|
|
5268
|
+
content: [
|
|
5269
|
+
{
|
|
5270
|
+
type: "text",
|
|
5271
|
+
text: toolJson({
|
|
5272
|
+
ok: true,
|
|
5273
|
+
decision: row ? { id: row.id, key, updated_at: row.updated_at } : null,
|
|
5274
|
+
superseded_requirements,
|
|
5275
|
+
superseded_memory_items,
|
|
5276
|
+
}),
|
|
5277
|
+
},
|
|
5278
|
+
],
|
|
5279
|
+
};
|
|
5280
|
+
}
|
|
5281
|
+
if (toolName === "supersede_memory") {
|
|
5282
|
+
const args = SupersedeMemoryArgsSchema.parse(rawArgs);
|
|
5283
|
+
const supersededReqIds = args.superseded_req_ids ?? [];
|
|
5284
|
+
const supersededMemoryIds = args.superseded_memory_ids ?? [];
|
|
5285
|
+
if (!supersededReqIds.length && !supersededMemoryIds.length) {
|
|
5286
|
+
return {
|
|
5287
|
+
isError: true,
|
|
5288
|
+
content: [
|
|
5289
|
+
{
|
|
5290
|
+
type: "text",
|
|
5291
|
+
text: toolJson({
|
|
5292
|
+
ok: false,
|
|
5293
|
+
error: "Provide superseded_req_ids and/or superseded_memory_ids.",
|
|
5294
|
+
}),
|
|
5295
|
+
},
|
|
5296
|
+
],
|
|
5297
|
+
};
|
|
5298
|
+
}
|
|
5299
|
+
const superseded_requirements = supersedeRequirementIds(supersededReqIds, {
|
|
5300
|
+
req_id: args.replacement_req_id,
|
|
5301
|
+
memory_id: args.replacement_memory_id,
|
|
5302
|
+
reason: args.reason,
|
|
5303
|
+
});
|
|
5304
|
+
const superseded_memory_items = supersedeMemoryItemIds(supersededMemoryIds, {
|
|
5305
|
+
req_id: args.replacement_req_id,
|
|
5306
|
+
memory_id: args.replacement_memory_id,
|
|
5307
|
+
reason: args.reason,
|
|
5308
|
+
});
|
|
5309
|
+
logActivity("supersede_memory", {
|
|
5310
|
+
superseded_requirements,
|
|
5311
|
+
superseded_memory_items,
|
|
5312
|
+
replacement_req_id: args.replacement_req_id ?? null,
|
|
5313
|
+
replacement_memory_id: args.replacement_memory_id ?? null,
|
|
5314
|
+
});
|
|
5315
|
+
return {
|
|
5316
|
+
content: [
|
|
5317
|
+
{
|
|
5318
|
+
type: "text",
|
|
5319
|
+
text: toolJson({ ok: true, superseded_requirements, superseded_memory_items }),
|
|
5320
|
+
},
|
|
5321
|
+
],
|
|
5322
|
+
};
|
|
5323
|
+
}
|
|
4815
5324
|
if (toolName === "upsert_convention") {
|
|
4816
5325
|
const args = UpsertConventionArgsSchema.parse(rawArgs);
|
|
4817
5326
|
const key = args.key.trim();
|