@vheins/local-memory-mcp 0.19.13 → 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/session.ts
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(path3.resolve(fileURLToPath(root.uri)));
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 = path3.resolve(targetPath);
3579
+ const normalizedTarget = path4.resolve(targetPath);
3496
3580
  return roots.some((rootPath) => {
3497
- const relative = path3.relative(rootPath, normalizedTarget);
3498
- return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
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 = path3.resolve(targetPath);
3588
+ const normalizedTarget = path4.resolve(targetPath);
3505
3589
  for (const rootPath of roots) {
3506
- const relative = path3.relative(rootPath, normalizedTarget);
3507
- if (relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative)) {
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 path3.basename(roots[0]);
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 path3.basename(cwd);
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 = path3.join(cwd, ".git", "config");
3528
- if (!fs4.existsSync(gitConfigPath)) return void 0;
3529
- const content = fs4.readFileSync(gitConfigPath, "utf-8");
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(path3.sep).filter(Boolean);
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(path3.sep).filter(Boolean);
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: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
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
 
@@ -6690,7 +6535,7 @@ var MemoryStoreSchema = z3.object({
6690
6535
  memories: z3.array(SingleMemorySchema).min(1).optional()
6691
6536
  });
6692
6537
  var MemoryUpdateSchema = z3.object({
6693
- id: z3.string().uuid().optional(),
6538
+ id: z3.string().optional(),
6694
6539
  code: z3.string().max(20).optional(),
6695
6540
  owner: z3.string().min(1),
6696
6541
  repo: z3.string().min(1).transform(normalizeRepo),
@@ -6730,7 +6575,7 @@ var MemorySearchSchema = z3.object({
6730
6575
  structured: z3.boolean().default(false)
6731
6576
  });
6732
6577
  var MemoryAcknowledgeSchema = z3.object({
6733
- memory_id: z3.string().uuid().optional(),
6578
+ memory_id: z3.string().optional(),
6734
6579
  code: z3.string().max(20).optional(),
6735
6580
  owner: z3.string().min(1),
6736
6581
  repo: z3.string().min(1).transform(normalizeRepo),
@@ -6750,8 +6595,8 @@ var MemoryRecapSchema = z3.object({
6750
6595
  var MemoryDeleteSchema = z3.object({
6751
6596
  owner: z3.string().min(1),
6752
6597
  repo: z3.string().min(1).transform(normalizeRepo),
6753
- id: z3.string().uuid().optional(),
6754
- ids: z3.array(z3.string().uuid()).min(1).optional(),
6598
+ id: z3.string().optional(),
6599
+ ids: z3.array(z3.string()).min(1).optional(),
6755
6600
  code: z3.string().max(20).optional(),
6756
6601
  codes: z3.array(z3.string().max(20)).min(1).optional(),
6757
6602
  structured: z3.boolean().default(false)
@@ -6762,7 +6607,7 @@ var MemoryDeleteSchema = z3.object({
6762
6607
  }
6763
6608
  );
6764
6609
  var MemoryDetailSchema = z3.object({
6765
- id: z3.string().uuid().optional(),
6610
+ id: z3.string().optional(),
6766
6611
  code: z3.string().max(20).optional(),
6767
6612
  owner: z3.string().min(1),
6768
6613
  repo: z3.string().min(1).transform(normalizeRepo),
@@ -6899,8 +6744,8 @@ var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
6899
6744
  var TaskUpdateSchema = z4.object({
6900
6745
  owner: z4.string().min(1),
6901
6746
  repo: z4.string().min(1).transform(normalizeRepo),
6902
- id: z4.string().uuid().optional(),
6903
- ids: z4.array(z4.string().uuid()).min(1).optional(),
6747
+ id: z4.string().optional(),
6748
+ ids: z4.array(z4.string()).min(1).optional(),
6904
6749
  task_code: z4.string().optional(),
6905
6750
  task_codes: z4.array(z4.string().min(1)).min(1).optional(),
6906
6751
  phase: z4.string().optional(),
@@ -6926,7 +6771,7 @@ var TaskUpdateSchema = z4.object({
6926
6771
  }).refine(
6927
6772
  (data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0 || data.task_codes !== void 0,
6928
6773
  {
6929
- 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."
6774
+ message: "Either 'id' (UUID), 'ids' (array of UUIDs or codes), 'task_code' (string code like PERF-1), or 'task_codes' (array of string codes) must be provided."
6930
6775
  }
6931
6776
  ).refine((data) => Object.keys(data).length > 2, {
6932
6777
  message: "At least one field besides repo and id/ids must be provided for update"
@@ -6955,21 +6800,21 @@ var TaskSearchSchema = z4.object({
6955
6800
  var TaskDeleteSchema = z4.object({
6956
6801
  owner: z4.string().min(1),
6957
6802
  repo: z4.string().min(1).transform(normalizeRepo),
6958
- id: z4.string().uuid().optional(),
6959
- ids: z4.array(z4.string().uuid()).min(1).optional(),
6803
+ id: z4.string().optional(),
6804
+ ids: z4.array(z4.string()).min(1).optional(),
6960
6805
  task_code: z4.string().optional(),
6961
6806
  task_codes: z4.array(z4.string().min(1)).min(1).optional(),
6962
6807
  structured: z4.boolean().default(false)
6963
6808
  }).refine(
6964
6809
  (data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0 || data.task_codes !== void 0,
6965
6810
  {
6966
- 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."
6811
+ message: "Either 'id' (UUID), 'ids' (array of UUIDs or codes), 'task_code' (string code like PERF-1), or 'task_codes' (array of string codes) must be provided."
6967
6812
  }
6968
6813
  );
6969
6814
  var TaskGetSchema = z4.object({
6970
6815
  owner: z4.string().min(1),
6971
6816
  repo: z4.string().min(1).transform(normalizeRepo),
6972
- id: z4.string().uuid().optional(),
6817
+ id: z4.string().optional(),
6973
6818
  task_code: z4.string().optional(),
6974
6819
  task_codes: z4.array(z4.string().min(1)).min(1).optional(),
6975
6820
  structured: z4.boolean().default(false)
@@ -6984,7 +6829,7 @@ var HandoffCreateSchema = z5.object({
6984
6829
  repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
6985
6830
  from_agent: z5.string().min(1),
6986
6831
  to_agent: z5.string().min(1).optional(),
6987
- task_id: z5.string().uuid().optional(),
6832
+ task_id: z5.string().optional(),
6988
6833
  task_code: z5.string().optional(),
6989
6834
  summary: z5.string().min(1),
6990
6835
  context: z5.record(z5.string(), z5.unknown()).optional(),
@@ -7016,7 +6861,7 @@ var HandoffListSchema = z5.object({
7016
6861
  var TaskClaimSchema = z5.object({
7017
6862
  owner: z5.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
7018
6863
  repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
7019
- task_id: z5.string().uuid().optional(),
6864
+ task_id: z5.string().optional(),
7020
6865
  task_code: z5.string().optional(),
7021
6866
  agent: z5.string().min(1),
7022
6867
  role: z5.string().optional(),
@@ -7039,7 +6884,7 @@ var ClaimListSchema = z5.object({
7039
6884
  var ClaimReleaseSchema = z5.object({
7040
6885
  owner: z5.string().min(1, "owner is required \u2014 provide it explicitly or configure MCP workspace roots"),
7041
6886
  repo: z5.string().min(1, "repo is required \u2014 provide it explicitly or configure MCP workspace roots").transform(normalizeRepo),
7042
- task_id: z5.string().uuid().optional(),
6887
+ task_id: z5.string().optional(),
7043
6888
  task_code: z5.string().optional(),
7044
6889
  agent: z5.string().min(1).optional(),
7045
6890
  structured: z5.boolean().default(false)
@@ -7078,7 +6923,7 @@ var StandardStoreSchema = z6.object({
7078
6923
  message: "repo is required for repo-specific standards"
7079
6924
  });
7080
6925
  var StandardUpdateSchema = z6.object({
7081
- id: z6.string().uuid().optional(),
6926
+ id: z6.string().optional(),
7082
6927
  code: z6.string().max(20).optional(),
7083
6928
  name: z6.string().min(3).max(255).optional(),
7084
6929
  content: z6.string().min(10).optional(),
@@ -7120,8 +6965,8 @@ var StandardSearchSchema = z6.object({
7120
6965
  var StandardDeleteSchema = z6.object({
7121
6966
  owner: z6.string().min(1),
7122
6967
  repo: z6.string().min(1).transform(normalizeRepo),
7123
- id: z6.string().uuid().optional(),
7124
- ids: z6.array(z6.string().uuid()).min(1).optional(),
6968
+ id: z6.string().optional(),
6969
+ ids: z6.array(z6.string()).min(1).optional(),
7125
6970
  code: z6.string().max(20).optional(),
7126
6971
  codes: z6.array(z6.string().max(20)).min(1).optional(),
7127
6972
  structured: z6.boolean().default(false)
@@ -7132,7 +6977,7 @@ var StandardDeleteSchema = z6.object({
7132
6977
  }
7133
6978
  );
7134
6979
  var StandardDetailSchema = z6.object({
7135
- id: z6.string().uuid().optional(),
6980
+ id: z6.string().optional(),
7136
6981
  code: z6.string().max(20).optional(),
7137
6982
  owner: z6.string().min(1),
7138
6983
  repo: z6.string().min(1).transform(normalizeRepo),
@@ -7215,6 +7060,9 @@ var KGBackfillSchema = z8.object({
7215
7060
  source: z8.enum(["memories", "standards", "both"]).optional().default("both")
7216
7061
  });
7217
7062
 
7063
+ // src/mcp/utils/uuid.ts
7064
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7065
+
7218
7066
  // src/mcp/tools/handoff.manage.ts
7219
7067
  function buildHandoffListSummary(repo, count, status, fromAgent, toAgent) {
7220
7068
  const parts = [`Found ${count} handoff${count === 1 ? "" : "s"} in repo "${repo}".`];
@@ -7243,6 +7091,13 @@ async function handleHandoffCreate(args, storage) {
7243
7091
  const validated = HandoffCreateSchema.parse(args);
7244
7092
  const { owner, repo, from_agent, to_agent, task_id, task_code, summary, context, expires_at, structured } = validated;
7245
7093
  let resolvedTaskId = task_id ?? null;
7094
+ if (resolvedTaskId && !UUID_REGEX.test(resolvedTaskId)) {
7095
+ const task = storage.tasks.getTaskByCode(owner, repo, resolvedTaskId);
7096
+ if (!task) {
7097
+ throw new Error(`Task not found: ${resolvedTaskId} in repo ${repo}`);
7098
+ }
7099
+ resolvedTaskId = task.id;
7100
+ }
7246
7101
  if (!resolvedTaskId && task_code) {
7247
7102
  const task = storage.tasks.getTaskByCode(owner, repo, task_code);
7248
7103
  if (!task) {
@@ -7361,6 +7216,13 @@ async function handleTaskClaim(args, storage) {
7361
7216
  const validated = TaskClaimSchema.parse(args);
7362
7217
  const { owner, repo, task_id, task_code, agent, role, metadata, structured } = validated;
7363
7218
  let taskId = task_id;
7219
+ if (taskId && !UUID_REGEX.test(taskId)) {
7220
+ const task2 = storage.tasks.getTaskByCode(owner, repo, taskId);
7221
+ if (!task2) {
7222
+ throw new Error(`Task not found: ${taskId} in repo ${repo}`);
7223
+ }
7224
+ taskId = task2.id;
7225
+ }
7364
7226
  let resolvedTaskCode;
7365
7227
  let task;
7366
7228
  if (taskId) {
@@ -7466,6 +7328,13 @@ async function handleClaimRelease(args, storage) {
7466
7328
  const validated = ClaimReleaseSchema.parse(args);
7467
7329
  const { owner, repo, task_id, task_code, agent, structured } = validated;
7468
7330
  let resolvedTaskId = task_id;
7331
+ if (resolvedTaskId && !UUID_REGEX.test(resolvedTaskId)) {
7332
+ const task = storage.tasks.getTaskByCode(owner, repo, resolvedTaskId);
7333
+ if (!task) {
7334
+ throw new Error(`Task not found: ${resolvedTaskId} in repo ${repo}`);
7335
+ }
7336
+ resolvedTaskId = task.id;
7337
+ }
7469
7338
  let resolvedTaskCode = task_code ?? null;
7470
7339
  if (resolvedTaskId) {
7471
7340
  const task = storage.tasks.getTaskById(resolvedTaskId);
@@ -7503,6 +7372,164 @@ async function handleClaimRelease(args, storage) {
7503
7372
  });
7504
7373
  }
7505
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
+
7506
7533
  // src/mcp/tools/standard.shared.ts
7507
7534
  function toContextSlug(value) {
7508
7535
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -7521,30 +7548,20 @@ function buildStandardVectorText(standard) {
7521
7548
  }
7522
7549
 
7523
7550
  export {
7524
- logger,
7525
- addLogSink,
7526
- createFileSink,
7527
- parseRepoInput,
7528
- normalizeRepo,
7529
- SQLiteStore,
7530
- RealVectorStore,
7531
- TOOL_DEFINITIONS,
7532
- rankCompletionValues,
7533
- listResources,
7534
- completeResourceArgument,
7551
+ listPromptFiles,
7552
+ loadPromptFromMarkdown,
7553
+ loadServerInstructions,
7535
7554
  createSessionContext,
7536
7555
  getFilesystemRoots,
7537
7556
  isPathWithinRoots,
7538
7557
  findContainingRoot,
7539
7558
  inferRepoFromSession,
7540
7559
  inferOwnerFromSession,
7541
- listPromptFiles,
7542
- loadPromptFromMarkdown,
7543
- loadServerInstructions,
7544
- PROMPTS,
7545
- completePromptArgument,
7546
- createMcpResponse,
7547
- getPrimaryTextContent,
7560
+ logger,
7561
+ addLogSink,
7562
+ createFileSink,
7563
+ parseRepoInput,
7564
+ normalizeRepo,
7548
7565
  TaskStatusSchema,
7549
7566
  MemoryStoreSchema,
7550
7567
  MemoryUpdateSchema,
@@ -7576,6 +7593,10 @@ export {
7576
7593
  DeleteRelationSchema,
7577
7594
  DeleteObservationSchema,
7578
7595
  KGBackfillSchema,
7596
+ TOOL_DEFINITIONS,
7597
+ createMcpResponse,
7598
+ getPrimaryTextContent,
7599
+ UUID_REGEX,
7579
7600
  handleHandoffCreate,
7580
7601
  handleHandoffList,
7581
7602
  handleHandoffUpdate,
@@ -7583,5 +7604,12 @@ export {
7583
7604
  handleClaimList,
7584
7605
  handleClaimRelease,
7585
7606
  toContextSlug,
7586
- buildStandardVectorText
7607
+ buildStandardVectorText,
7608
+ rankCompletionValues,
7609
+ PROMPTS,
7610
+ completePromptArgument,
7611
+ listResources,
7612
+ completeResourceArgument,
7613
+ SQLiteStore,
7614
+ RealVectorStore
7587
7615
  };
@@ -15,7 +15,7 @@ import {
15
15
  handleTaskClaim,
16
16
  listResources,
17
17
  logger
18
- } from "../chunk-H7BLOZB6.js";
18
+ } from "../chunk-BPQ42JEQ.js";
19
19
 
20
20
  // src/dashboard/server.ts
21
21
  import express from "express";
@@ -34,6 +34,7 @@ import {
34
34
  TaskSearchSchema,
35
35
  TaskStatusSchema,
36
36
  TaskUpdateSchema,
37
+ UUID_REGEX,
37
38
  addLogSink,
38
39
  buildStandardVectorText,
39
40
  completePromptArgument,
@@ -61,7 +62,7 @@ import {
61
62
  parseRepoInput,
62
63
  rankCompletionValues,
63
64
  toContextSlug
64
- } from "../chunk-H7BLOZB6.js";
65
+ } from "../chunk-BPQ42JEQ.js";
65
66
 
66
67
  // src/mcp/server.ts
67
68
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
@@ -74,8 +75,8 @@ import path from "path";
74
75
  import { fileURLToPath } from "url";
75
76
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
76
77
  var pkgVersion = "0.1.0";
77
- if ("0.19.13") {
78
- pkgVersion = "0.19.13";
78
+ if ("0.19.15") {
79
+ pkgVersion = "0.19.15";
79
80
  } else {
80
81
  let searchDir = __dirname;
81
82
  for (let i = 0; i < 5; i++) {
@@ -422,7 +423,6 @@ async function saveExtractions(content, title, owner, repo, db2) {
422
423
  }
423
424
 
424
425
  // src/mcp/tools/memory.store.ts
425
- var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
426
426
  function hasMetadataLikeTitle(title) {
427
427
  const normalized = title.trim();
428
428
  return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
@@ -657,14 +657,13 @@ async function handleMemoryStore(params, db2, vectors2) {
657
657
  }
658
658
 
659
659
  // src/mcp/tools/memory.update.ts
660
- var UUID_REGEX2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
661
660
  function hasMetadataLikeTitle2(title) {
662
661
  const normalized = title.trim();
663
662
  return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
664
663
  }
665
664
  function resolveMemorySupersedes2(value, db2, owner, repo) {
666
665
  if (!value) return null;
667
- if (UUID_REGEX2.test(value)) return value;
666
+ if (UUID_REGEX.test(value)) return value;
668
667
  const memory = db2.memories.getByCode(value, owner, repo);
669
668
  if (!memory) throw new Error(`supersedes: memory with code '${value}' not found`);
670
669
  return memory.id;
@@ -672,6 +671,11 @@ function resolveMemorySupersedes2(value, db2, owner, repo) {
672
671
  async function handleMemoryUpdate(params, db2, vectors2) {
673
672
  const validated = MemoryUpdateSchema.parse(params);
674
673
  let resolvedId = validated.id;
674
+ if (resolvedId && !UUID_REGEX.test(resolvedId)) {
675
+ const byCode = db2.memories.getByCode(resolvedId, validated.owner, validated.repo);
676
+ if (!byCode) throw new Error(`Memory not found: ${resolvedId}`);
677
+ resolvedId = byCode.id;
678
+ }
675
679
  if (!resolvedId && validated.code) {
676
680
  const byCode = db2.memories.getByCode(validated.code, validated.owner, validated.repo);
677
681
  if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
@@ -1037,6 +1041,11 @@ function applyTimeFilter(memories, tunnel) {
1037
1041
  async function handleMemoryAcknowledge(params, db2) {
1038
1042
  const validated = MemoryAcknowledgeSchema.parse(params);
1039
1043
  let memoryId = validated.memory_id;
1044
+ if (memoryId && !UUID_REGEX.test(memoryId)) {
1045
+ const byCode = db2.memories.getByCode(memoryId, validated.owner, validated.repo);
1046
+ if (!byCode) throw new Error(`Memory not found: ${memoryId}`);
1047
+ memoryId = byCode.id;
1048
+ }
1040
1049
  if (!memoryId && validated.code) {
1041
1050
  const byCode = db2.memories.getByCode(validated.code, validated.owner, validated.repo);
1042
1051
  if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
@@ -1541,8 +1550,26 @@ async function handleMemoryDelete(params, db2, vectors2, onProgress) {
1541
1550
  const validated = MemoryDeleteSchema.parse(params);
1542
1551
  const { id, ids, code, codes, owner, repo, structured } = validated;
1543
1552
  const resolvedIds = [];
1544
- if (ids) resolvedIds.push(...ids);
1545
- if (id) resolvedIds.push(id);
1553
+ if (ids) {
1554
+ for (const item of ids) {
1555
+ if (UUID_REGEX.test(item)) {
1556
+ resolvedIds.push(item);
1557
+ } else {
1558
+ const entry = db2.memories.getByCode(item, owner, repo);
1559
+ if (!entry) throw new Error(`Memory not found: ${item}`);
1560
+ resolvedIds.push(entry.id);
1561
+ }
1562
+ }
1563
+ }
1564
+ if (id) {
1565
+ if (!UUID_REGEX.test(id)) {
1566
+ const entry = db2.memories.getByCode(id, owner, repo);
1567
+ if (!entry) throw new Error(`Memory not found: ${id}`);
1568
+ resolvedIds.push(entry.id);
1569
+ } else {
1570
+ resolvedIds.push(id);
1571
+ }
1572
+ }
1546
1573
  if (code) {
1547
1574
  const entry = db2.memories.getByCode(code, owner, repo);
1548
1575
  if (!entry) throw new Error(`Memory not found: ${code}`);
@@ -1573,7 +1600,7 @@ async function handleMemoryDelete(params, db2, vectors2, onProgress) {
1573
1600
  lastRepo = existing.scope.repo;
1574
1601
  deletedCodes.push(existing.code || existing.id);
1575
1602
  validIdsToDelete.push(targetId);
1576
- } else if (id) {
1603
+ } else {
1577
1604
  throw new Error(`Memory not found: ${targetId}`);
1578
1605
  }
1579
1606
  }
@@ -1615,7 +1642,7 @@ async function handleMemoryDetail(args, storage) {
1615
1642
  const { id, code, owner, repo } = validated;
1616
1643
  let memory;
1617
1644
  if (id) {
1618
- memory = storage.memories.getById(id);
1645
+ memory = UUID_REGEX.test(id) ? storage.memories.getById(id) : storage.memories.getByCode(id, owner, repo);
1619
1646
  } else if (code) {
1620
1647
  memory = storage.memories.getByCode(code, owner, repo);
1621
1648
  }
@@ -1645,10 +1672,9 @@ async function handleMemoryDetail(args, storage) {
1645
1672
 
1646
1673
  // src/mcp/tools/standard.store.ts
1647
1674
  import { randomUUID as randomUUID3 } from "crypto";
1648
- var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1649
1675
  function resolveStandardParentId(value, db2, owner, repo) {
1650
1676
  if (!value) return null;
1651
- if (UUID_REGEX3.test(value)) return value;
1677
+ if (UUID_REGEX.test(value)) return value;
1652
1678
  const standard = db2.standards.getByCode(value, owner, repo);
1653
1679
  if (!standard) throw new Error(`parent_id: standard with code '${value}' not found`);
1654
1680
  return standard.id;
@@ -2055,10 +2081,9 @@ async function handleStandardSearch(params, db2, vectors2) {
2055
2081
  }
2056
2082
 
2057
2083
  // src/mcp/tools/standard.update.ts
2058
- var UUID_REGEX4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2059
2084
  function resolveStandardParentId2(value, db2, owner, repo) {
2060
2085
  if (!value) return null;
2061
- if (UUID_REGEX4.test(value)) return value;
2086
+ if (UUID_REGEX.test(value)) return value;
2062
2087
  const standard = db2.standards.getByCode(value, owner, repo);
2063
2088
  if (!standard) throw new Error(`parent_id: standard with code '${value}' not found`);
2064
2089
  return standard.id;
@@ -2066,6 +2091,11 @@ function resolveStandardParentId2(value, db2, owner, repo) {
2066
2091
  async function handleStandardUpdate(params, db2, vectors2) {
2067
2092
  const validated = StandardUpdateSchema.parse(params);
2068
2093
  let resolvedId = validated.id;
2094
+ if (resolvedId && !UUID_REGEX.test(resolvedId)) {
2095
+ const byCode = db2.standards.getByCode(resolvedId, validated.owner, validated.repo);
2096
+ if (!byCode) throw new Error(`Coding standard not found: ${resolvedId}`);
2097
+ resolvedId = byCode.id;
2098
+ }
2069
2099
  if (!resolvedId && validated.code) {
2070
2100
  const byCode = db2.standards.getByCode(validated.code, validated.owner, validated.repo);
2071
2101
  if (!byCode) throw new Error(`Coding standard not found: ${validated.code}`);
@@ -2123,9 +2153,15 @@ async function handleStandardUpdate(params, db2, vectors2) {
2123
2153
  // src/mcp/tools/standard.detail.ts
2124
2154
  async function handleStandardDetail(args, storage) {
2125
2155
  const validated = StandardDetailSchema.parse(args);
2126
- const standard = validated.id ? storage.standards.getById(validated.id) : storage.standards.getByCode(validated.code, validated.owner, validated.repo);
2156
+ const { id, code, owner, repo } = validated;
2157
+ let standard;
2158
+ if (id) {
2159
+ standard = UUID_REGEX.test(id) ? storage.standards.getById(id) : storage.standards.getByCode(id, owner, repo);
2160
+ } else if (code) {
2161
+ standard = storage.standards.getByCode(code, owner, repo);
2162
+ }
2127
2163
  if (!standard) {
2128
- const identifier = validated.id ?? validated.code;
2164
+ const identifier = id ?? code;
2129
2165
  throw new Error(`Coding standard not found: ${identifier}`);
2130
2166
  }
2131
2167
  storage.standards.incrementHitCounts([standard.id]);
@@ -2159,8 +2195,26 @@ async function handleStandardDelete(params, db2, vectors2) {
2159
2195
  const validated = StandardDeleteSchema.parse(params);
2160
2196
  const { id, ids, code, codes, owner, repo, structured } = validated;
2161
2197
  const resolvedIds = [];
2162
- if (ids) resolvedIds.push(...ids);
2163
- if (id) resolvedIds.push(id);
2198
+ if (ids) {
2199
+ for (const item of ids) {
2200
+ if (UUID_REGEX.test(item)) {
2201
+ resolvedIds.push(item);
2202
+ } else {
2203
+ const entry = db2.standards.getByCode(item, owner, repo);
2204
+ if (!entry) throw new Error(`Coding standard not found: ${item}`);
2205
+ resolvedIds.push(entry.id);
2206
+ }
2207
+ }
2208
+ }
2209
+ if (id) {
2210
+ if (!UUID_REGEX.test(id)) {
2211
+ const entry = db2.standards.getByCode(id, owner, repo);
2212
+ if (!entry) throw new Error(`Coding standard not found: ${id}`);
2213
+ resolvedIds.push(entry.id);
2214
+ } else {
2215
+ resolvedIds.push(id);
2216
+ }
2217
+ }
2164
2218
  if (code) {
2165
2219
  const entry = db2.standards.getByCode(code, owner, repo);
2166
2220
  if (!entry) throw new Error(`Coding standard not found: ${code}`);
@@ -2188,7 +2242,7 @@ async function handleStandardDelete(params, db2, vectors2) {
2188
2242
  db2.standards.delete(targetId);
2189
2243
  await vectors2.remove(targetId, "standard");
2190
2244
  deletedCount++;
2191
- } else if (id) {
2245
+ } else {
2192
2246
  throw new Error(`Coding standard not found: ${targetId}`);
2193
2247
  }
2194
2248
  }
@@ -2214,10 +2268,9 @@ async function handleStandardDelete(params, db2, vectors2) {
2214
2268
  import { randomUUID as randomUUID4 } from "crypto";
2215
2269
 
2216
2270
  // src/mcp/tools/task.helpers.ts
2217
- var UUID_REGEX5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2218
2271
  function resolveParentId(value, owner, repo, storage, localCodeMap) {
2219
2272
  if (!value) return null;
2220
- if (UUID_REGEX5.test(value)) return value;
2273
+ if (UUID_REGEX.test(value)) return value;
2221
2274
  if (localCodeMap?.has(value)) return localCodeMap.get(value);
2222
2275
  const parent = storage.tasks.getTaskByCode(owner, repo, value);
2223
2276
  if (!parent) throw new Error(`parent_id: task with code '${value}' not found in repo '${repo}'`);
@@ -2225,7 +2278,7 @@ function resolveParentId(value, owner, repo, storage, localCodeMap) {
2225
2278
  }
2226
2279
  function resolveDependsOn(value, owner, repo, storage, localCodeMap) {
2227
2280
  if (!value) return null;
2228
- if (UUID_REGEX5.test(value)) return value;
2281
+ if (UUID_REGEX.test(value)) return value;
2229
2282
  if (localCodeMap?.has(value)) return localCodeMap.get(value);
2230
2283
  const task = storage.tasks.getTaskByCode(owner, repo, value);
2231
2284
  if (!task) throw new Error(`depends_on: task with code '${value}' not found in repo '${repo}'`);
@@ -2338,7 +2391,9 @@ async function handleTaskCreate(args, storage) {
2338
2391
  throw new Error(`Duplicate task_code in request: '${code}'`);
2339
2392
  }
2340
2393
  if (existingCodes.has(code)) {
2341
- throw new Error(`Duplicate task_code: '${code}' already exists in repository '${repo}'`);
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
+ );
2342
2397
  }
2343
2398
  codesInRequest.add(code);
2344
2399
  let normalizedStatus = taskData.status || "backlog";
@@ -2419,7 +2474,9 @@ async function handleTaskCreate(args, storage) {
2419
2474
  let effectiveStatus = requestedStatus || "backlog";
2420
2475
  const resolvedCode = task_code || generateNextCode(owner ?? "", repo, "task", storage);
2421
2476
  if (storage.tasks.isTaskCodeDuplicate(owner, repo, resolvedCode)) {
2422
- throw new Error(`Duplicate task_code: '${resolvedCode}' already exists in repository '${repo}'`);
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
+ );
2423
2480
  }
2424
2481
  if (requestedStatus !== "backlog" && requestedStatus !== "pending" && requestedStatus !== void 0) {
2425
2482
  throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
@@ -2583,12 +2640,29 @@ async function handleTaskUpdate(args, storage, vectors2) {
2583
2640
  const updateData = TaskUpdateSchema.parse(args);
2584
2641
  const { owner, repo, id, ids, comment, force, ...updates } = updateData;
2585
2642
  let resolvedId = id;
2643
+ if (resolvedId && !UUID_REGEX.test(resolvedId)) {
2644
+ const found = storage.tasks.getTaskByCode(owner, repo, resolvedId);
2645
+ if (!found) throw new Error(`Task not found: ${resolvedId}`);
2646
+ resolvedId = found.id;
2647
+ }
2586
2648
  if (!resolvedId && !ids && updates.task_code) {
2587
2649
  const found = storage.tasks.getTaskByCode(owner, repo, updates.task_code);
2588
2650
  if (!found) throw new Error(`Task not found: ${updates.task_code}`);
2589
2651
  resolvedId = found.id;
2590
2652
  }
2591
- const targetIds = ids || (resolvedId ? [resolvedId] : []);
2653
+ const resolvedIdsArray = [];
2654
+ if (ids) {
2655
+ for (const item of ids) {
2656
+ if (UUID_REGEX.test(item)) {
2657
+ resolvedIdsArray.push(item);
2658
+ } else {
2659
+ const task = storage.tasks.getTaskByCode(owner, repo, item);
2660
+ if (!task) throw new Error(`Task not found: ${item}`);
2661
+ resolvedIdsArray.push(task.id);
2662
+ }
2663
+ }
2664
+ }
2665
+ const targetIds = resolvedIdsArray.length > 0 ? resolvedIdsArray : resolvedId ? [resolvedId] : [];
2592
2666
  if (targetIds.length === 0) {
2593
2667
  throw new Error("Either 'id' or 'ids' must be provided for update");
2594
2668
  }
@@ -2731,8 +2805,26 @@ async function handleTaskDelete(args, storage) {
2731
2805
  const validated = TaskDeleteSchema.parse(args);
2732
2806
  const { owner, repo, id, ids, task_code } = validated;
2733
2807
  const resolvedIds = [];
2734
- if (ids) resolvedIds.push(...ids);
2735
- if (id) resolvedIds.push(id);
2808
+ if (ids) {
2809
+ for (const item of ids) {
2810
+ if (UUID_REGEX.test(item)) {
2811
+ resolvedIds.push(item);
2812
+ } else {
2813
+ const task = storage.tasks.getTaskByCode(owner, repo, item);
2814
+ if (!task) throw new Error(`Task not found: ${item}`);
2815
+ resolvedIds.push(task.id);
2816
+ }
2817
+ }
2818
+ }
2819
+ if (id) {
2820
+ if (!UUID_REGEX.test(id)) {
2821
+ const task = storage.tasks.getTaskByCode(owner, repo, id);
2822
+ if (!task) throw new Error(`Task not found: ${id}`);
2823
+ resolvedIds.push(task.id);
2824
+ } else {
2825
+ resolvedIds.push(id);
2826
+ }
2827
+ }
2736
2828
  if (task_code) {
2737
2829
  const task = storage.tasks.getTaskByCode(owner, repo, task_code);
2738
2830
  if (!task) throw new Error(`Task not found: ${task_code}`);
@@ -2768,7 +2860,7 @@ async function handleTaskGet(args, storage) {
2768
2860
  const { owner, repo, id, task_code, structured: isStructuredRequest } = validated;
2769
2861
  let task;
2770
2862
  if (id) {
2771
- task = storage.tasks.getTaskById(id);
2863
+ task = UUID_REGEX.test(id) ? storage.tasks.getTaskById(id) : storage.tasks.getTaskByCode(owner, repo, id);
2772
2864
  } else if (task_code) {
2773
2865
  task = storage.tasks.getTaskByCode(owner, repo, task_code);
2774
2866
  } else {
@@ -3063,6 +3155,10 @@ async function handleDecisionLog(args, db2, vectors2) {
3063
3155
  tags,
3064
3156
  structured: validated.structured
3065
3157
  };
3158
+ const conflict = await db2.memoryVectors.checkConflicts(formattedContent, owner, repo, "decision", vectors2, 0.55);
3159
+ if (conflict) {
3160
+ memoryStoreParams.supersedes = conflict.id;
3161
+ }
3066
3162
  return handleMemoryStore(memoryStoreParams, db2, vectors2);
3067
3163
  }
3068
3164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vheins/local-memory-mcp",
3
- "version": "0.19.13",
3
+ "version": "0.19.15",
4
4
  "description": "MCP Local Memory Service for coding copilot agents",
5
5
  "mcpName": "io.github.vheins/local-memory-mcp",
6
6
  "type": "module",