@vheins/local-memory-mcp 0.19.14 → 0.19.15
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.
|
@@ -1748,7 +1748,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1748
1748
|
handleDuplicateTaskCode(err, taskCode, repo) {
|
|
1749
1749
|
if (isSqliteError(err) && err.code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
1750
1750
|
throw new Error(
|
|
1751
|
-
`Duplicate task_code: '${taskCode}' already exists in repository '${repo}'. The task_code must be unique within the repository.`
|
|
1751
|
+
`Duplicate task_code: '${taskCode}' already exists in repository '${repo}'. The task_code must be unique within the repository. Omit task_code to auto-generate a new unique code.`
|
|
1752
1752
|
);
|
|
1753
1753
|
}
|
|
1754
1754
|
throw err;
|
|
@@ -3462,10 +3462,94 @@ var RealVectorStore = class {
|
|
|
3462
3462
|
}
|
|
3463
3463
|
};
|
|
3464
3464
|
|
|
3465
|
-
// src/mcp/
|
|
3465
|
+
// src/mcp/prompts/loader.ts
|
|
3466
3466
|
import fs4 from "fs";
|
|
3467
3467
|
import path3 from "path";
|
|
3468
3468
|
import { fileURLToPath } from "url";
|
|
3469
|
+
import matter from "gray-matter";
|
|
3470
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
3471
|
+
var __dirname = path3.dirname(__filename);
|
|
3472
|
+
function findPromptDir() {
|
|
3473
|
+
const candidates = [
|
|
3474
|
+
// Production if chunked into dist/
|
|
3475
|
+
"./prompts",
|
|
3476
|
+
// Production if inlined into dist/mcp/
|
|
3477
|
+
"../prompts",
|
|
3478
|
+
// Dev: /src/mcp/prompts/definitions (next to loader.ts)
|
|
3479
|
+
"./definitions"
|
|
3480
|
+
].map((relPath) => path3.resolve(__dirname, relPath));
|
|
3481
|
+
for (const dir of candidates) {
|
|
3482
|
+
if (fs4.existsSync(dir)) {
|
|
3483
|
+
const files = fs4.readdirSync(dir);
|
|
3484
|
+
if (files.some((f) => f.endsWith(".md"))) {
|
|
3485
|
+
return dir;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
return path3.resolve(__dirname, "./definitions");
|
|
3490
|
+
}
|
|
3491
|
+
var PROMPT_DIR = findPromptDir();
|
|
3492
|
+
var promptCache = /* @__PURE__ */ new Map();
|
|
3493
|
+
function listPromptFiles() {
|
|
3494
|
+
if (!fs4.existsSync(PROMPT_DIR)) return [];
|
|
3495
|
+
return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|
|
3496
|
+
}
|
|
3497
|
+
function loadPromptFromMarkdown(name) {
|
|
3498
|
+
const cached = promptCache.get(name);
|
|
3499
|
+
if (cached) return cached;
|
|
3500
|
+
const filePath = path3.join(PROMPT_DIR, `${name}.md`);
|
|
3501
|
+
if (!fs4.existsSync(filePath)) {
|
|
3502
|
+
throw new Error(`Prompt file not found: ${filePath}`);
|
|
3503
|
+
}
|
|
3504
|
+
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3505
|
+
const { data, content } = matter(fileContent);
|
|
3506
|
+
const result = {
|
|
3507
|
+
name: data.name || name,
|
|
3508
|
+
description: data.description || "",
|
|
3509
|
+
arguments: data.arguments || [],
|
|
3510
|
+
agent: data.agent,
|
|
3511
|
+
content: content.trim()
|
|
3512
|
+
};
|
|
3513
|
+
promptCache.set(name, result);
|
|
3514
|
+
return result;
|
|
3515
|
+
}
|
|
3516
|
+
function findServerInstructionsDir() {
|
|
3517
|
+
const candidates = [
|
|
3518
|
+
// Production if chunked into dist/
|
|
3519
|
+
"./prompts/server",
|
|
3520
|
+
// Production if inlined into dist/mcp/
|
|
3521
|
+
"../prompts/server",
|
|
3522
|
+
// Dev: /src/mcp/prompts/server (next to loader.ts)
|
|
3523
|
+
"./server"
|
|
3524
|
+
].map((relPath) => path3.resolve(__dirname, relPath));
|
|
3525
|
+
for (const dir of candidates) {
|
|
3526
|
+
if (fs4.existsSync(dir)) {
|
|
3527
|
+
const filePath = path3.join(dir, "instructions.md");
|
|
3528
|
+
if (fs4.existsSync(filePath)) {
|
|
3529
|
+
return dir;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return path3.resolve(__dirname, "./server");
|
|
3534
|
+
}
|
|
3535
|
+
var SERVER_DIR = findServerInstructionsDir();
|
|
3536
|
+
var serverInstructionsCache = null;
|
|
3537
|
+
function loadServerInstructions() {
|
|
3538
|
+
if (serverInstructionsCache !== null) return serverInstructionsCache;
|
|
3539
|
+
const filePath = path3.join(SERVER_DIR, "instructions.md");
|
|
3540
|
+
if (!fs4.existsSync(filePath)) {
|
|
3541
|
+
throw new Error(`Server instructions file not found: ${filePath}`);
|
|
3542
|
+
}
|
|
3543
|
+
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3544
|
+
const { content } = matter(fileContent);
|
|
3545
|
+
serverInstructionsCache = content.trim();
|
|
3546
|
+
return serverInstructionsCache;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
// src/mcp/session.ts
|
|
3550
|
+
import fs5 from "fs";
|
|
3551
|
+
import path4 from "path";
|
|
3552
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3469
3553
|
function createSessionContext() {
|
|
3470
3554
|
return {
|
|
3471
3555
|
roots: [],
|
|
@@ -3483,7 +3567,7 @@ function getFilesystemRoots(session) {
|
|
|
3483
3567
|
for (const root of session.roots) {
|
|
3484
3568
|
if (!root.uri.startsWith("file://")) continue;
|
|
3485
3569
|
try {
|
|
3486
|
-
resolved.push(
|
|
3570
|
+
resolved.push(path4.resolve(fileURLToPath2(root.uri)));
|
|
3487
3571
|
} catch {
|
|
3488
3572
|
}
|
|
3489
3573
|
}
|
|
@@ -3492,19 +3576,19 @@ function getFilesystemRoots(session) {
|
|
|
3492
3576
|
function isPathWithinRoots(targetPath, session) {
|
|
3493
3577
|
const roots = getFilesystemRoots(session);
|
|
3494
3578
|
if (roots.length === 0) return true;
|
|
3495
|
-
const normalizedTarget =
|
|
3579
|
+
const normalizedTarget = path4.resolve(targetPath);
|
|
3496
3580
|
return roots.some((rootPath) => {
|
|
3497
|
-
const relative =
|
|
3498
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
3581
|
+
const relative = path4.relative(rootPath, normalizedTarget);
|
|
3582
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
3499
3583
|
});
|
|
3500
3584
|
}
|
|
3501
3585
|
function findContainingRoot(targetPath, session) {
|
|
3502
3586
|
const roots = getFilesystemRoots(session);
|
|
3503
3587
|
if (roots.length === 0) return null;
|
|
3504
|
-
const normalizedTarget =
|
|
3588
|
+
const normalizedTarget = path4.resolve(targetPath);
|
|
3505
3589
|
for (const rootPath of roots) {
|
|
3506
|
-
const relative =
|
|
3507
|
-
if (relative === "" || !relative.startsWith("..") && !
|
|
3590
|
+
const relative = path4.relative(rootPath, normalizedTarget);
|
|
3591
|
+
if (relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative)) {
|
|
3508
3592
|
return rootPath;
|
|
3509
3593
|
}
|
|
3510
3594
|
}
|
|
@@ -3513,20 +3597,20 @@ function findContainingRoot(targetPath, session) {
|
|
|
3513
3597
|
function inferRepoFromSession(session) {
|
|
3514
3598
|
const roots = getFilesystemRoots(session);
|
|
3515
3599
|
if (roots.length === 1) {
|
|
3516
|
-
return
|
|
3600
|
+
return path4.basename(roots[0]);
|
|
3517
3601
|
}
|
|
3518
3602
|
if (roots.length === 0) {
|
|
3519
3603
|
if (!session) return void 0;
|
|
3520
3604
|
const cwd = process.cwd();
|
|
3521
|
-
return
|
|
3605
|
+
return path4.basename(cwd);
|
|
3522
3606
|
}
|
|
3523
3607
|
return void 0;
|
|
3524
3608
|
}
|
|
3525
3609
|
function inferOwnerFromGit(cwd) {
|
|
3526
3610
|
try {
|
|
3527
|
-
const gitConfigPath =
|
|
3528
|
-
if (!
|
|
3529
|
-
const content =
|
|
3611
|
+
const gitConfigPath = path4.join(cwd, ".git", "config");
|
|
3612
|
+
if (!fs5.existsSync(gitConfigPath)) return void 0;
|
|
3613
|
+
const content = fs5.readFileSync(gitConfigPath, "utf-8");
|
|
3530
3614
|
const match = content.match(
|
|
3531
3615
|
/url\s*=\s*(?:git@github\.com:|https?:\/\/github\.com\/|git:\/\/github\.com\/)([^\/\s]+)/
|
|
3532
3616
|
);
|
|
@@ -3548,7 +3632,7 @@ function inferOwnerFromSession(session) {
|
|
|
3548
3632
|
if (gitOwner) return gitOwner;
|
|
3549
3633
|
}
|
|
3550
3634
|
if (roots.length === 1) {
|
|
3551
|
-
const parts = roots[0].split(
|
|
3635
|
+
const parts = roots[0].split(path4.sep).filter(Boolean);
|
|
3552
3636
|
if (parts.length >= 2) {
|
|
3553
3637
|
return parts[parts.length - 2];
|
|
3554
3638
|
}
|
|
@@ -3556,7 +3640,7 @@ function inferOwnerFromSession(session) {
|
|
|
3556
3640
|
if (roots.length === 0) {
|
|
3557
3641
|
if (!session) return void 0;
|
|
3558
3642
|
const workdir = process.cwd();
|
|
3559
|
-
const parts = workdir.split(
|
|
3643
|
+
const parts = workdir.split(path4.sep).filter(Boolean);
|
|
3560
3644
|
if (parts.length >= 2) {
|
|
3561
3645
|
return parts[parts.length - 2];
|
|
3562
3646
|
}
|
|
@@ -3564,90 +3648,6 @@ function inferOwnerFromSession(session) {
|
|
|
3564
3648
|
return void 0;
|
|
3565
3649
|
}
|
|
3566
3650
|
|
|
3567
|
-
// src/mcp/prompts/loader.ts
|
|
3568
|
-
import fs5 from "fs";
|
|
3569
|
-
import path4 from "path";
|
|
3570
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3571
|
-
import matter from "gray-matter";
|
|
3572
|
-
var __filename = fileURLToPath2(import.meta.url);
|
|
3573
|
-
var __dirname = path4.dirname(__filename);
|
|
3574
|
-
function findPromptDir() {
|
|
3575
|
-
const candidates = [
|
|
3576
|
-
// Production if chunked into dist/
|
|
3577
|
-
"./prompts",
|
|
3578
|
-
// Production if inlined into dist/mcp/
|
|
3579
|
-
"../prompts",
|
|
3580
|
-
// Dev: /src/mcp/prompts/definitions (next to loader.ts)
|
|
3581
|
-
"./definitions"
|
|
3582
|
-
].map((relPath) => path4.resolve(__dirname, relPath));
|
|
3583
|
-
for (const dir of candidates) {
|
|
3584
|
-
if (fs5.existsSync(dir)) {
|
|
3585
|
-
const files = fs5.readdirSync(dir);
|
|
3586
|
-
if (files.some((f) => f.endsWith(".md"))) {
|
|
3587
|
-
return dir;
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
|
-
return path4.resolve(__dirname, "./definitions");
|
|
3592
|
-
}
|
|
3593
|
-
var PROMPT_DIR = findPromptDir();
|
|
3594
|
-
var promptCache = /* @__PURE__ */ new Map();
|
|
3595
|
-
function listPromptFiles() {
|
|
3596
|
-
if (!fs5.existsSync(PROMPT_DIR)) return [];
|
|
3597
|
-
return fs5.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|
|
3598
|
-
}
|
|
3599
|
-
function loadPromptFromMarkdown(name) {
|
|
3600
|
-
const cached = promptCache.get(name);
|
|
3601
|
-
if (cached) return cached;
|
|
3602
|
-
const filePath = path4.join(PROMPT_DIR, `${name}.md`);
|
|
3603
|
-
if (!fs5.existsSync(filePath)) {
|
|
3604
|
-
throw new Error(`Prompt file not found: ${filePath}`);
|
|
3605
|
-
}
|
|
3606
|
-
const fileContent = fs5.readFileSync(filePath, "utf-8");
|
|
3607
|
-
const { data, content } = matter(fileContent);
|
|
3608
|
-
const result = {
|
|
3609
|
-
name: data.name || name,
|
|
3610
|
-
description: data.description || "",
|
|
3611
|
-
arguments: data.arguments || [],
|
|
3612
|
-
agent: data.agent,
|
|
3613
|
-
content: content.trim()
|
|
3614
|
-
};
|
|
3615
|
-
promptCache.set(name, result);
|
|
3616
|
-
return result;
|
|
3617
|
-
}
|
|
3618
|
-
function findServerInstructionsDir() {
|
|
3619
|
-
const candidates = [
|
|
3620
|
-
// Production if chunked into dist/
|
|
3621
|
-
"./prompts/server",
|
|
3622
|
-
// Production if inlined into dist/mcp/
|
|
3623
|
-
"../prompts/server",
|
|
3624
|
-
// Dev: /src/mcp/prompts/server (next to loader.ts)
|
|
3625
|
-
"./server"
|
|
3626
|
-
].map((relPath) => path4.resolve(__dirname, relPath));
|
|
3627
|
-
for (const dir of candidates) {
|
|
3628
|
-
if (fs5.existsSync(dir)) {
|
|
3629
|
-
const filePath = path4.join(dir, "instructions.md");
|
|
3630
|
-
if (fs5.existsSync(filePath)) {
|
|
3631
|
-
return dir;
|
|
3632
|
-
}
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
return path4.resolve(__dirname, "./server");
|
|
3636
|
-
}
|
|
3637
|
-
var SERVER_DIR = findServerInstructionsDir();
|
|
3638
|
-
var serverInstructionsCache = null;
|
|
3639
|
-
function loadServerInstructions() {
|
|
3640
|
-
if (serverInstructionsCache !== null) return serverInstructionsCache;
|
|
3641
|
-
const filePath = path4.join(SERVER_DIR, "instructions.md");
|
|
3642
|
-
if (!fs5.existsSync(filePath)) {
|
|
3643
|
-
throw new Error(`Server instructions file not found: ${filePath}`);
|
|
3644
|
-
}
|
|
3645
|
-
const fileContent = fs5.readFileSync(filePath, "utf-8");
|
|
3646
|
-
const { content } = matter(fileContent);
|
|
3647
|
-
serverInstructionsCache = content.trim();
|
|
3648
|
-
return serverInstructionsCache;
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
3651
|
// src/mcp/tools/definitions/memory.ts
|
|
3652
3652
|
var MEMORY_TOOL_DEFINITIONS = [
|
|
3653
3653
|
{
|
|
@@ -4442,7 +4442,10 @@ var TASK_TOOL_DEFINITIONS = [
|
|
|
4442
4442
|
type: "string",
|
|
4443
4443
|
description: "Repository/project name (e.g., 'local-memory-mcp'). Auto-inferred from session when omitted."
|
|
4444
4444
|
},
|
|
4445
|
-
task_code: {
|
|
4445
|
+
task_code: {
|
|
4446
|
+
type: "string",
|
|
4447
|
+
description: "Unique task code (e.g. TASK-001, REFACTOR-2). Optional \u2014 auto-generated as TASK-xxx if omitted."
|
|
4448
|
+
},
|
|
4446
4449
|
phase: { type: "string", description: "Project phase (Required for single task)" },
|
|
4447
4450
|
title: {
|
|
4448
4451
|
type: "string",
|
|
@@ -6333,164 +6336,6 @@ var TOOL_DEFINITIONS = [
|
|
|
6333
6336
|
...KG_TOOL_DEFINITIONS
|
|
6334
6337
|
];
|
|
6335
6338
|
|
|
6336
|
-
// src/mcp/utils/completion.ts
|
|
6337
|
-
var MAX_COMPLETION_VALUES = 100;
|
|
6338
|
-
function rankCompletionValues(candidates, input) {
|
|
6339
|
-
const unique = [...new Set(candidates.filter(Boolean))];
|
|
6340
|
-
const needle = input.trim().toLowerCase();
|
|
6341
|
-
if (!needle) {
|
|
6342
|
-
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
6343
|
-
}
|
|
6344
|
-
return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
|
|
6345
|
-
}
|
|
6346
|
-
function scoreCompletionValue(value, needle) {
|
|
6347
|
-
const haystack = value.toLowerCase();
|
|
6348
|
-
if (haystack === needle) return 100;
|
|
6349
|
-
if (haystack.startsWith(needle)) return 75;
|
|
6350
|
-
if (haystack.includes(needle)) return 50;
|
|
6351
|
-
const compactNeedle = needle.replace(/[\s_-]+/g, "");
|
|
6352
|
-
const compactHaystack = haystack.replace(/[\s_-]+/g, "");
|
|
6353
|
-
if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
|
|
6354
|
-
return 0;
|
|
6355
|
-
}
|
|
6356
|
-
|
|
6357
|
-
// src/mcp/utils/pagination.ts
|
|
6358
|
-
function encodeCursor(offset) {
|
|
6359
|
-
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
6360
|
-
}
|
|
6361
|
-
function decodeCursor(cursor) {
|
|
6362
|
-
if (cursor === void 0 || cursor === null || cursor === "") {
|
|
6363
|
-
return 0;
|
|
6364
|
-
}
|
|
6365
|
-
if (typeof cursor !== "string" || cursor.trim() === "") {
|
|
6366
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
6367
|
-
}
|
|
6368
|
-
let decoded;
|
|
6369
|
-
try {
|
|
6370
|
-
decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
6371
|
-
} catch {
|
|
6372
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
6373
|
-
}
|
|
6374
|
-
if (!/^\d+$/.test(decoded)) {
|
|
6375
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
6376
|
-
}
|
|
6377
|
-
const offset = Number.parseInt(decoded, 10);
|
|
6378
|
-
if (!Number.isFinite(offset) || offset < 0) {
|
|
6379
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
6380
|
-
}
|
|
6381
|
-
return offset;
|
|
6382
|
-
}
|
|
6383
|
-
function invalidPaginationParams(message) {
|
|
6384
|
-
const error = new Error(message);
|
|
6385
|
-
error.code = -32602;
|
|
6386
|
-
return error;
|
|
6387
|
-
}
|
|
6388
|
-
|
|
6389
|
-
// src/mcp/resources/index.ts
|
|
6390
|
-
var DEFAULT_PAGE_SIZE = 25;
|
|
6391
|
-
var MAX_PAGE_SIZE = 100;
|
|
6392
|
-
function listResources(session, params) {
|
|
6393
|
-
const resources = [
|
|
6394
|
-
{
|
|
6395
|
-
uri: "repository://index",
|
|
6396
|
-
name: "Repository Index",
|
|
6397
|
-
title: "Repository Index",
|
|
6398
|
-
description: "List of all known repositories with memory/task counts and last activity",
|
|
6399
|
-
mimeType: "application/json",
|
|
6400
|
-
annotations: {
|
|
6401
|
-
audience: ["assistant"],
|
|
6402
|
-
priority: 1,
|
|
6403
|
-
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
6404
|
-
}
|
|
6405
|
-
},
|
|
6406
|
-
{
|
|
6407
|
-
uri: "session://roots",
|
|
6408
|
-
name: "Session Roots",
|
|
6409
|
-
title: "Session Roots",
|
|
6410
|
-
description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
|
|
6411
|
-
mimeType: "application/json",
|
|
6412
|
-
size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
|
|
6413
|
-
annotations: {
|
|
6414
|
-
audience: ["assistant"],
|
|
6415
|
-
priority: 0.95,
|
|
6416
|
-
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
6417
|
-
}
|
|
6418
|
-
}
|
|
6419
|
-
];
|
|
6420
|
-
return paginateEntries("resources", resources, params);
|
|
6421
|
-
}
|
|
6422
|
-
function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
|
|
6423
|
-
if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
|
|
6424
|
-
if (argumentName === "name") {
|
|
6425
|
-
return rankCompletionValues(dataSources.repos, argumentValue);
|
|
6426
|
-
}
|
|
6427
|
-
}
|
|
6428
|
-
if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
|
|
6429
|
-
if (argumentName === "tag") {
|
|
6430
|
-
return rankCompletionValues(dataSources.tags, argumentValue);
|
|
6431
|
-
}
|
|
6432
|
-
}
|
|
6433
|
-
throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
|
|
6434
|
-
}
|
|
6435
|
-
function paginateEntries(key, entries, params) {
|
|
6436
|
-
const limit = normalizeLimit(params?.limit);
|
|
6437
|
-
const offset = decodeCursor(params?.cursor);
|
|
6438
|
-
const sliced = entries.slice(offset, offset + limit);
|
|
6439
|
-
const nextOffset = offset + sliced.length;
|
|
6440
|
-
return {
|
|
6441
|
-
[key]: sliced,
|
|
6442
|
-
nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
|
|
6443
|
-
};
|
|
6444
|
-
}
|
|
6445
|
-
function normalizeLimit(limit) {
|
|
6446
|
-
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
6447
|
-
return DEFAULT_PAGE_SIZE;
|
|
6448
|
-
}
|
|
6449
|
-
return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
|
|
6450
|
-
}
|
|
6451
|
-
function invalidCompletionParams(message) {
|
|
6452
|
-
const error = new Error(message);
|
|
6453
|
-
error.code = -32602;
|
|
6454
|
-
return error;
|
|
6455
|
-
}
|
|
6456
|
-
|
|
6457
|
-
// src/mcp/prompts/registry.ts
|
|
6458
|
-
function createPromptDefinition(loaded) {
|
|
6459
|
-
return {
|
|
6460
|
-
name: loaded.name,
|
|
6461
|
-
description: loaded.description,
|
|
6462
|
-
arguments: loaded.arguments,
|
|
6463
|
-
agent: loaded.agent,
|
|
6464
|
-
messages: [
|
|
6465
|
-
{
|
|
6466
|
-
role: "user",
|
|
6467
|
-
content: {
|
|
6468
|
-
type: "text",
|
|
6469
|
-
text: loaded.content
|
|
6470
|
-
}
|
|
6471
|
-
}
|
|
6472
|
-
]
|
|
6473
|
-
};
|
|
6474
|
-
}
|
|
6475
|
-
var PROMPTS = {};
|
|
6476
|
-
var promptFiles = listPromptFiles();
|
|
6477
|
-
for (const name of promptFiles) {
|
|
6478
|
-
try {
|
|
6479
|
-
PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
|
|
6480
|
-
} catch (e) {
|
|
6481
|
-
logger.warn(`Failed to load prompt ${name}: ${e}`);
|
|
6482
|
-
}
|
|
6483
|
-
}
|
|
6484
|
-
async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
|
|
6485
|
-
void name;
|
|
6486
|
-
void contextArguments;
|
|
6487
|
-
if (argName === "task_id") {
|
|
6488
|
-
const values = dataSources.tasks.map((t) => t.id);
|
|
6489
|
-
return rankCompletionValues(values, value);
|
|
6490
|
-
}
|
|
6491
|
-
return [];
|
|
6492
|
-
}
|
|
6493
|
-
|
|
6494
6339
|
// src/mcp/tools/handoff.manage.ts
|
|
6495
6340
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6496
6341
|
|
|
@@ -7527,6 +7372,164 @@ async function handleClaimRelease(args, storage) {
|
|
|
7527
7372
|
});
|
|
7528
7373
|
}
|
|
7529
7374
|
|
|
7375
|
+
// src/mcp/utils/completion.ts
|
|
7376
|
+
var MAX_COMPLETION_VALUES = 100;
|
|
7377
|
+
function rankCompletionValues(candidates, input) {
|
|
7378
|
+
const unique = [...new Set(candidates.filter(Boolean))];
|
|
7379
|
+
const needle = input.trim().toLowerCase();
|
|
7380
|
+
if (!needle) {
|
|
7381
|
+
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
7382
|
+
}
|
|
7383
|
+
return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
|
|
7384
|
+
}
|
|
7385
|
+
function scoreCompletionValue(value, needle) {
|
|
7386
|
+
const haystack = value.toLowerCase();
|
|
7387
|
+
if (haystack === needle) return 100;
|
|
7388
|
+
if (haystack.startsWith(needle)) return 75;
|
|
7389
|
+
if (haystack.includes(needle)) return 50;
|
|
7390
|
+
const compactNeedle = needle.replace(/[\s_-]+/g, "");
|
|
7391
|
+
const compactHaystack = haystack.replace(/[\s_-]+/g, "");
|
|
7392
|
+
if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
|
|
7393
|
+
return 0;
|
|
7394
|
+
}
|
|
7395
|
+
|
|
7396
|
+
// src/mcp/utils/pagination.ts
|
|
7397
|
+
function encodeCursor(offset) {
|
|
7398
|
+
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
7399
|
+
}
|
|
7400
|
+
function decodeCursor(cursor) {
|
|
7401
|
+
if (cursor === void 0 || cursor === null || cursor === "") {
|
|
7402
|
+
return 0;
|
|
7403
|
+
}
|
|
7404
|
+
if (typeof cursor !== "string" || cursor.trim() === "") {
|
|
7405
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
7406
|
+
}
|
|
7407
|
+
let decoded;
|
|
7408
|
+
try {
|
|
7409
|
+
decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
7410
|
+
} catch {
|
|
7411
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
7412
|
+
}
|
|
7413
|
+
if (!/^\d+$/.test(decoded)) {
|
|
7414
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
7415
|
+
}
|
|
7416
|
+
const offset = Number.parseInt(decoded, 10);
|
|
7417
|
+
if (!Number.isFinite(offset) || offset < 0) {
|
|
7418
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
7419
|
+
}
|
|
7420
|
+
return offset;
|
|
7421
|
+
}
|
|
7422
|
+
function invalidPaginationParams(message) {
|
|
7423
|
+
const error = new Error(message);
|
|
7424
|
+
error.code = -32602;
|
|
7425
|
+
return error;
|
|
7426
|
+
}
|
|
7427
|
+
|
|
7428
|
+
// src/mcp/prompts/registry.ts
|
|
7429
|
+
function createPromptDefinition(loaded) {
|
|
7430
|
+
return {
|
|
7431
|
+
name: loaded.name,
|
|
7432
|
+
description: loaded.description,
|
|
7433
|
+
arguments: loaded.arguments,
|
|
7434
|
+
agent: loaded.agent,
|
|
7435
|
+
messages: [
|
|
7436
|
+
{
|
|
7437
|
+
role: "user",
|
|
7438
|
+
content: {
|
|
7439
|
+
type: "text",
|
|
7440
|
+
text: loaded.content
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
]
|
|
7444
|
+
};
|
|
7445
|
+
}
|
|
7446
|
+
var PROMPTS = {};
|
|
7447
|
+
var promptFiles = listPromptFiles();
|
|
7448
|
+
for (const name of promptFiles) {
|
|
7449
|
+
try {
|
|
7450
|
+
PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
|
|
7451
|
+
} catch (e) {
|
|
7452
|
+
logger.warn(`Failed to load prompt ${name}: ${e}`);
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
|
|
7456
|
+
void name;
|
|
7457
|
+
void contextArguments;
|
|
7458
|
+
if (argName === "task_id") {
|
|
7459
|
+
const values = dataSources.tasks.map((t) => t.id);
|
|
7460
|
+
return rankCompletionValues(values, value);
|
|
7461
|
+
}
|
|
7462
|
+
return [];
|
|
7463
|
+
}
|
|
7464
|
+
|
|
7465
|
+
// src/mcp/resources/index.ts
|
|
7466
|
+
var DEFAULT_PAGE_SIZE = 25;
|
|
7467
|
+
var MAX_PAGE_SIZE = 100;
|
|
7468
|
+
function listResources(session, params) {
|
|
7469
|
+
const resources = [
|
|
7470
|
+
{
|
|
7471
|
+
uri: "repository://index",
|
|
7472
|
+
name: "Repository Index",
|
|
7473
|
+
title: "Repository Index",
|
|
7474
|
+
description: "List of all known repositories with memory/task counts and last activity",
|
|
7475
|
+
mimeType: "application/json",
|
|
7476
|
+
annotations: {
|
|
7477
|
+
audience: ["assistant"],
|
|
7478
|
+
priority: 1,
|
|
7479
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
7480
|
+
}
|
|
7481
|
+
},
|
|
7482
|
+
{
|
|
7483
|
+
uri: "session://roots",
|
|
7484
|
+
name: "Session Roots",
|
|
7485
|
+
title: "Session Roots",
|
|
7486
|
+
description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
|
|
7487
|
+
mimeType: "application/json",
|
|
7488
|
+
size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
|
|
7489
|
+
annotations: {
|
|
7490
|
+
audience: ["assistant"],
|
|
7491
|
+
priority: 0.95,
|
|
7492
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
];
|
|
7496
|
+
return paginateEntries("resources", resources, params);
|
|
7497
|
+
}
|
|
7498
|
+
function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
|
|
7499
|
+
if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
|
|
7500
|
+
if (argumentName === "name") {
|
|
7501
|
+
return rankCompletionValues(dataSources.repos, argumentValue);
|
|
7502
|
+
}
|
|
7503
|
+
}
|
|
7504
|
+
if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
|
|
7505
|
+
if (argumentName === "tag") {
|
|
7506
|
+
return rankCompletionValues(dataSources.tags, argumentValue);
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
|
|
7510
|
+
}
|
|
7511
|
+
function paginateEntries(key, entries, params) {
|
|
7512
|
+
const limit = normalizeLimit(params?.limit);
|
|
7513
|
+
const offset = decodeCursor(params?.cursor);
|
|
7514
|
+
const sliced = entries.slice(offset, offset + limit);
|
|
7515
|
+
const nextOffset = offset + sliced.length;
|
|
7516
|
+
return {
|
|
7517
|
+
[key]: sliced,
|
|
7518
|
+
nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
|
|
7519
|
+
};
|
|
7520
|
+
}
|
|
7521
|
+
function normalizeLimit(limit) {
|
|
7522
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
7523
|
+
return DEFAULT_PAGE_SIZE;
|
|
7524
|
+
}
|
|
7525
|
+
return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
|
|
7526
|
+
}
|
|
7527
|
+
function invalidCompletionParams(message) {
|
|
7528
|
+
const error = new Error(message);
|
|
7529
|
+
error.code = -32602;
|
|
7530
|
+
return error;
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7530
7533
|
// src/mcp/tools/standard.shared.ts
|
|
7531
7534
|
function toContextSlug(value) {
|
|
7532
7535
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -7545,30 +7548,20 @@ function buildStandardVectorText(standard) {
|
|
|
7545
7548
|
}
|
|
7546
7549
|
|
|
7547
7550
|
export {
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
parseRepoInput,
|
|
7552
|
-
normalizeRepo,
|
|
7553
|
-
SQLiteStore,
|
|
7554
|
-
RealVectorStore,
|
|
7555
|
-
TOOL_DEFINITIONS,
|
|
7556
|
-
rankCompletionValues,
|
|
7557
|
-
listResources,
|
|
7558
|
-
completeResourceArgument,
|
|
7551
|
+
listPromptFiles,
|
|
7552
|
+
loadPromptFromMarkdown,
|
|
7553
|
+
loadServerInstructions,
|
|
7559
7554
|
createSessionContext,
|
|
7560
7555
|
getFilesystemRoots,
|
|
7561
7556
|
isPathWithinRoots,
|
|
7562
7557
|
findContainingRoot,
|
|
7563
7558
|
inferRepoFromSession,
|
|
7564
7559
|
inferOwnerFromSession,
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
createMcpResponse,
|
|
7571
|
-
getPrimaryTextContent,
|
|
7560
|
+
logger,
|
|
7561
|
+
addLogSink,
|
|
7562
|
+
createFileSink,
|
|
7563
|
+
parseRepoInput,
|
|
7564
|
+
normalizeRepo,
|
|
7572
7565
|
TaskStatusSchema,
|
|
7573
7566
|
MemoryStoreSchema,
|
|
7574
7567
|
MemoryUpdateSchema,
|
|
@@ -7600,6 +7593,9 @@ export {
|
|
|
7600
7593
|
DeleteRelationSchema,
|
|
7601
7594
|
DeleteObservationSchema,
|
|
7602
7595
|
KGBackfillSchema,
|
|
7596
|
+
TOOL_DEFINITIONS,
|
|
7597
|
+
createMcpResponse,
|
|
7598
|
+
getPrimaryTextContent,
|
|
7603
7599
|
UUID_REGEX,
|
|
7604
7600
|
handleHandoffCreate,
|
|
7605
7601
|
handleHandoffList,
|
|
@@ -7608,5 +7604,12 @@ export {
|
|
|
7608
7604
|
handleClaimList,
|
|
7609
7605
|
handleClaimRelease,
|
|
7610
7606
|
toContextSlug,
|
|
7611
|
-
buildStandardVectorText
|
|
7607
|
+
buildStandardVectorText,
|
|
7608
|
+
rankCompletionValues,
|
|
7609
|
+
PROMPTS,
|
|
7610
|
+
completePromptArgument,
|
|
7611
|
+
listResources,
|
|
7612
|
+
completeResourceArgument,
|
|
7613
|
+
SQLiteStore,
|
|
7614
|
+
RealVectorStore
|
|
7612
7615
|
};
|
package/dist/dashboard/server.js
CHANGED
package/dist/mcp/server.js
CHANGED
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
parseRepoInput,
|
|
63
63
|
rankCompletionValues,
|
|
64
64
|
toContextSlug
|
|
65
|
-
} from "../chunk-
|
|
65
|
+
} from "../chunk-BPQ42JEQ.js";
|
|
66
66
|
|
|
67
67
|
// src/mcp/server.ts
|
|
68
68
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -75,8 +75,8 @@ import path from "path";
|
|
|
75
75
|
import { fileURLToPath } from "url";
|
|
76
76
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
77
77
|
var pkgVersion = "0.1.0";
|
|
78
|
-
if ("0.19.
|
|
79
|
-
pkgVersion = "0.19.
|
|
78
|
+
if ("0.19.15") {
|
|
79
|
+
pkgVersion = "0.19.15";
|
|
80
80
|
} else {
|
|
81
81
|
let searchDir = __dirname;
|
|
82
82
|
for (let i = 0; i < 5; i++) {
|
|
@@ -2391,7 +2391,9 @@ async function handleTaskCreate(args, storage) {
|
|
|
2391
2391
|
throw new Error(`Duplicate task_code in request: '${code}'`);
|
|
2392
2392
|
}
|
|
2393
2393
|
if (existingCodes.has(code)) {
|
|
2394
|
-
throw new Error(
|
|
2394
|
+
throw new Error(
|
|
2395
|
+
`Duplicate task_code: '${code}' already exists in repository '${repo}'. Omit task_code to auto-generate, or use task-list/task-search to find available codes.`
|
|
2396
|
+
);
|
|
2395
2397
|
}
|
|
2396
2398
|
codesInRequest.add(code);
|
|
2397
2399
|
let normalizedStatus = taskData.status || "backlog";
|
|
@@ -2472,7 +2474,9 @@ async function handleTaskCreate(args, storage) {
|
|
|
2472
2474
|
let effectiveStatus = requestedStatus || "backlog";
|
|
2473
2475
|
const resolvedCode = task_code || generateNextCode(owner ?? "", repo, "task", storage);
|
|
2474
2476
|
if (storage.tasks.isTaskCodeDuplicate(owner, repo, resolvedCode)) {
|
|
2475
|
-
throw new Error(
|
|
2477
|
+
throw new Error(
|
|
2478
|
+
`Duplicate task_code: '${resolvedCode}' already exists in repository '${repo}'. Omit task_code to auto-generate, or use task-list/task-search to find available codes.`
|
|
2479
|
+
);
|
|
2476
2480
|
}
|
|
2477
2481
|
if (requestedStatus !== "backlog" && requestedStatus !== "pending" && requestedStatus !== void 0) {
|
|
2478
2482
|
throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
|