kontexta-mcp 2.0.10 → 3.0.1

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.
Files changed (2) hide show
  1. package/dist/index.js +114 -35
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1048,6 +1048,47 @@ function listProjectFolders(projectPath) {
1048
1048
  }
1049
1049
  return folders;
1050
1050
  }
1051
+ function listProjectFoldersWithFiles(projectPath) {
1052
+ const folderFileCount = /* @__PURE__ */ new Map();
1053
+ function scan(dir, currentRel) {
1054
+ const entries = readdirSync3(dir);
1055
+ for (const entry of entries) {
1056
+ if (entry.startsWith(".") || entry === "node_modules")
1057
+ continue;
1058
+ const fullPath = join4(dir, entry);
1059
+ const relPath = currentRel ? join4(currentRel, entry) : entry;
1060
+ try {
1061
+ const lst = lstatSync2(fullPath);
1062
+ if (lst.isSymbolicLink())
1063
+ continue;
1064
+ if (lst.isDirectory()) {
1065
+ scan(fullPath, relPath);
1066
+ } else if (lst.isFile()) {
1067
+ const ext = entry.endsWith(".mmd") ? ".mmd" : entry.endsWith(".md") ? ".md" : "";
1068
+ if (ext) {
1069
+ const parts = relPath.split("/");
1070
+ for (let i4 = 0; i4 < parts.length - 1; i4++) {
1071
+ const ancestor = parts.slice(0, i4 + 1).join("/");
1072
+ folderFileCount.set(ancestor, (folderFileCount.get(ancestor) ?? 0) + 1);
1073
+ }
1074
+ }
1075
+ }
1076
+ } catch (e3) {
1077
+ }
1078
+ }
1079
+ }
1080
+ try {
1081
+ scan(projectPath, "");
1082
+ } catch (e3) {
1083
+ console.error("Failed to scan project folders with files:", e3);
1084
+ }
1085
+ const result = [];
1086
+ for (const [folder, count] of folderFileCount.entries()) {
1087
+ if (count > 0)
1088
+ result.push(folder);
1089
+ }
1090
+ return result.sort();
1091
+ }
1051
1092
  function computeHash(content) {
1052
1093
  return createHash2("sha256").update(content, "utf8").digest("hex");
1053
1094
  }
@@ -1333,9 +1374,15 @@ function listFiles(opts) {
1333
1374
  }
1334
1375
  if (filters.folder !== void 0) {
1335
1376
  const segment = escapeLike(filters.folder);
1336
- sql += " AND (path LIKE ? ESCAPE '\\' OR path LIKE ? ESCAPE '\\')";
1337
- params.push(`%/${segment}/%`);
1338
- params.push(`%\\${segment}\\%`);
1377
+ if (filters.project_path) {
1378
+ sql += " AND (path LIKE ? ESCAPE '\\' OR path LIKE ? ESCAPE '\\')";
1379
+ params.push(`${filters.project_path}/${segment}/%`);
1380
+ params.push(`${filters.project_path}\\${segment}\\%`);
1381
+ } else {
1382
+ sql += " AND (path LIKE ? ESCAPE '\\' OR path LIKE ? ESCAPE '\\')";
1383
+ params.push(`%/${segment}/%`);
1384
+ params.push(`%\\${segment}\\%`);
1385
+ }
1339
1386
  }
1340
1387
  }
1341
1388
  sql += " ORDER BY updated_at DESC";
@@ -1582,27 +1629,33 @@ function registerProject(name50, path3, description, remoteUrl) {
1582
1629
  `);
1583
1630
  const absolutePath = resolve5(path3);
1584
1631
  const result = insertStmt.run(name50, slug, absolutePath, description || null, remoteUrl || null);
1632
+ let projectId;
1585
1633
  if (result.changes > 0) {
1586
- const projectId = Number(result.lastInsertRowid);
1587
- return db2.prepare("SELECT * FROM projects WHERE id = ?").get(projectId);
1588
- }
1589
- const byName = db2.prepare("SELECT * FROM projects WHERE name = ?").get(name50);
1590
- const bySlug = db2.prepare("SELECT * FROM projects WHERE slug = ?").get(slug);
1591
- const existing = byName ?? bySlug;
1592
- if (!existing) {
1593
- throw new Error(`registerProject: insert ignored but no matching project found for name='${name50}'`);
1594
- }
1595
- if (existing.path !== absolutePath) {
1596
- const conflicts = [];
1597
- if (byName)
1598
- conflicts.push(`name conflicts with '${byName.name}' at '${byName.path}'`);
1599
- if (bySlug && bySlug.id !== byName?.id)
1600
- conflicts.push(`slug '${slug}' conflicts with '${bySlug.name}' at '${bySlug.path}'`);
1601
- const err = new Error(`Cannot register '${name50}' at '${path3}': ${conflicts.join("; ")}. Pick a different name or unregister the existing project first.`);
1602
- err.code = "PROJECT_CONFLICT";
1603
- throw err;
1604
- }
1605
- return existing;
1634
+ projectId = Number(result.lastInsertRowid);
1635
+ } else {
1636
+ const byName = db2.prepare("SELECT * FROM projects WHERE name = ?").get(name50);
1637
+ const bySlug = db2.prepare("SELECT * FROM projects WHERE slug = ?").get(slug);
1638
+ const existing = byName ?? bySlug;
1639
+ if (!existing) {
1640
+ throw new Error(`registerProject: insert ignored but no matching project found for name='${name50}'`);
1641
+ }
1642
+ if (existing.path !== absolutePath) {
1643
+ const conflicts = [];
1644
+ if (byName)
1645
+ conflicts.push(`name conflicts with '${byName.name}' at '${byName.path}'`);
1646
+ if (bySlug && bySlug.id !== byName?.id)
1647
+ conflicts.push(`slug '${slug}' conflicts with '${bySlug.name}' at '${bySlug.path}'`);
1648
+ const err = new Error(`Cannot register '${name50}' at '${path3}': ${conflicts.join("; ")}. Pick a different name or unregister the existing project first.`);
1649
+ err.code = "PROJECT_CONFLICT";
1650
+ throw err;
1651
+ }
1652
+ projectId = existing.id;
1653
+ }
1654
+ const newlyIndexed = reconcileIndex({ projectId, dataDir: getDataDir() }).newRecords.length;
1655
+ return {
1656
+ ...db2.prepare("SELECT * FROM projects WHERE id = ?").get(projectId),
1657
+ newlyIndexed
1658
+ };
1606
1659
  }
1607
1660
  function unregisterProject(projectId, dataDir2) {
1608
1661
  const db2 = getDatabase();
@@ -1678,9 +1731,14 @@ function reconcileIndex(opts) {
1678
1731
  } catch (e3) {
1679
1732
  if (isTopLevel) {
1680
1733
  if (typeof projectId === "number") {
1681
- throw new Error(`Failed to read project root ${dir}: ${e3?.message ?? e3}`);
1734
+ if (e3?.code === "ENOENT") {
1735
+ console.warn(`reconcileIndex: project root does not exist yet: ${dir}`);
1736
+ } else {
1737
+ throw new Error(`Failed to read project root ${dir}: ${e3?.message ?? e3}`);
1738
+ }
1739
+ } else {
1740
+ console.warn(`reconcileIndex: failed to read KB root ${dir}: ${e3?.message ?? e3}`);
1682
1741
  }
1683
- console.warn(`reconcileIndex: failed to read KB root ${dir}: ${e3?.message ?? e3}`);
1684
1742
  }
1685
1743
  return;
1686
1744
  }
@@ -1775,7 +1833,9 @@ function reconcileIndex(opts) {
1775
1833
  return { scope, newly_indexed, refreshed, pruned, newRecords };
1776
1834
  }
1777
1835
  function discoverFiles(projectId, dataDir2) {
1778
- return reconcileIndex({ projectId, dataDir: dataDir2 }).newRecords;
1836
+ reconcileIndex({ projectId, dataDir: dataDir2 });
1837
+ const db2 = getDatabase();
1838
+ return db2.prepare("SELECT * FROM files WHERE project_id = ? ORDER BY path").all(projectId);
1779
1839
  }
1780
1840
  function getTagsForFiles(fileIds) {
1781
1841
  const out = /* @__PURE__ */ new Map();
@@ -1822,6 +1882,7 @@ var init_metadata = __esm({
1822
1882
  init_safety();
1823
1883
  init_files();
1824
1884
  init_extensions();
1885
+ init_paths();
1825
1886
  FtsQueryError = class extends Error {
1826
1887
  code = "FTS_PARSE";
1827
1888
  constructor(message) {
@@ -130573,7 +130634,7 @@ var init_dist3 = __esm({
130573
130634
  }
130574
130635
  });
130575
130636
 
130576
- // ../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@4.0.0__5q6mm7d5muktbf6idkt2s7pica/node_modules/@csstools/css-calc/dist/index.mjs
130637
+ // ../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@_3b33f237655dfa7a35d4b0433ee15cf1/node_modules/@csstools/css-calc/dist/index.mjs
130577
130638
  function toLowerCaseAZ(e3) {
130578
130639
  return e3.replace(M, (e4) => String.fromCharCode(e4.charCodeAt(0) + 32));
130579
130640
  }
@@ -131332,7 +131393,7 @@ function replaceComponentValues2(n3, r5) {
131332
131393
  }
131333
131394
  var ParseError2, ParseErrorWithComponentValues, y, M, T, x, P, k, W, O, U, L, $2, V, Z, z, q, G, R, j, Y, _, H, J, K, Q;
131334
131395
  var init_dist4 = __esm({
131335
- "../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@4.0.0__5q6mm7d5muktbf6idkt2s7pica/node_modules/@csstools/css-calc/dist/index.mjs"() {
131396
+ "../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@_3b33f237655dfa7a35d4b0433ee15cf1/node_modules/@csstools/css-calc/dist/index.mjs"() {
131336
131397
  "use strict";
131337
131398
  init_dist3();
131338
131399
  init_dist2();
@@ -132456,7 +132517,7 @@ var init_dist5 = __esm({
132456
132517
  }
132457
132518
  });
132458
132519
 
132459
- // ../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenize_2jw36v2skt7mvdil6fqdameniu/node_modules/@csstools/css-color-parser/dist/index.mjs
132520
+ // ../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-to_402b4b31fb8b001854d943f6a3e18a48/node_modules/@csstools/css-color-parser/dist/index.mjs
132460
132521
  function convertNaNToZero(e3) {
132461
132522
  return [Number.isNaN(e3[0]) ? 0 : e3[0], Number.isNaN(e3[1]) ? 0 : e3[1], Number.isNaN(e3[2]) ? 0 : e3[2]];
132462
132523
  }
@@ -133521,7 +133582,7 @@ function color(e3) {
133521
133582
  }
133522
133583
  var he, me, pe, Ne, be, ge, ve, fe, ye;
133523
133584
  var init_dist6 = __esm({
133524
- "../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenize_2jw36v2skt7mvdil6fqdameniu/node_modules/@csstools/css-color-parser/dist/index.mjs"() {
133585
+ "../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-to_402b4b31fb8b001854d943f6a3e18a48/node_modules/@csstools/css-color-parser/dist/index.mjs"() {
133525
133586
  "use strict";
133526
133587
  init_dist2();
133527
133588
  init_dist5();
@@ -331165,6 +331226,7 @@ __export(dist_exports2, {
331165
331226
  journalRefsByValue: () => journalRefsByValue,
331166
331227
  listFiles: () => listFiles,
331167
331228
  listProjectFolders: () => listProjectFolders,
331229
+ listProjectFoldersWithFiles: () => listProjectFoldersWithFiles,
331168
331230
  listProjects: () => listProjects,
331169
331231
  listTags: () => listTags,
331170
331232
  loadExtraPatterns: () => loadExtraPatterns,
@@ -331230,8 +331292,24 @@ var init_dist7 = __esm({
331230
331292
  }
331231
331293
  });
331232
331294
 
331295
+ // src/re2-compat.ts
331296
+ import { createRequire as createRequire4 } from "module";
331297
+ var require5, RE2Class, re2_compat_default;
331298
+ var init_re2_compat = __esm({
331299
+ "src/re2-compat.ts"() {
331300
+ "use strict";
331301
+ require5 = createRequire4(import.meta.url);
331302
+ try {
331303
+ RE2Class = require5("re2");
331304
+ } catch (e3) {
331305
+ console.warn("kontexta-mcp: Failed to load native 're2' module, falling back to standard RegExp.");
331306
+ RE2Class = RegExp;
331307
+ }
331308
+ re2_compat_default = RE2Class;
331309
+ }
331310
+ });
331311
+
331233
331312
  // src/hands/sanitizer.ts
331234
- import RE2 from "re2";
331235
331313
  function rejectNul(s3) {
331236
331314
  if (s3.includes("\0")) {
331237
331315
  throw new Error("NUL byte not permitted in param value");
@@ -331267,7 +331345,7 @@ function isLiteralArgv0(s3) {
331267
331345
  function compilePattern(src) {
331268
331346
  let r5;
331269
331347
  try {
331270
- r5 = new RE2(src);
331348
+ r5 = new re2_compat_default(src);
331271
331349
  } catch (e3) {
331272
331350
  throw new Error(`invalid pattern: ${e3?.message ?? e3}`);
331273
331351
  }
@@ -331304,6 +331382,7 @@ var SAFE_INT_MIN, SAFE_INT_MAX, DEFAULT_STRING_PATTERN, MAX_STRING_LENGTH, CONTR
331304
331382
  var init_sanitizer = __esm({
331305
331383
  "src/hands/sanitizer.ts"() {
331306
331384
  "use strict";
331385
+ init_re2_compat();
331307
331386
  SAFE_INT_MIN = Number.MIN_SAFE_INTEGER;
331308
331387
  SAFE_INT_MAX = Number.MAX_SAFE_INTEGER;
331309
331388
  DEFAULT_STRING_PATTERN = "^[^-\\s][^\\n\\r]*$";
@@ -331542,10 +331621,10 @@ ${tailStr}`;
331542
331621
 
331543
331622
  // src/index.ts
331544
331623
  init_dist7();
331624
+ init_re2_compat();
331545
331625
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
331546
331626
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
331547
331627
  import { z as z6 } from "zod";
331548
- import RE22 from "re2";
331549
331628
  import { isAbsolute as isAbsolute7, join as join16, resolve as resolve7, sep as sep9, dirname as dirname6 } from "path";
331550
331629
  import { statSync as statSync5, lstatSync as lstatSync5, openSync as openSync2, readSync, closeSync as closeSync2, readFileSync as readFileSync12, existsSync as existsSync13, realpathSync as realpathSync2 } from "fs";
331551
331630
  import { createHash as createHash3 } from "crypto";
@@ -332929,7 +333008,7 @@ server.tool(
332929
333008
  try {
332930
333009
  let re;
332931
333010
  try {
332932
- re = new RE22(pattern, case_insensitive ? "i" : "");
333011
+ re = new re2_compat_default(pattern, case_insensitive ? "i" : "");
332933
333012
  } catch (e3) {
332934
333013
  throw new Error(`invalid regex: ${e3?.message ?? e3}`);
332935
333014
  }
@@ -332984,7 +333063,7 @@ server.tool(
332984
333063
  try {
332985
333064
  let re;
332986
333065
  try {
332987
- re = new RE22(pattern, case_insensitive ? "i" : "");
333066
+ re = new re2_compat_default(pattern, case_insensitive ? "i" : "");
332988
333067
  } catch (e3) {
332989
333068
  throw new Error(`invalid regex: ${e3?.message ?? e3}`);
332990
333069
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kontexta-mcp",
3
- "version": "2.0.10",
3
+ "version": "3.0.1",
4
4
  "description": "The local Brain and Hands for AI coding agents (MCP Server)",
5
5
  "keywords": [
6
6
  "mcp",
@@ -38,7 +38,7 @@
38
38
  "./hands/types": "./dist/hands/types.js"
39
39
  },
40
40
  "engines": {
41
- "node": ">=20"
41
+ "node": ">=22"
42
42
  },
43
43
  "files": [
44
44
  "dist",