@vheins/local-memory-mcp 0.19.4 → 0.19.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-R45NRES5.js → chunk-IT2PAPJ6.js} +126 -36
- package/dist/dashboard/server.js +1 -1
- package/dist/mcp/server.js +101 -80
- package/package.json +1 -1
|
@@ -132,6 +132,7 @@ import fs3 from "fs";
|
|
|
132
132
|
import os from "os";
|
|
133
133
|
|
|
134
134
|
// src/mcp/storage/migrations.ts
|
|
135
|
+
var SCHEMA_VERSION = 2;
|
|
135
136
|
var MigrationManager = class {
|
|
136
137
|
constructor(db) {
|
|
137
138
|
this.db = db;
|
|
@@ -149,7 +150,25 @@ var MigrationManager = class {
|
|
|
149
150
|
get(sql) {
|
|
150
151
|
return this.db.prepare(sql).get();
|
|
151
152
|
}
|
|
153
|
+
getSchemaVersion() {
|
|
154
|
+
try {
|
|
155
|
+
const row = this.get("SELECT version FROM _schema_version LIMIT 1");
|
|
156
|
+
return row ? row.version : 0;
|
|
157
|
+
} catch {
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
setSchemaVersion(version) {
|
|
162
|
+
this.exec(`CREATE TABLE IF NOT EXISTS _schema_version (version INTEGER NOT NULL)`);
|
|
163
|
+
this.run("DELETE FROM _schema_version");
|
|
164
|
+
this.run("INSERT INTO _schema_version (version) VALUES (?)", version);
|
|
165
|
+
}
|
|
152
166
|
migrate() {
|
|
167
|
+
const currentVersion = this.getSchemaVersion();
|
|
168
|
+
if (currentVersion >= SCHEMA_VERSION) {
|
|
169
|
+
logger.debug(`[Migration] Schema already at version ${SCHEMA_VERSION}, skipping`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
153
172
|
this.exec(`
|
|
154
173
|
CREATE TABLE IF NOT EXISTS memories (
|
|
155
174
|
id TEXT PRIMARY KEY,
|
|
@@ -599,6 +618,8 @@ var MigrationManager = class {
|
|
|
599
618
|
this.run("UPDATE tasks SET task_code = substr(id, 1, 8) WHERE task_code IS NULL");
|
|
600
619
|
} catch {
|
|
601
620
|
}
|
|
621
|
+
this.setSchemaVersion(SCHEMA_VERSION);
|
|
622
|
+
logger.info(`[Migration] Schema upgraded to version ${SCHEMA_VERSION}`);
|
|
602
623
|
}
|
|
603
624
|
ensureMemoryTypeConstraint() {
|
|
604
625
|
const tableSql = this.get("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memories'");
|
|
@@ -1049,7 +1070,15 @@ var BaseEntity = class {
|
|
|
1049
1070
|
status: r.status || "active",
|
|
1050
1071
|
is_global: r.is_global === 1,
|
|
1051
1072
|
tags: this.safeJSONParse(r.tags, []),
|
|
1052
|
-
metadata:
|
|
1073
|
+
metadata: (() => {
|
|
1074
|
+
const meta = this.safeJSONParse(r.metadata, {});
|
|
1075
|
+
delete meta.structuredData;
|
|
1076
|
+
return meta;
|
|
1077
|
+
})(),
|
|
1078
|
+
structuredData: (() => {
|
|
1079
|
+
const meta = this.safeJSONParse(r.metadata, {});
|
|
1080
|
+
return meta.structuredData ?? void 0;
|
|
1081
|
+
})()
|
|
1053
1082
|
};
|
|
1054
1083
|
}
|
|
1055
1084
|
rowToTask(row) {
|
|
@@ -1141,6 +1170,7 @@ var VALID_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
1141
1170
|
]);
|
|
1142
1171
|
var MemoryEntity = class extends BaseEntity {
|
|
1143
1172
|
insert(entry) {
|
|
1173
|
+
const mergedMeta = this.mergeStructuredData(entry.metadata, entry.structuredData);
|
|
1144
1174
|
this.run(
|
|
1145
1175
|
`INSERT INTO memories (
|
|
1146
1176
|
id, code, repo, owner, type, title, content, importance, folder, language,
|
|
@@ -1165,7 +1195,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1165
1195
|
entry.status || "active",
|
|
1166
1196
|
entry.is_global ? 1 : 0,
|
|
1167
1197
|
entry.tags ? JSON.stringify(entry.tags) : null,
|
|
1168
|
-
|
|
1198
|
+
mergedMeta ? JSON.stringify(mergedMeta) : null,
|
|
1169
1199
|
entry.agent || "unknown",
|
|
1170
1200
|
entry.role || "unknown",
|
|
1171
1201
|
entry.model || "unknown",
|
|
@@ -1173,6 +1203,9 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1173
1203
|
]
|
|
1174
1204
|
);
|
|
1175
1205
|
}
|
|
1206
|
+
mergeStructuredData(metadata, structuredData) {
|
|
1207
|
+
return { ...metadata, structuredData: structuredData ?? {} };
|
|
1208
|
+
}
|
|
1176
1209
|
update(id, updates) {
|
|
1177
1210
|
const fields = [];
|
|
1178
1211
|
const values = [];
|
|
@@ -1198,6 +1231,12 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1198
1231
|
fields.push("language = ?");
|
|
1199
1232
|
values.push(scope.language);
|
|
1200
1233
|
}
|
|
1234
|
+
} else if (k === "structuredData") {
|
|
1235
|
+
const existingRow = this.get("SELECT metadata FROM memories WHERE id = ?", [id]);
|
|
1236
|
+
const existingMeta = existingRow ? this.safeJSONParse(existingRow.metadata, {}) : {};
|
|
1237
|
+
const merged = { ...existingMeta, structuredData: val };
|
|
1238
|
+
fields.push("metadata = ?");
|
|
1239
|
+
values.push(JSON.stringify(merged));
|
|
1201
1240
|
} else if (k === "tags" || k === "metadata") {
|
|
1202
1241
|
fields.push(`${k} = ?`);
|
|
1203
1242
|
values.push(JSON.stringify(val));
|
|
@@ -1301,6 +1340,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1301
1340
|
return this.transaction(() => {
|
|
1302
1341
|
let count = 0;
|
|
1303
1342
|
for (const entry of entries) {
|
|
1343
|
+
const mergedMeta = this.mergeStructuredData(entry.metadata, entry.structuredData);
|
|
1304
1344
|
this.run(
|
|
1305
1345
|
`INSERT INTO memories (
|
|
1306
1346
|
id, repo, owner, type, title, content, importance, folder, language,
|
|
@@ -1324,7 +1364,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1324
1364
|
entry.status || "active",
|
|
1325
1365
|
entry.is_global ? 1 : 0,
|
|
1326
1366
|
entry.tags ? JSON.stringify(entry.tags) : null,
|
|
1327
|
-
|
|
1367
|
+
mergedMeta ? JSON.stringify(mergedMeta) : null,
|
|
1328
1368
|
entry.agent || "unknown",
|
|
1329
1369
|
entry.role || "unknown",
|
|
1330
1370
|
entry.model || "unknown",
|
|
@@ -3208,7 +3248,11 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
3208
3248
|
this.db.pragma("foreign_keys = ON");
|
|
3209
3249
|
this.db.pragma("wal_autocheckpoint = 100");
|
|
3210
3250
|
if (finalPath !== ":memory:") {
|
|
3211
|
-
|
|
3251
|
+
try {
|
|
3252
|
+
this.db.pragma("wal_checkpoint(PASSIVE)");
|
|
3253
|
+
} catch (err) {
|
|
3254
|
+
logger.warn("[SQLiteStore] WAL checkpoint failed on startup", { error: String(err) });
|
|
3255
|
+
}
|
|
3212
3256
|
}
|
|
3213
3257
|
const migrator = new MigrationManager(this.db);
|
|
3214
3258
|
migrator.migrate();
|
|
@@ -3494,24 +3538,29 @@ function findPromptDir() {
|
|
|
3494
3538
|
return path4.resolve(__dirname, "./definitions");
|
|
3495
3539
|
}
|
|
3496
3540
|
var PROMPT_DIR = findPromptDir();
|
|
3541
|
+
var promptCache = /* @__PURE__ */ new Map();
|
|
3497
3542
|
function listPromptFiles() {
|
|
3498
3543
|
if (!fs4.existsSync(PROMPT_DIR)) return [];
|
|
3499
3544
|
return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|
|
3500
3545
|
}
|
|
3501
3546
|
function loadPromptFromMarkdown(name) {
|
|
3547
|
+
const cached = promptCache.get(name);
|
|
3548
|
+
if (cached) return cached;
|
|
3502
3549
|
const filePath = path4.join(PROMPT_DIR, `${name}.md`);
|
|
3503
3550
|
if (!fs4.existsSync(filePath)) {
|
|
3504
3551
|
throw new Error(`Prompt file not found: ${filePath}`);
|
|
3505
3552
|
}
|
|
3506
3553
|
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3507
3554
|
const { data, content } = matter(fileContent);
|
|
3508
|
-
|
|
3555
|
+
const result = {
|
|
3509
3556
|
name: data.name || name,
|
|
3510
3557
|
description: data.description || "",
|
|
3511
3558
|
arguments: data.arguments || [],
|
|
3512
3559
|
agent: data.agent,
|
|
3513
3560
|
content: content.trim()
|
|
3514
3561
|
};
|
|
3562
|
+
promptCache.set(name, result);
|
|
3563
|
+
return result;
|
|
3515
3564
|
}
|
|
3516
3565
|
function findServerInstructionsDir() {
|
|
3517
3566
|
const candidates = [
|
|
@@ -3533,14 +3582,17 @@ function findServerInstructionsDir() {
|
|
|
3533
3582
|
return path4.resolve(__dirname, "./server");
|
|
3534
3583
|
}
|
|
3535
3584
|
var SERVER_DIR = findServerInstructionsDir();
|
|
3585
|
+
var serverInstructionsCache = null;
|
|
3536
3586
|
function loadServerInstructions() {
|
|
3587
|
+
if (serverInstructionsCache !== null) return serverInstructionsCache;
|
|
3537
3588
|
const filePath = path4.join(SERVER_DIR, "instructions.md");
|
|
3538
3589
|
if (!fs4.existsSync(filePath)) {
|
|
3539
3590
|
throw new Error(`Server instructions file not found: ${filePath}`);
|
|
3540
3591
|
}
|
|
3541
3592
|
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3542
3593
|
const { content } = matter(fileContent);
|
|
3543
|
-
|
|
3594
|
+
serverInstructionsCache = content.trim();
|
|
3595
|
+
return serverInstructionsCache;
|
|
3544
3596
|
}
|
|
3545
3597
|
|
|
3546
3598
|
// src/mcp/tools/definitions/memory.ts
|
|
@@ -4255,7 +4307,10 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4255
4307
|
properties: {
|
|
4256
4308
|
owner: { type: "string", description: "Organization/namespace (e.g., GitHub org or username)" },
|
|
4257
4309
|
repo: { type: "string", description: "Repository name" },
|
|
4258
|
-
task_code: {
|
|
4310
|
+
task_code: {
|
|
4311
|
+
type: "string",
|
|
4312
|
+
description: "Task code (e.g. PERF-1, TASK-001). Use instead of 'id' for string code lookup."
|
|
4313
|
+
},
|
|
4259
4314
|
structured: {
|
|
4260
4315
|
type: "boolean",
|
|
4261
4316
|
default: false,
|
|
@@ -4273,7 +4328,7 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4273
4328
|
type: "array",
|
|
4274
4329
|
items: { type: "string" },
|
|
4275
4330
|
minItems: 1,
|
|
4276
|
-
description: "Array of task codes (e.g. TASK-001)"
|
|
4331
|
+
description: "Array of task codes (e.g. PERF-1, TASK-001). Use instead of 'ids' when identifying tasks by code rather than UUID."
|
|
4277
4332
|
},
|
|
4278
4333
|
structured: {
|
|
4279
4334
|
type: "boolean",
|
|
@@ -4401,7 +4456,7 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4401
4456
|
{
|
|
4402
4457
|
name: "task-update",
|
|
4403
4458
|
title: "Task Update",
|
|
4404
|
-
description: "Update one or more tasks. Supports single update via 'id' or bulk
|
|
4459
|
+
description: "Update one or more tasks. Supports single update via 'id' (UUID) or 'task_code' (e.g. PERF-1), bulk via 'ids' (UUID array) or 'task_codes' (string array). Use 'task_code'/'task_codes' for human-readable identifiers, 'id'/'ids' for UUID lookups. Provide only the fields that need to be changed. MANDATORY WORKFLOW: Cannot move 'pending'/'blocked' \u2192 'completed' directly; MUST go through 'in_progress' first. Include 'est_tokens' when moving to 'completed'.",
|
|
4405
4460
|
annotations: {
|
|
4406
4461
|
readOnlyHint: false,
|
|
4407
4462
|
idempotentHint: false,
|
|
@@ -4482,7 +4537,7 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4482
4537
|
ids: {
|
|
4483
4538
|
type: "array",
|
|
4484
4539
|
items: { type: "string", format: "uuid" },
|
|
4485
|
-
description: "Task
|
|
4540
|
+
description: "Task UUIDs (for bulk update). NOT task codes \u2014 use 'task_codes' for PERF-1 style identifiers."
|
|
4486
4541
|
},
|
|
4487
4542
|
phase: { type: "string" },
|
|
4488
4543
|
title: { type: "string", minLength: 3, maxLength: 100 },
|
|
@@ -4613,7 +4668,7 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4613
4668
|
type: "array",
|
|
4614
4669
|
items: { type: "string" },
|
|
4615
4670
|
minItems: 1,
|
|
4616
|
-
description: "Array of task codes (e.g. TASK-001)"
|
|
4671
|
+
description: "Array of task codes (e.g. PERF-1, TASK-001). Use instead of 'ids' when identifying tasks by code rather than UUID."
|
|
4617
4672
|
},
|
|
4618
4673
|
phase: { type: "string" },
|
|
4619
4674
|
title: { type: "string", minLength: 3, maxLength: 100 },
|
|
@@ -4723,7 +4778,7 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4723
4778
|
ids: {
|
|
4724
4779
|
type: "array",
|
|
4725
4780
|
items: { type: "string", format: "uuid" },
|
|
4726
|
-
description: "Task
|
|
4781
|
+
description: "Task UUIDs (for bulk deletion). NOT task codes \u2014 use 'task_codes' for PERF-1 style identifiers."
|
|
4727
4782
|
},
|
|
4728
4783
|
structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
|
|
4729
4784
|
}
|
|
@@ -4734,7 +4789,10 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4734
4789
|
properties: {
|
|
4735
4790
|
owner: { type: "string", description: "Organization/namespace (e.g., GitHub org or username)" },
|
|
4736
4791
|
repo: { type: "string", description: "Repository name" },
|
|
4737
|
-
task_code: {
|
|
4792
|
+
task_code: {
|
|
4793
|
+
type: "string",
|
|
4794
|
+
description: "Task code (e.g. PERF-1, TASK-001). Use instead of 'id' for string code lookup."
|
|
4795
|
+
},
|
|
4738
4796
|
structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
|
|
4739
4797
|
}
|
|
4740
4798
|
},
|
|
@@ -4748,7 +4806,7 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4748
4806
|
type: "array",
|
|
4749
4807
|
items: { type: "string" },
|
|
4750
4808
|
minItems: 1,
|
|
4751
|
-
description: "Array of task codes (e.g. TASK-001)"
|
|
4809
|
+
description: "Array of task codes (e.g. PERF-1, TASK-001). Use instead of 'ids' when identifying tasks by code rather than UUID."
|
|
4752
4810
|
},
|
|
4753
4811
|
structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
|
|
4754
4812
|
}
|
|
@@ -5622,7 +5680,7 @@ var AGENT_TOOL_DEFINITIONS = [
|
|
|
5622
5680
|
{
|
|
5623
5681
|
name: "decision-log",
|
|
5624
5682
|
title: "Decision Logger",
|
|
5625
|
-
description: "Logs a structured decision as a memory entry.
|
|
5683
|
+
description: "Logs a structured decision as a memory entry (type='decision', importance=4). Fields: summary (max 255 chars \u2014 becomes the memory title), context (situation/background, min 10 chars), rationale (why it was decided, min 10 chars), and alternatives (ARRAY of strings, NOT a single string \u2014 e.g. ['option A', 'option B']). Do NOT pass 'title' \u2014 that field does not exist on this tool. Owner and repo are auto-inferred from session when omitted.",
|
|
5626
5684
|
annotations: {
|
|
5627
5685
|
readOnlyHint: false,
|
|
5628
5686
|
idempotentHint: false,
|
|
@@ -6418,6 +6476,28 @@ var SingleStandardSchema = z2.object({
|
|
|
6418
6476
|
model: z2.string().optional()
|
|
6419
6477
|
});
|
|
6420
6478
|
var TaskStatusValues = TaskStatusSchema.options;
|
|
6479
|
+
function isPlainObject(v) {
|
|
6480
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
6481
|
+
}
|
|
6482
|
+
function isJsonSerializable(val) {
|
|
6483
|
+
try {
|
|
6484
|
+
JSON.stringify(val);
|
|
6485
|
+
return true;
|
|
6486
|
+
} catch {
|
|
6487
|
+
return false;
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
var StructuredDataValue = z2.lazy(
|
|
6491
|
+
() => z2.union([
|
|
6492
|
+
z2.string(),
|
|
6493
|
+
z2.number().refine((v) => Number.isFinite(v), "Number must be finite"),
|
|
6494
|
+
z2.boolean(),
|
|
6495
|
+
z2.null(),
|
|
6496
|
+
z2.array(StructuredDataValue),
|
|
6497
|
+
z2.record(z2.string(), StructuredDataValue).refine(isPlainObject, "Plain object required")
|
|
6498
|
+
])
|
|
6499
|
+
);
|
|
6500
|
+
var StructuredDataSchema = z2.unknown().refine(isJsonSerializable, { message: "Value must be JSON-serializable" }).pipe(z2.record(z2.string(), StructuredDataValue));
|
|
6421
6501
|
|
|
6422
6502
|
// src/mcp/tools/schemas/memory.ts
|
|
6423
6503
|
import { z as z3 } from "zod";
|
|
@@ -6499,8 +6579,8 @@ var MemoryAcknowledgeSchema = z3.object({
|
|
|
6499
6579
|
message: "Either memory_id or code must be provided"
|
|
6500
6580
|
});
|
|
6501
6581
|
var MemoryRecapSchema = z3.object({
|
|
6502
|
-
owner: z3.string().min(1),
|
|
6503
|
-
repo: z3.string().min(1).transform(normalizeRepo),
|
|
6582
|
+
owner: z3.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
|
|
6583
|
+
repo: z3.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
|
|
6504
6584
|
limit: z3.number().min(1).max(50).default(20),
|
|
6505
6585
|
offset: z3.number().min(0).default(0),
|
|
6506
6586
|
structured: z3.boolean().default(false)
|
|
@@ -6683,13 +6763,15 @@ var TaskUpdateSchema = z4.object({
|
|
|
6683
6763
|
structured: z4.boolean().default(false)
|
|
6684
6764
|
}).refine(
|
|
6685
6765
|
(data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0 || data.task_codes !== void 0,
|
|
6686
|
-
{
|
|
6766
|
+
{
|
|
6767
|
+
message: "Either 'id' (UUID), 'ids' (array of UUIDs), 'task_code' (string code like PERF-1), or 'task_codes' (array of string codes) must be provided. Note: 'ids' expects UUID format, not task codes \u2014 use 'task_code'/'task_codes' for string identifiers."
|
|
6768
|
+
}
|
|
6687
6769
|
).refine((data) => Object.keys(data).length > 2, {
|
|
6688
6770
|
message: "At least one field besides repo and id/ids must be provided for update"
|
|
6689
6771
|
});
|
|
6690
6772
|
var TaskListSchema = z4.object({
|
|
6691
|
-
owner: z4.string().min(1),
|
|
6692
|
-
repo: z4.string().min(1).transform(normalizeRepo),
|
|
6773
|
+
owner: z4.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
|
|
6774
|
+
repo: z4.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
|
|
6693
6775
|
status: TaskStatusListSchema.default("backlog,pending,in_progress,blocked"),
|
|
6694
6776
|
phase: z4.string().optional(),
|
|
6695
6777
|
query: z4.string().optional(),
|
|
@@ -6718,7 +6800,9 @@ var TaskDeleteSchema = z4.object({
|
|
|
6718
6800
|
structured: z4.boolean().default(false)
|
|
6719
6801
|
}).refine(
|
|
6720
6802
|
(data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0 || data.task_codes !== void 0,
|
|
6721
|
-
{
|
|
6803
|
+
{
|
|
6804
|
+
message: "Either 'id' (UUID), 'ids' (array of UUIDs), 'task_code' (string code like PERF-1), or 'task_codes' (array of string codes) must be provided. Note: 'ids' expects UUID format, not task codes \u2014 use 'task_code'/'task_codes' for string identifiers."
|
|
6805
|
+
}
|
|
6722
6806
|
);
|
|
6723
6807
|
var TaskGetSchema = z4.object({
|
|
6724
6808
|
owner: z4.string().min(1),
|
|
@@ -6734,8 +6818,8 @@ var TaskGetSchema = z4.object({
|
|
|
6734
6818
|
// src/mcp/tools/schemas/handoff.ts
|
|
6735
6819
|
import { z as z5 } from "zod";
|
|
6736
6820
|
var HandoffCreateSchema = z5.object({
|
|
6737
|
-
owner: z5.string().min(1
|
|
6738
|
-
repo: z5.string().min(1).transform(normalizeRepo),
|
|
6821
|
+
owner: z5.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
|
|
6822
|
+
repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
|
|
6739
6823
|
from_agent: z5.string().min(1),
|
|
6740
6824
|
to_agent: z5.string().min(1).optional(),
|
|
6741
6825
|
task_id: z5.string().uuid().optional(),
|
|
@@ -6758,8 +6842,8 @@ var HandoffUpdateSchema = z5.object({
|
|
|
6758
6842
|
structured: z5.boolean().default(false)
|
|
6759
6843
|
});
|
|
6760
6844
|
var HandoffListSchema = z5.object({
|
|
6761
|
-
owner: z5.string().min(1
|
|
6762
|
-
repo: z5.string().min(1).transform(normalizeRepo),
|
|
6845
|
+
owner: z5.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
|
|
6846
|
+
repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
|
|
6763
6847
|
status: HandoffStatusSchema.optional(),
|
|
6764
6848
|
from_agent: z5.string().min(1).optional(),
|
|
6765
6849
|
to_agent: z5.string().min(1).optional(),
|
|
@@ -6768,8 +6852,8 @@ var HandoffListSchema = z5.object({
|
|
|
6768
6852
|
structured: z5.boolean().default(false)
|
|
6769
6853
|
});
|
|
6770
6854
|
var TaskClaimSchema = z5.object({
|
|
6771
|
-
owner: z5.string().min(1
|
|
6772
|
-
repo: z5.string().min(1).transform(normalizeRepo),
|
|
6855
|
+
owner: z5.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
|
|
6856
|
+
repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
|
|
6773
6857
|
task_id: z5.string().uuid().optional(),
|
|
6774
6858
|
task_code: z5.string().optional(),
|
|
6775
6859
|
agent: z5.string().min(1),
|
|
@@ -6782,8 +6866,8 @@ var TaskClaimSchema = z5.object({
|
|
|
6782
6866
|
message: "Provide either task_id or task_code, not both"
|
|
6783
6867
|
});
|
|
6784
6868
|
var ClaimListSchema = z5.object({
|
|
6785
|
-
owner: z5.string().
|
|
6786
|
-
repo: z5.string().
|
|
6869
|
+
owner: z5.string().optional().default(""),
|
|
6870
|
+
repo: z5.string().transform(normalizeRepo).optional().default(""),
|
|
6787
6871
|
agent: z5.string().min(1).optional(),
|
|
6788
6872
|
active_only: z5.boolean().default(true),
|
|
6789
6873
|
limit: z5.number().min(1).max(100).default(20),
|
|
@@ -6791,8 +6875,8 @@ var ClaimListSchema = z5.object({
|
|
|
6791
6875
|
structured: z5.boolean().default(false)
|
|
6792
6876
|
});
|
|
6793
6877
|
var ClaimReleaseSchema = z5.object({
|
|
6794
|
-
owner: z5.string().min(1
|
|
6795
|
-
repo: z5.string().min(1).transform(normalizeRepo),
|
|
6878
|
+
owner: z5.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
|
|
6879
|
+
repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
|
|
6796
6880
|
task_id: z5.string().uuid().optional(),
|
|
6797
6881
|
task_code: z5.string().optional(),
|
|
6798
6882
|
agent: z5.string().min(1).optional(),
|
|
@@ -6906,10 +6990,10 @@ var AgentContextSchema = z7.object({
|
|
|
6906
6990
|
structured: z7.boolean().default(false)
|
|
6907
6991
|
});
|
|
6908
6992
|
var DecisionLogSchema = z7.object({
|
|
6909
|
-
summary: z7.string().min(3).max(255, { message: "Summary must be at most 255 characters" }),
|
|
6910
|
-
context: z7.string().min(10, { message: "Context must be at least 10 characters" }),
|
|
6911
|
-
rationale: z7.string().min(10, { message: "Rationale must be at least 10 characters" }),
|
|
6912
|
-
alternatives: z7.array(z7.string()).optional(),
|
|
6993
|
+
summary: z7.string().min(3, { message: "Summary must be at least 3 characters (this becomes the memory title)" }).max(255, { message: "Summary must be at most 255 characters (this becomes the memory title)" }),
|
|
6994
|
+
context: z7.string().min(10, { message: "Context must be at least 10 characters describing the situation" }),
|
|
6995
|
+
rationale: z7.string().min(10, { message: "Rationale must be at least 10 characters explaining why the decision was made" }),
|
|
6996
|
+
alternatives: z7.array(z7.string(), { message: "Alternatives must be an array of strings, e.g. ['option A', 'option B']" }).optional(),
|
|
6913
6997
|
tags: z7.array(z7.string()).optional(),
|
|
6914
6998
|
owner: z7.string().optional(),
|
|
6915
6999
|
repo: z7.string().optional(),
|
|
@@ -7029,7 +7113,13 @@ async function handleHandoffCreate(args, storage) {
|
|
|
7029
7113
|
});
|
|
7030
7114
|
}
|
|
7031
7115
|
async function handleHandoffList(args, storage) {
|
|
7032
|
-
const
|
|
7116
|
+
const parsed = HandoffListSchema.safeParse(args);
|
|
7117
|
+
if (!parsed.success) {
|
|
7118
|
+
const missing = parsed.error.issues.filter((i) => i.path.some((p) => p === "owner" || p === "repo")).map((i) => i.message).filter(Boolean);
|
|
7119
|
+
const msg = missing.length > 0 ? `Missing required fields: ${missing.join("; ")}. Pass owner/repo explicitly or configure MCP workspace roots so they can be auto-inferred.` : `Validation error: ${parsed.error.message}`;
|
|
7120
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
7121
|
+
}
|
|
7122
|
+
const validated = parsed.data;
|
|
7033
7123
|
const { owner, repo, status, from_agent, to_agent, limit, offset, structured } = validated;
|
|
7034
7124
|
const handoffs = storage.handoffs.listHandoffs({
|
|
7035
7125
|
owner,
|
package/dist/dashboard/server.js
CHANGED
package/dist/mcp/server.js
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
parseRepoInput,
|
|
62
62
|
rankCompletionValues,
|
|
63
63
|
toContextSlug
|
|
64
|
-
} from "../chunk-
|
|
64
|
+
} from "../chunk-IT2PAPJ6.js";
|
|
65
65
|
|
|
66
66
|
// src/mcp/server.ts
|
|
67
67
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -74,8 +74,8 @@ import path from "path";
|
|
|
74
74
|
import { fileURLToPath } from "url";
|
|
75
75
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
76
76
|
var pkgVersion = "0.1.0";
|
|
77
|
-
if ("0.19.
|
|
78
|
-
pkgVersion = "0.19.
|
|
77
|
+
if ("0.19.6") {
|
|
78
|
+
pkgVersion = "0.19.6";
|
|
79
79
|
} else {
|
|
80
80
|
let searchDir = __dirname;
|
|
81
81
|
for (let i = 0; i < 5; i++) {
|
|
@@ -154,7 +154,6 @@ function generateNextCode(owner, repo, entityType, storage, batchCodes) {
|
|
|
154
154
|
|
|
155
155
|
// src/mcp/tools/kg-archivist.ts
|
|
156
156
|
import { randomUUID } from "crypto";
|
|
157
|
-
import nlp from "compromise";
|
|
158
157
|
var MAX_CONTENT_LENGTH = 5e3;
|
|
159
158
|
var PRONOUNS = /* @__PURE__ */ new Set([
|
|
160
159
|
"i",
|
|
@@ -354,8 +353,9 @@ function isExcludedNoun(candidate) {
|
|
|
354
353
|
if (/^\d+$/.test(candidate)) return true;
|
|
355
354
|
return false;
|
|
356
355
|
}
|
|
357
|
-
function extractEntities(content) {
|
|
356
|
+
async function extractEntities(content) {
|
|
358
357
|
if (!content || content.trim().length === 0) return [];
|
|
358
|
+
const { default: nlp } = await import("compromise");
|
|
359
359
|
const text = content.length > MAX_CONTENT_LENGTH ? content.slice(0, MAX_CONTENT_LENGTH) : content;
|
|
360
360
|
const doc = nlp(text);
|
|
361
361
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -386,11 +386,11 @@ function extractEntities(content) {
|
|
|
386
386
|
}
|
|
387
387
|
return entities;
|
|
388
388
|
}
|
|
389
|
-
function saveExtractions(content, title, owner, repo, db2) {
|
|
389
|
+
async function saveExtractions(content, title, owner, repo, db2) {
|
|
390
390
|
if (!content || content.trim().length === 0) return;
|
|
391
391
|
let entities;
|
|
392
392
|
try {
|
|
393
|
-
entities = extractEntities(content);
|
|
393
|
+
entities = await extractEntities(content);
|
|
394
394
|
} catch (err) {
|
|
395
395
|
logger.warn("[KG-Archivist] Entity extraction failed, skipping", {
|
|
396
396
|
error: String(err)
|
|
@@ -507,7 +507,7 @@ async function storeSingleMemory(params, db2, vectors2) {
|
|
|
507
507
|
logger.warn("Failed to generate vector embedding", { error: String(error) });
|
|
508
508
|
}
|
|
509
509
|
try {
|
|
510
|
-
saveExtractions(entry.content, entry.title, entry.scope.owner, entry.scope.repo, db2);
|
|
510
|
+
await saveExtractions(entry.content, entry.title, entry.scope.owner, entry.scope.repo, db2);
|
|
511
511
|
} catch (error) {
|
|
512
512
|
logger.warn("[KG-Archivist] NLP extraction failed, memory stored without KG enrichment", {
|
|
513
513
|
error: String(error)
|
|
@@ -614,7 +614,7 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
614
614
|
logger.warn("Failed to generate vector embedding", { error: String(error) });
|
|
615
615
|
}
|
|
616
616
|
try {
|
|
617
|
-
saveExtractions(entry.content, entry.title, entry.scope.owner, entry.scope.repo, db2);
|
|
617
|
+
await saveExtractions(entry.content, entry.title, entry.scope.owner, entry.scope.repo, db2);
|
|
618
618
|
} catch (error) {
|
|
619
619
|
logger.warn("[KG-Archivist] NLP extraction failed, memory stored without KG enrichment", {
|
|
620
620
|
error: String(error)
|
|
@@ -1118,7 +1118,13 @@ function extractAcceptedElicitationContent(result) {
|
|
|
1118
1118
|
|
|
1119
1119
|
// src/mcp/tools/memory.recap.ts
|
|
1120
1120
|
async function handleMemoryRecap(params, db2) {
|
|
1121
|
-
const
|
|
1121
|
+
const parsed = MemoryRecapSchema.safeParse(params);
|
|
1122
|
+
if (!parsed.success) {
|
|
1123
|
+
const missing = parsed.error.issues.filter((i) => i.path.some((p) => p === "owner" || p === "repo")).map((i) => i.message).filter(Boolean);
|
|
1124
|
+
const msg = missing.length > 0 ? `Missing required fields: ${missing.join("; ")}. Pass owner/repo explicitly or configure MCP workspace roots so they can be auto-inferred.` : `Validation error: ${parsed.error.message}`;
|
|
1125
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
1126
|
+
}
|
|
1127
|
+
const validated = parsed.data;
|
|
1122
1128
|
logger.info("[Tool] memory.recap", { repo: validated.repo, limit: validated.limit, offset: validated.offset });
|
|
1123
1129
|
const stats = db2.memories.getStats(validated.owner, validated.repo);
|
|
1124
1130
|
const total = db2.memories.getTotalCount(validated.owner, validated.repo, false, ["task_archive"]);
|
|
@@ -1241,7 +1247,13 @@ function capitalize3(str) {
|
|
|
1241
1247
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1242
1248
|
}
|
|
1243
1249
|
async function handleTaskList(args, storage) {
|
|
1244
|
-
const
|
|
1250
|
+
const parsed = TaskListSchema.safeParse(args);
|
|
1251
|
+
if (!parsed.success) {
|
|
1252
|
+
const missing = parsed.error.issues.filter((i) => i.path.some((p) => p === "owner" || p === "repo")).map((i) => i.message).filter(Boolean);
|
|
1253
|
+
const msg = missing.length > 0 ? `Missing required fields: ${missing.join("; ")}. Pass owner/repo explicitly or configure MCP workspace roots so they can be auto-inferred.` : `Validation error: ${parsed.error.message}`;
|
|
1254
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
1255
|
+
}
|
|
1256
|
+
const validated = parsed.data;
|
|
1245
1257
|
const { owner, repo, status, phase, query, limit, offset, structured: isStructuredRequest = false } = validated;
|
|
1246
1258
|
let statuses = [];
|
|
1247
1259
|
if (status !== "all") {
|
|
@@ -3419,6 +3431,55 @@ async function handleKGBackfill(args, db2) {
|
|
|
3419
3431
|
};
|
|
3420
3432
|
const scanRepos = repo ? [repo] : db2.system.listRepoNavigation().map((r) => r.repo);
|
|
3421
3433
|
stats.reposScanned = scanRepos.length;
|
|
3434
|
+
const pendingOps = [];
|
|
3435
|
+
for (const r of scanRepos) {
|
|
3436
|
+
const currentOwner = owner || "unknown";
|
|
3437
|
+
if (source === "memories" || source === "both") {
|
|
3438
|
+
const rows = db2.db.prepare("SELECT title, content FROM memories WHERE repo = ?").all(r);
|
|
3439
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3440
|
+
const row = rows[i];
|
|
3441
|
+
const text = `${row.content || ""} ${row.title || ""}`;
|
|
3442
|
+
try {
|
|
3443
|
+
const entities = await extractEntities(text);
|
|
3444
|
+
if (entities.length > 0) {
|
|
3445
|
+
pendingOps.push({
|
|
3446
|
+
entities,
|
|
3447
|
+
repo: r,
|
|
3448
|
+
owner: currentOwner,
|
|
3449
|
+
observationText: `Mentioned in memory: ${row.title || "untitled"}`
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
} catch {
|
|
3453
|
+
stats.errors++;
|
|
3454
|
+
}
|
|
3455
|
+
stats.itemsProcessed++;
|
|
3456
|
+
if ((i + 1) % 100 === 0) {
|
|
3457
|
+
logger.info(`[kg-backfill] Processed ${i + 1}/${rows.length} memories in repo "${r}"`);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
if (source === "standards" || source === "both") {
|
|
3462
|
+
const rows = db2.db.prepare("SELECT title, content FROM coding_standards WHERE repo = ?").all(r);
|
|
3463
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3464
|
+
const row = rows[i];
|
|
3465
|
+
const text = `${row.content || ""} ${row.title || ""}`;
|
|
3466
|
+
try {
|
|
3467
|
+
const entities = await extractEntities(text);
|
|
3468
|
+
if (entities.length > 0) {
|
|
3469
|
+
pendingOps.push({
|
|
3470
|
+
entities,
|
|
3471
|
+
repo: r,
|
|
3472
|
+
owner: currentOwner,
|
|
3473
|
+
observationText: `Mentioned in standard: ${row.title || "untitled"}`
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
} catch {
|
|
3477
|
+
stats.errors++;
|
|
3478
|
+
}
|
|
3479
|
+
stats.itemsProcessed++;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3422
3483
|
const insertEntity = db2.db.prepare(
|
|
3423
3484
|
`INSERT OR IGNORE INTO entities (name, type, description, repo, owner, created_at, updated_at)
|
|
3424
3485
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -3429,66 +3490,12 @@ async function handleKGBackfill(args, db2) {
|
|
|
3429
3490
|
);
|
|
3430
3491
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3431
3492
|
const transaction = db2.db.transaction(() => {
|
|
3432
|
-
for (const
|
|
3433
|
-
const
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
const content = `${row.content || ""} ${row.title || ""}`;
|
|
3439
|
-
let entities;
|
|
3440
|
-
try {
|
|
3441
|
-
entities = extractEntities(content);
|
|
3442
|
-
} catch {
|
|
3443
|
-
stats.errors++;
|
|
3444
|
-
continue;
|
|
3445
|
-
}
|
|
3446
|
-
for (const ent of entities) {
|
|
3447
|
-
insertEntity.run(ent.name, ent.type, null, r, currentOwner, now, now);
|
|
3448
|
-
stats.entitiesCreated++;
|
|
3449
|
-
insertObservation.run(
|
|
3450
|
-
randomUUID6(),
|
|
3451
|
-
ent.name,
|
|
3452
|
-
`Mentioned in memory: ${row.title || "untitled"}`,
|
|
3453
|
-
r,
|
|
3454
|
-
currentOwner,
|
|
3455
|
-
now
|
|
3456
|
-
);
|
|
3457
|
-
stats.observationsCreated++;
|
|
3458
|
-
}
|
|
3459
|
-
stats.itemsProcessed++;
|
|
3460
|
-
if ((i + 1) % 100 === 0) {
|
|
3461
|
-
logger.info(`[kg-backfill] Processed ${i + 1}/${rows.length} memories in repo "${r}"`);
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
}
|
|
3465
|
-
if (source === "standards" || source === "both") {
|
|
3466
|
-
const rows = db2.db.prepare("SELECT title, content FROM coding_standards WHERE repo = ?").all(r);
|
|
3467
|
-
for (let i = 0; i < rows.length; i++) {
|
|
3468
|
-
const row = rows[i];
|
|
3469
|
-
const content = `${row.content || ""} ${row.title || ""}`;
|
|
3470
|
-
let entities;
|
|
3471
|
-
try {
|
|
3472
|
-
entities = extractEntities(content);
|
|
3473
|
-
} catch {
|
|
3474
|
-
stats.errors++;
|
|
3475
|
-
continue;
|
|
3476
|
-
}
|
|
3477
|
-
for (const ent of entities) {
|
|
3478
|
-
insertEntity.run(ent.name, ent.type, null, r, currentOwner, now, now);
|
|
3479
|
-
stats.entitiesCreated++;
|
|
3480
|
-
insertObservation.run(
|
|
3481
|
-
randomUUID6(),
|
|
3482
|
-
ent.name,
|
|
3483
|
-
`Mentioned in standard: ${row.title || "untitled"}`,
|
|
3484
|
-
r,
|
|
3485
|
-
currentOwner,
|
|
3486
|
-
now
|
|
3487
|
-
);
|
|
3488
|
-
stats.observationsCreated++;
|
|
3489
|
-
}
|
|
3490
|
-
stats.itemsProcessed++;
|
|
3491
|
-
}
|
|
3493
|
+
for (const op of pendingOps) {
|
|
3494
|
+
for (const ent of op.entities) {
|
|
3495
|
+
insertEntity.run(ent.name, ent.type, null, op.repo, op.owner, now, now);
|
|
3496
|
+
stats.entitiesCreated++;
|
|
3497
|
+
insertObservation.run(randomUUID6(), ent.name, op.observationText, op.repo, op.owner, now);
|
|
3498
|
+
stats.observationsCreated++;
|
|
3492
3499
|
}
|
|
3493
3500
|
}
|
|
3494
3501
|
});
|
|
@@ -3835,8 +3842,8 @@ function registerAllResources(server, store, _vectors, session) {
|
|
|
3835
3842
|
mimeType: "application/json"
|
|
3836
3843
|
},
|
|
3837
3844
|
async (uri, _extra) => {
|
|
3838
|
-
const
|
|
3839
|
-
const payload = JSON.stringify(
|
|
3845
|
+
const repos = db2.system.listRepoNavigation();
|
|
3846
|
+
const payload = JSON.stringify(repos, null, 2);
|
|
3840
3847
|
return {
|
|
3841
3848
|
contents: [
|
|
3842
3849
|
{
|
|
@@ -3869,15 +3876,29 @@ function registerAllResources(server, store, _vectors, session) {
|
|
|
3869
3876
|
};
|
|
3870
3877
|
}
|
|
3871
3878
|
);
|
|
3872
|
-
|
|
3873
|
-
|
|
3879
|
+
function reposLazy() {
|
|
3880
|
+
let cached = null;
|
|
3881
|
+
return () => {
|
|
3882
|
+
if (cached === null) cached = getRepos();
|
|
3883
|
+
return cached;
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
function tagsLazy() {
|
|
3887
|
+
let cached = null;
|
|
3888
|
+
return () => {
|
|
3889
|
+
if (cached === null) cached = getTags();
|
|
3890
|
+
return cached;
|
|
3891
|
+
};
|
|
3892
|
+
}
|
|
3893
|
+
const reposFn = reposLazy();
|
|
3894
|
+
const tagsFn = tagsLazy();
|
|
3874
3895
|
server.registerResource(
|
|
3875
3896
|
"repository-memories",
|
|
3876
3897
|
new ResourceTemplate("repository://{name}/memories{?search,type,tag,limit,offset}", {
|
|
3877
3898
|
list: void 0,
|
|
3878
3899
|
complete: {
|
|
3879
|
-
name: async (value) => rankCompletionValues(
|
|
3880
|
-
tag: async (value) => rankCompletionValues(
|
|
3900
|
+
name: async (value) => rankCompletionValues(reposFn(), value),
|
|
3901
|
+
tag: async (value) => rankCompletionValues(tagsFn(), value)
|
|
3881
3902
|
}
|
|
3882
3903
|
}),
|
|
3883
3904
|
{
|
|
@@ -3941,7 +3962,7 @@ function registerAllResources(server, store, _vectors, session) {
|
|
|
3941
3962
|
new ResourceTemplate("repository://{name}/tasks{?status,priority,limit,offset}", {
|
|
3942
3963
|
list: void 0,
|
|
3943
3964
|
complete: {
|
|
3944
|
-
name: async (value) => rankCompletionValues(
|
|
3965
|
+
name: async (value) => rankCompletionValues(reposFn(), value)
|
|
3945
3966
|
}
|
|
3946
3967
|
}),
|
|
3947
3968
|
{
|
|
@@ -4010,7 +4031,7 @@ function registerAllResources(server, store, _vectors, session) {
|
|
|
4010
4031
|
new ResourceTemplate("repository://{name}/summary", {
|
|
4011
4032
|
list: void 0,
|
|
4012
4033
|
complete: {
|
|
4013
|
-
name: async (value) => rankCompletionValues(
|
|
4034
|
+
name: async (value) => rankCompletionValues(reposFn(), value)
|
|
4014
4035
|
}
|
|
4015
4036
|
}),
|
|
4016
4037
|
{
|
|
@@ -4038,7 +4059,7 @@ function registerAllResources(server, store, _vectors, session) {
|
|
|
4038
4059
|
new ResourceTemplate("repository://{name}/actions{?limit,offset}", {
|
|
4039
4060
|
list: void 0,
|
|
4040
4061
|
complete: {
|
|
4041
|
-
name: async (value) => rankCompletionValues(
|
|
4062
|
+
name: async (value) => rankCompletionValues(reposFn(), value)
|
|
4042
4063
|
}
|
|
4043
4064
|
}),
|
|
4044
4065
|
{
|