kontexta-mcp 2.0.7 → 2.0.10

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.
@@ -161,4 +161,5 @@ The matrix below is grouped by intent. For each tool: when to reach for it, the
161
161
  |---|---|---|---|
162
162
  | `register_project` | Register a new project root with kontexta | Already registered | `list_projects` |
163
163
  | `onboard_agent` | Write/update the rules block in CLAUDE.md / AGENTS.md / etc. | Editing regular project content | `update_file` |
164
+ | `transfer_agent_context` | Copy CLAUDE.md / AGENTS.md / `.cursor/rules/*.mdc` etc. from the project repo into Kontexta's KB. Originals are NEVER deleted by this tool. | User wants files to stay in the repo (default) | `register_project` (which keeps files in place) |
164
165
  <!-- END kontexta:rules v{{VERSION}} -->
package/dist/index.js CHANGED
@@ -57,6 +57,14 @@ function defaultDataDir() {
57
57
  function isWebContext() {
58
58
  return process.env.npm_package_name === "kxta-web" || !!process.env.NEXT_RUNTIME || !!process.env.__NEXT_PAGES_DIR;
59
59
  }
60
+ function isTempOrTestPath(p) {
61
+ const lower = p.toLowerCase();
62
+ const tmp = os.tmpdir().toLowerCase();
63
+ return lower.startsWith("/tmp") || lower.startsWith(tmp) || lower.includes(`${path.sep}tmp${path.sep}`) || lower.includes("test") || lower.includes("-tmp-") || process.platform === "win32" && lower.includes("\\temp\\");
64
+ }
65
+ function resetDataDirCache() {
66
+ _resolvedDataDir = null;
67
+ }
60
68
  function getDataDir() {
61
69
  if (_resolvedDataDir)
62
70
  return _resolvedDataDir;
@@ -66,31 +74,40 @@ function getDataDir() {
66
74
  const isWeb = isWebContext();
67
75
  if (isWeb && envOverride) {
68
76
  _resolvedDataDir = path.resolve(envOverride);
69
- try {
70
- fs.writeFileSync(cacheFile, _resolvedDataDir, "utf8");
71
- } catch {
77
+ if (!isTempOrTestPath(_resolvedDataDir)) {
78
+ try {
79
+ fs.writeFileSync(cacheFile, _resolvedDataDir, "utf8");
80
+ } catch {
81
+ }
82
+ }
83
+ return _resolvedDataDir;
84
+ }
85
+ if (envOverride) {
86
+ _resolvedDataDir = path.resolve(envOverride);
87
+ const isTestEnv2 = !!process.env.VITEST || process.env.NODE_ENV === "test";
88
+ if (!isTempOrTestPath(_resolvedDataDir) && !isTestEnv2) {
89
+ try {
90
+ fs.writeFileSync(cacheFile, _resolvedDataDir, "utf8");
91
+ } catch {
92
+ }
72
93
  }
73
94
  return _resolvedDataDir;
74
95
  }
75
- const isTest = !!process.env.VITEST || process.env.NODE_ENV === "test" || envOverride && (envOverride.includes("test") || envOverride.includes("tmp"));
76
- if (!isTest && fs.existsSync(cacheFile)) {
96
+ const isTestEnv = !!process.env.VITEST || process.env.NODE_ENV === "test";
97
+ if (!isTestEnv && fs.existsSync(cacheFile)) {
77
98
  try {
78
99
  const cached = fs.readFileSync(cacheFile, "utf8").trim();
79
- if (cached && path.isAbsolute(cached)) {
100
+ if (cached && path.isAbsolute(cached) && !isTempOrTestPath(cached)) {
80
101
  _resolvedDataDir = cached;
81
102
  return _resolvedDataDir;
82
103
  }
104
+ try {
105
+ fs.unlinkSync(cacheFile);
106
+ } catch {
107
+ }
83
108
  } catch {
84
109
  }
85
110
  }
86
- if (envOverride) {
87
- _resolvedDataDir = path.resolve(envOverride);
88
- try {
89
- fs.writeFileSync(cacheFile, _resolvedDataDir, "utf8");
90
- } catch {
91
- }
92
- return _resolvedDataDir;
93
- }
94
111
  _resolvedDataDir = defaultDataDir();
95
112
  return _resolvedDataDir;
96
113
  }
@@ -149,7 +166,7 @@ __export(safety_exports, {
149
166
  track: () => track,
150
167
  withLock: () => withLock
151
168
  });
152
- import { resolve as resolve2, sep, isAbsolute as isAbsolute2 } from "path";
169
+ import { resolve as resolve2, sep as sep2, isAbsolute as isAbsolute2 } from "path";
153
170
  import { AsyncLocalStorage } from "async_hooks";
154
171
  function assertPathInside(base3, name50) {
155
172
  if (typeof name50 !== "string" || name50.length === 0) {
@@ -163,7 +180,7 @@ function assertPathInside(base3, name50) {
163
180
  }
164
181
  const baseResolved = resolve2(base3);
165
182
  const target = resolve2(baseResolved, name50);
166
- if (target !== baseResolved && !target.startsWith(baseResolved + sep)) {
183
+ if (target !== baseResolved && !target.startsWith(baseResolved + sep2)) {
167
184
  throw new Error("Invalid path: escapes base directory");
168
185
  }
169
186
  return target;
@@ -336,6 +353,10 @@ function getDatabase() {
336
353
  }
337
354
  function closeDatabase() {
338
355
  if (db) {
356
+ try {
357
+ db.pragma("PRAGMA wal_checkpoint(TRUNCATE)");
358
+ } catch {
359
+ }
339
360
  db.close();
340
361
  db = null;
341
362
  globalThis.__kontextaDb = null;
@@ -416,8 +437,8 @@ var init_extensions = __esm({
416
437
  // ../../packages/core/dist/git/index.js
417
438
  import { createHash } from "crypto";
418
439
  import simpleGit from "simple-git";
419
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, copyFileSync, mkdirSync as mkdirSync3, existsSync as existsSync3, rmSync, readdirSync as readdirSync2, statSync, lstatSync, unlinkSync, renameSync } from "fs";
420
- import { join as join3, relative, dirname as dirname2, isAbsolute as isAbsolute3, resolve as resolve3, sep as sep2 } from "path";
440
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, copyFileSync, mkdirSync as mkdirSync3, existsSync as existsSync3, rmSync, readdirSync as readdirSync2, statSync, lstatSync, unlinkSync as unlinkSync2, renameSync } from "fs";
441
+ import { join as join3, relative, dirname as dirname2, isAbsolute as isAbsolute3, resolve as resolve3, sep as sep3 } from "path";
421
442
  import { spawnSync } from "child_process";
422
443
  function redactCredentials(s3) {
423
444
  return s3.replace(/([a-z][a-z0-9+.-]*:\/\/)([^:@\/\s]+:[^@\/\s]+)@/gi, "$1***:***@");
@@ -536,7 +557,7 @@ async function restoreVersion(repoDir, filePath, commitHash) {
536
557
  renameSync(tmpPath, filePath);
537
558
  } catch (e3) {
538
559
  try {
539
- unlinkSync(tmpPath);
560
+ unlinkSync2(tmpPath);
540
561
  } catch {
541
562
  }
542
563
  throw e3;
@@ -814,7 +835,7 @@ async function _syncBackupLocked(projectId, dataDir2, onStage) {
814
835
  }
815
836
  } else if (!expectedBackupPaths.has(fullPath)) {
816
837
  try {
817
- unlinkSync(fullPath);
838
+ unlinkSync2(fullPath);
818
839
  } catch {
819
840
  }
820
841
  }
@@ -884,7 +905,7 @@ async function _syncBackupLocked(projectId, dataDir2, onStage) {
884
905
  if (project.path) {
885
906
  const localAbsolutePath = join3(project.path, relativePath);
886
907
  const projectResolved = resolve3(project.path);
887
- if (resolve3(localAbsolutePath) !== projectResolved && !resolve3(localAbsolutePath).startsWith(projectResolved + sep2)) {
908
+ if (resolve3(localAbsolutePath) !== projectResolved && !resolve3(localAbsolutePath).startsWith(projectResolved + sep3)) {
888
909
  continue;
889
910
  }
890
911
  mkdirSync3(dirname2(localAbsolutePath), { recursive: true });
@@ -950,12 +971,12 @@ async function _syncBackupLocked(projectId, dataDir2, onStage) {
950
971
  continue;
951
972
  const localAbsolutePath = join3(project.path, insideBackup);
952
973
  const resolvedLocal = resolve3(localAbsolutePath);
953
- if (resolvedLocal !== projectResolved && !resolvedLocal.startsWith(projectResolved + sep2)) {
974
+ if (resolvedLocal !== projectResolved && !resolvedLocal.startsWith(projectResolved + sep3)) {
954
975
  continue;
955
976
  }
956
977
  try {
957
978
  if (existsSync3(localAbsolutePath)) {
958
- unlinkSync(localAbsolutePath);
979
+ unlinkSync2(localAbsolutePath);
959
980
  }
960
981
  } catch (e3) {
961
982
  console.warn(`syncBackup: failed to remove ${localAbsolutePath}:`, e3);
@@ -997,8 +1018,8 @@ var init_git = __esm({
997
1018
 
998
1019
  // ../../packages/core/dist/files/index.js
999
1020
  import { createHash as createHash2 } from "crypto";
1000
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, renameSync as renameSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync3, lstatSync as lstatSync2, existsSync as existsSync4, rmSync as rmSync2 } from "fs";
1001
- import { join as join4, dirname as dirname3, resolve as resolve4, sep as sep3, isAbsolute as isAbsolute4 } from "path";
1021
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, renameSync as renameSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync3, lstatSync as lstatSync2, existsSync as existsSync4, rmSync as rmSync2 } from "fs";
1022
+ import { join as join4, dirname as dirname3, resolve as resolve4, sep as sep4, isAbsolute as isAbsolute4 } from "path";
1002
1023
  function listProjectFolders(projectPath) {
1003
1024
  const folders = [];
1004
1025
  function scan(dir, currentRel) {
@@ -1133,7 +1154,7 @@ async function createFile(opts) {
1133
1154
  }
1134
1155
  } else {
1135
1156
  try {
1136
- unlinkSync2(filePath);
1157
+ unlinkSync3(filePath);
1137
1158
  } catch {
1138
1159
  }
1139
1160
  }
@@ -1251,11 +1272,11 @@ function deleteFile(id, dataDir2) {
1251
1272
  if (file && dataDir2) {
1252
1273
  const knowledgeRoot = resolve4(dataDir2, "knowledge");
1253
1274
  const filePathResolved = isAbsolute4(file.path) ? file.path : resolve4(dataDir2, file.path);
1254
- const inKnowledge = filePathResolved === knowledgeRoot || filePathResolved.startsWith(knowledgeRoot + sep3);
1275
+ const inKnowledge = filePathResolved === knowledgeRoot || filePathResolved.startsWith(knowledgeRoot + sep4);
1255
1276
  if (file.project_id === null && inKnowledge) {
1256
1277
  try {
1257
1278
  if (existsSync4(filePathResolved)) {
1258
- unlinkSync2(filePathResolved);
1279
+ unlinkSync3(filePathResolved);
1259
1280
  }
1260
1281
  } catch (e3) {
1261
1282
  console.error("Failed to delete Knowledge Base file from disk:", e3);
@@ -1858,8 +1879,8 @@ var init_settings = __esm({
1858
1879
  });
1859
1880
 
1860
1881
  // ../../packages/core/dist/agent-rules/index.js
1861
- import { lstatSync as lstatSync4, readdirSync as readdirSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync as unlinkSync3, existsSync as existsSync6 } from "fs";
1862
- import { join as join6, sep as sep4, dirname as dirname4, isAbsolute as isAbsolute5 } from "path";
1882
+ import { lstatSync as lstatSync4, readdirSync as readdirSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync as unlinkSync4, existsSync as existsSync6 } from "fs";
1883
+ import { join as join6, sep as sep5, dirname as dirname4, isAbsolute as isAbsolute5 } from "path";
1863
1884
  import { fileURLToPath as fileURLToPath2 } from "url";
1864
1885
  function loadCorePackageVersion() {
1865
1886
  try {
@@ -1922,7 +1943,7 @@ function isRegularNonSymlink(absPath) {
1922
1943
  }
1923
1944
  }
1924
1945
  function toForwardSlashes(p) {
1925
- return sep4 === "/" ? p : p.split(sep4).join("/");
1946
+ return sep5 === "/" ? p : p.split(sep5).join("/");
1926
1947
  }
1927
1948
  function detectAgentContextFiles(projectPath) {
1928
1949
  const out = [];
@@ -1971,8 +1992,8 @@ function parseMarker(content) {
1971
1992
  function injectOrUpdate(content, block, version2) {
1972
1993
  const parsed = parseMarker(content);
1973
1994
  if (parsed === null) {
1974
- const sep9 = content.length === 0 ? "" : content.endsWith("\n\n") ? "" : content.endsWith("\n") ? "\n" : "\n\n";
1975
- return { action: "updated", content: content + sep9 + block };
1995
+ const sep10 = content.length === 0 ? "" : content.endsWith("\n\n") ? "" : content.endsWith("\n") ? "\n" : "\n\n";
1996
+ return { action: "updated", content: content + sep10 + block };
1976
1997
  }
1977
1998
  if (parsed.kind === "malformed") {
1978
1999
  throw new InjectError("malformed", "kontexta rules marker is malformed (BEGIN with no matching END)");
@@ -1995,7 +2016,7 @@ function atomicWrite(absPath, content) {
1995
2016
  renameSync3(tmp, absPath);
1996
2017
  } catch (e3) {
1997
2018
  try {
1998
- unlinkSync3(tmp);
2019
+ unlinkSync4(tmp);
1999
2020
  } catch {
2000
2021
  }
2001
2022
  throw e3;
@@ -137499,7 +137520,7 @@ var require_util7 = __commonJS({
137499
137520
  return path3;
137500
137521
  });
137501
137522
  exports.normalize = normalize3;
137502
- function join18(aRoot, aPath) {
137523
+ function join17(aRoot, aPath) {
137503
137524
  if (aRoot === "") {
137504
137525
  aRoot = ".";
137505
137526
  }
@@ -137531,7 +137552,7 @@ var require_util7 = __commonJS({
137531
137552
  }
137532
137553
  return joined;
137533
137554
  }
137534
- exports.join = join18;
137555
+ exports.join = join17;
137535
137556
  exports.isAbsolute = function(aPath) {
137536
137557
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
137537
137558
  };
@@ -137745,7 +137766,7 @@ var require_util7 = __commonJS({
137745
137766
  parsed.path = parsed.path.substring(0, index + 1);
137746
137767
  }
137747
137768
  }
137748
- sourceURL = join18(urlGenerate(parsed), sourceURL);
137769
+ sourceURL = join17(urlGenerate(parsed), sourceURL);
137749
137770
  }
137750
137771
  return normalize3(sourceURL);
137751
137772
  }
@@ -329703,17 +329724,17 @@ var init_whats_new = __esm({
329703
329724
  });
329704
329725
 
329705
329726
  // ../../packages/core/dist/project-map/index.js
329706
- import { sep as sep5, join as join8, basename as basename2 } from "path";
329727
+ import { sep as sep6, join as join8, basename as basename2 } from "path";
329707
329728
  function emptyNode() {
329708
329729
  return { dirs: /* @__PURE__ */ new Map(), files: [] };
329709
329730
  }
329710
329731
  function stripRoot(filePath, root2) {
329711
- const rootWithSep = root2.endsWith(sep5) ? root2 : root2 + sep5;
329732
+ const rootWithSep = root2.endsWith(sep6) ? root2 : root2 + sep6;
329712
329733
  if (filePath === root2)
329713
329734
  return [];
329714
329735
  if (!filePath.startsWith(rootWithSep))
329715
329736
  return null;
329716
- return filePath.slice(rootWithSep.length).split(sep5).filter(Boolean);
329737
+ return filePath.slice(rootWithSep.length).split(sep6).filter(Boolean);
329717
329738
  }
329718
329739
  function locateFile(file, projectsById, dataDir2) {
329719
329740
  if (file.project_id == null) {
@@ -329856,73 +329877,6 @@ var init_project_map = __esm({
329856
329877
  }
329857
329878
  });
329858
329879
 
329859
- // ../../packages/core/dist/compat/env-shim.js
329860
- function migrateEnvVars() {
329861
- const migrated = [];
329862
- for (const [oldKey, newKey] of RENAMED_VARS) {
329863
- if (process.env[oldKey] && !process.env[newKey]) {
329864
- process.env[newKey] = process.env[oldKey];
329865
- migrated.push(oldKey);
329866
- }
329867
- }
329868
- if (migrated.length > 0) {
329869
- console.warn(`[Kontexta] Deprecated env vars migrated: ${migrated.join(", ")}. Rename to KONTEXTA_* prefix. Support removed in v2.0.`);
329870
- }
329871
- return migrated;
329872
- }
329873
- var RENAMED_VARS;
329874
- var init_env_shim = __esm({
329875
- "../../packages/core/dist/compat/env-shim.js"() {
329876
- "use strict";
329877
- RENAMED_VARS = [
329878
- ["MNEXIS_DATA_DIR", "KONTEXTA_DATA_DIR"],
329879
- ["MNEXIS_DB_PATH", "KONTEXTA_DB_PATH"],
329880
- ["MNEXIS_WS_HOST", "KONTEXTA_WS_HOST"],
329881
- ["MNEXIS_WS_ORIGINS", "KONTEXTA_WS_ORIGINS"],
329882
- ["MNEXIS_WS_TOKEN", "KONTEXTA_WS_TOKEN"],
329883
- ["MNEXIS_EXPORT_MAX_BYTES", "KONTEXTA_EXPORT_MAX_BYTES"],
329884
- ["MNEXIS_INSTALL_HINT", "KONTEXTA_INSTALL_HINT"],
329885
- ["MNEXIS_PROJECT_TOKEN_WARN", "KONTEXTA_PROJECT_TOKEN_WARN"],
329886
- ["MNEXIS_SHUTDOWN_DRAIN_MS", "KONTEXTA_SHUTDOWN_DRAIN_MS"]
329887
- ];
329888
- }
329889
- });
329890
-
329891
- // ../../packages/core/dist/compat/file-migration.js
329892
- import { existsSync as existsSync7, renameSync as renameSync4, copyFileSync as copyFileSync2 } from "fs";
329893
- import { join as join9 } from "path";
329894
- function migrateDataFiles(dataDir2) {
329895
- for (const [oldName, newName] of DB_RENAMES) {
329896
- const oldPath = join9(dataDir2, oldName);
329897
- const newPath = join9(dataDir2, newName);
329898
- if (existsSync7(oldPath) && !existsSync7(newPath)) {
329899
- copyFileSync2(oldPath, oldPath + ".bak");
329900
- renameSync4(oldPath, newPath);
329901
- console.warn(`[Kontexta] Migrated ${oldName} \u2192 ${newName} (backup: ${oldName}.bak). Automatic migration removed in v2.0.`);
329902
- }
329903
- }
329904
- }
329905
- function migrateProjectConfig(projectRoot) {
329906
- const oldConfig = join9(projectRoot, "mnexis.json");
329907
- const newConfig = join9(projectRoot, "kontexta.json");
329908
- if (existsSync7(oldConfig) && !existsSync7(newConfig)) {
329909
- copyFileSync2(oldConfig, oldConfig + ".bak");
329910
- renameSync4(oldConfig, newConfig);
329911
- console.warn(`[Kontexta] Migrated mnexis.json \u2192 kontexta.json in ${projectRoot} (backup: mnexis.json.bak). Automatic migration removed in v2.0.`);
329912
- }
329913
- }
329914
- var DB_RENAMES;
329915
- var init_file_migration = __esm({
329916
- "../../packages/core/dist/compat/file-migration.js"() {
329917
- "use strict";
329918
- DB_RENAMES = [
329919
- ["mnexis.db", "kontexta.db"],
329920
- ["mnexis.db-wal", "kontexta.db-wal"],
329921
- ["mnexis.db-shm", "kontexta.db-shm"]
329922
- ];
329923
- }
329924
- });
329925
-
329926
329880
  // ../../packages/core/dist/journal/types.js
329927
329881
  var init_types2 = __esm({
329928
329882
  "../../packages/core/dist/journal/types.js"() {
@@ -329932,7 +329886,7 @@ var init_types2 = __esm({
329932
329886
 
329933
329887
  // ../../packages/core/dist/journal/writer.js
329934
329888
  import { mkdirSync as mkdirSync6, openSync, closeSync, writeSync, fsyncSync } from "fs";
329935
- import { join as join10, dirname as dirname5 } from "path";
329889
+ import { join as join9, dirname as dirname5 } from "path";
329936
329890
  var JournalWriter;
329937
329891
  var init_writer = __esm({
329938
329892
  "../../packages/core/dist/journal/writer.js"() {
@@ -329942,7 +329896,7 @@ var init_writer = __esm({
329942
329896
  currentDay = null;
329943
329897
  rawDir;
329944
329898
  constructor(opts) {
329945
- this.rawDir = join10(opts.baseDir, opts.projectSlug, "raw");
329899
+ this.rawDir = join9(opts.baseDir, opts.projectSlug, "raw");
329946
329900
  }
329947
329901
  append(event) {
329948
329902
  const day = event.ts.slice(0, 10);
@@ -329964,7 +329918,7 @@ var init_writer = __esm({
329964
329918
  closeSync(this.fd);
329965
329919
  this.fd = null;
329966
329920
  }
329967
- const path3 = join10(this.rawDir, `${day}.jsonl`);
329921
+ const path3 = join9(this.rawDir, `${day}.jsonl`);
329968
329922
  mkdirSync6(dirname5(path3), { recursive: true });
329969
329923
  this.fd = openSync(path3, "a");
329970
329924
  this.currentDay = day;
@@ -330019,14 +329973,14 @@ var init_redact = __esm({
330019
329973
  });
330020
329974
 
330021
329975
  // ../../packages/core/dist/journal/high-water.js
330022
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync5, mkdirSync as mkdirSync7, existsSync as existsSync8 } from "fs";
330023
- import { join as join11 } from "path";
329976
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
329977
+ import { join as join10 } from "path";
330024
329978
  function pathFor(baseDir, projectSlug2) {
330025
- return join11(baseDir, projectSlug2, ".distilled-up-to.json");
329979
+ return join10(baseDir, projectSlug2, ".distilled-up-to.json");
330026
329980
  }
330027
329981
  function readHighWater(baseDir, projectSlug2) {
330028
329982
  const p = pathFor(baseDir, projectSlug2);
330029
- if (!existsSync8(p))
329983
+ if (!existsSync7(p))
330030
329984
  return null;
330031
329985
  try {
330032
329986
  const raw = readFileSync7(p, "utf8");
@@ -330040,12 +329994,12 @@ function readHighWater(baseDir, projectSlug2) {
330040
329994
  }
330041
329995
  }
330042
329996
  function writeHighWater(baseDir, projectSlug2, hw) {
330043
- const dir = join11(baseDir, projectSlug2);
329997
+ const dir = join10(baseDir, projectSlug2);
330044
329998
  mkdirSync7(dir, { recursive: true });
330045
329999
  const final = pathFor(baseDir, projectSlug2);
330046
330000
  const tmp = `${final}.tmp`;
330047
330001
  writeFileSync5(tmp, JSON.stringify(hw, null, 2));
330048
- renameSync5(tmp, final);
330002
+ renameSync4(tmp, final);
330049
330003
  }
330050
330004
  var init_high_water = __esm({
330051
330005
  "../../packages/core/dist/journal/high-water.js"() {
@@ -330665,12 +330619,12 @@ var init_repository = __esm({
330665
330619
  });
330666
330620
 
330667
330621
  // ../../packages/core/dist/journal/cooldown.js
330668
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
330669
- import { join as join12 } from "path";
330622
+ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, mkdirSync as mkdirSync8 } from "fs";
330623
+ import { join as join11 } from "path";
330670
330624
  function acquireCooldown(baseDir, projectSlug2, cooldownSeconds) {
330671
- mkdirSync8(join12(baseDir, projectSlug2), { recursive: true });
330672
- const lockPath = join12(baseDir, projectSlug2, ".distill.lock");
330673
- if (existsSync9(lockPath)) {
330625
+ mkdirSync8(join11(baseDir, projectSlug2), { recursive: true });
330626
+ const lockPath = join11(baseDir, projectSlug2, ".distill.lock");
330627
+ if (existsSync8(lockPath)) {
330674
330628
  try {
330675
330629
  const ts = Number(readFileSync8(lockPath, "utf8"));
330676
330630
  if (!isNaN(ts) && Date.now() - ts < cooldownSeconds * 1e3)
@@ -330682,10 +330636,10 @@ function acquireCooldown(baseDir, projectSlug2, cooldownSeconds) {
330682
330636
  return true;
330683
330637
  }
330684
330638
  function releaseCooldown(baseDir, projectSlug2) {
330685
- const lockPath = join12(baseDir, projectSlug2, ".distill.lock");
330686
- if (existsSync9(lockPath)) {
330639
+ const lockPath = join11(baseDir, projectSlug2, ".distill.lock");
330640
+ if (existsSync8(lockPath)) {
330687
330641
  try {
330688
- unlinkSync4(lockPath);
330642
+ unlinkSync5(lockPath);
330689
330643
  } catch {
330690
330644
  }
330691
330645
  }
@@ -330697,16 +330651,16 @@ var init_cooldown = __esm({
330697
330651
  });
330698
330652
 
330699
330653
  // ../../packages/core/dist/journal/distill.js
330700
- import { readFileSync as readFileSync9, readdirSync as readdirSync6, existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
330701
- import { join as join13 } from "path";
330654
+ import { readFileSync as readFileSync9, readdirSync as readdirSync6, existsSync as existsSync9, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
330655
+ import { join as join12 } from "path";
330702
330656
  function rawDir(opts) {
330703
- return join13(opts.dataDir, ...REL_BASE, opts.projectSlug, "raw");
330657
+ return join12(opts.dataDir, ...REL_BASE, opts.projectSlug, "raw");
330704
330658
  }
330705
330659
  function distilledDir(opts, ts) {
330706
- return join13(opts.dataDir, ...REL_BASE, opts.projectSlug, ts.slice(0, 4), ts.slice(5, 7), ts.slice(8, 10));
330660
+ return join12(opts.dataDir, ...REL_BASE, opts.projectSlug, ts.slice(0, 4), ts.slice(5, 7), ts.slice(8, 10));
330707
330661
  }
330708
330662
  async function distillJournal(opts) {
330709
- const cooldownBase = join13(opts.dataDir, ...REL_BASE);
330663
+ const cooldownBase = join12(opts.dataDir, ...REL_BASE);
330710
330664
  const cooldownSec = opts.cooldownSeconds ?? 0;
330711
330665
  if (!acquireCooldown(cooldownBase, opts.projectSlug, cooldownSec)) {
330712
330666
  return {
@@ -330718,7 +330672,7 @@ async function distillJournal(opts) {
330718
330672
  };
330719
330673
  }
330720
330674
  try {
330721
- const hw = readHighWater(join13(opts.dataDir, ...REL_BASE), opts.projectSlug);
330675
+ const hw = readHighWater(join12(opts.dataDir, ...REL_BASE), opts.projectSlug);
330722
330676
  const since = hw?.last_event_ts ?? "0000-01-01T00:00:00Z";
330723
330677
  const cutoff = new Date(opts.now.getTime() - opts.inFlightWindowSeconds * 1e3).toISOString();
330724
330678
  const events = readRawEvents(opts, since, cutoff, opts.maxEvents);
@@ -330734,7 +330688,7 @@ async function distillJournal(opts) {
330734
330688
  const dir = distilledDir(opts, lastEvent.ts);
330735
330689
  mkdirSync9(dir, { recursive: true });
330736
330690
  const filename = `task-${bucket.task_slug}.md`;
330737
- const filePath = join13(dir, filename);
330691
+ const filePath = join12(dir, filename);
330738
330692
  const fm = buildFrontmatter2(bucket, opts.projectSlug);
330739
330693
  const entry = renderMechanicalEntry({
330740
330694
  task_slug: bucket.task_slug,
@@ -330742,7 +330696,7 @@ async function distillJournal(opts) {
330742
330696
  now: lastEvent.ts,
330743
330697
  extraPatterns: opts.extraPatterns
330744
330698
  });
330745
- if (existsSync10(filePath)) {
330699
+ if (existsSync9(filePath)) {
330746
330700
  const existing = readFileSync9(filePath, "utf8");
330747
330701
  writeFileSync7(filePath, replaceOrAppendEntry(existing, fm, entry));
330748
330702
  } else {
@@ -330764,7 +330718,7 @@ async function distillJournal(opts) {
330764
330718
  });
330765
330719
  }
330766
330720
  const newHw = events[events.length - 1].ts;
330767
- writeHighWater(join13(opts.dataDir, ...REL_BASE), opts.projectSlug, {
330721
+ writeHighWater(join12(opts.dataDir, ...REL_BASE), opts.projectSlug, {
330768
330722
  last_event_ts: newHw,
330769
330723
  last_distilled_at: opts.now.toISOString(),
330770
330724
  events_processed: (hw?.events_processed ?? 0) + events.length
@@ -330782,17 +330736,17 @@ async function distillJournal(opts) {
330782
330736
  }
330783
330737
  function readRawEvents(opts, sinceTs, untilTs, max2) {
330784
330738
  const dirs = [rawDir(opts)];
330785
- const defaultDir = join13(opts.dataDir, ...REL_BASE, "default", "raw");
330786
- if (defaultDir !== dirs[0] && existsSync10(defaultDir)) {
330739
+ const defaultDir = join12(opts.dataDir, ...REL_BASE, "default", "raw");
330740
+ if (defaultDir !== dirs[0] && existsSync9(defaultDir)) {
330787
330741
  dirs.push(defaultDir);
330788
330742
  }
330789
330743
  const out = [];
330790
330744
  for (const dir of dirs) {
330791
- if (!existsSync10(dir))
330745
+ if (!existsSync9(dir))
330792
330746
  continue;
330793
330747
  const files = readdirSync6(dir).filter((f3) => f3.endsWith(".jsonl")).sort();
330794
330748
  for (const f3 of files) {
330795
- const lines = readFileSync9(join13(dir, f3), "utf8").split("\n").filter(Boolean);
330749
+ const lines = readFileSync9(join12(dir, f3), "utf8").split("\n").filter(Boolean);
330796
330750
  for (const line of lines) {
330797
330751
  try {
330798
330752
  const ev = JSON.parse(line);
@@ -330979,17 +330933,17 @@ var init_git_watcher = __esm({
330979
330933
  });
330980
330934
 
330981
330935
  // ../../packages/core/dist/journal/presence.js
330982
- import { readdirSync as readdirSync7, statSync as statSync3, existsSync as existsSync11 } from "fs";
330983
- import { join as join14 } from "path";
330936
+ import { readdirSync as readdirSync7, statSync as statSync3, existsSync as existsSync10 } from "fs";
330937
+ import { join as join13 } from "path";
330984
330938
  function isMcpActive(baseDir, projectSlug2, windowSec) {
330985
- const rawDir2 = join14(baseDir, projectSlug2, "raw");
330986
- if (!existsSync11(rawDir2))
330939
+ const rawDir2 = join13(baseDir, projectSlug2, "raw");
330940
+ if (!existsSync10(rawDir2))
330987
330941
  return false;
330988
330942
  const cutoff = Date.now() - windowSec * 1e3;
330989
330943
  for (const f3 of readdirSync7(rawDir2)) {
330990
330944
  if (!f3.endsWith(".jsonl"))
330991
330945
  continue;
330992
- if (statSync3(join14(rawDir2, f3)).mtimeMs >= cutoff)
330946
+ if (statSync3(join13(rawDir2, f3)).mtimeMs >= cutoff)
330993
330947
  return true;
330994
330948
  }
330995
330949
  return false;
@@ -331001,8 +330955,8 @@ var init_presence = __esm({
331001
330955
  });
331002
330956
 
331003
330957
  // ../../packages/core/dist/journal/housekeep.js
331004
- import { readdirSync as readdirSync8, statSync as statSync4, unlinkSync as unlinkSync5, renameSync as renameSync6, mkdirSync as mkdirSync10, existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
331005
- import { join as join15 } from "path";
330958
+ import { readdirSync as readdirSync8, statSync as statSync4, unlinkSync as unlinkSync6, renameSync as renameSync5, mkdirSync as mkdirSync10, existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
330959
+ import { join as join14 } from "path";
331006
330960
  function lastEventTs(path3) {
331007
330961
  try {
331008
330962
  const content = readFileSync10(path3, "utf8");
@@ -331026,12 +330980,12 @@ function housekeepJournal(cfg) {
331026
330980
  };
331027
330981
  if (cfg.retention.raw_days > 0) {
331028
330982
  const cutoff = now.getTime() - cfg.retention.raw_days * 864e5;
331029
- const rawDir2 = join15(cfg.baseDir, cfg.projectSlug, "raw");
331030
- if (existsSync12(rawDir2)) {
330983
+ const rawDir2 = join14(cfg.baseDir, cfg.projectSlug, "raw");
330984
+ if (existsSync11(rawDir2)) {
331031
330985
  const hw = readHighWater(cfg.baseDir, cfg.projectSlug);
331032
330986
  const highWaterTs = hw?.last_event_ts ?? null;
331033
330987
  for (const f3 of readdirSync8(rawDir2).filter((n3) => n3.endsWith(".jsonl"))) {
331034
- const p = join15(rawDir2, f3);
330988
+ const p = join14(rawDir2, f3);
331035
330989
  if (statSync4(p).mtimeMs >= cutoff)
331036
330990
  continue;
331037
330991
  const fileLastTs = lastEventTs(p);
@@ -331039,7 +330993,7 @@ function housekeepJournal(cfg) {
331039
330993
  result.raw_files_skipped_undistilled++;
331040
330994
  continue;
331041
330995
  }
331042
- unlinkSync5(p);
330996
+ unlinkSync6(p);
331043
330997
  result.raw_files_pruned++;
331044
330998
  }
331045
330999
  }
@@ -331053,12 +331007,12 @@ function housekeepJournal(cfg) {
331053
331007
  JOIN projects p ON p.id = jm.project_id
331054
331008
  WHERE p.slug = ? AND jm.last_active_at < ?
331055
331009
  `).all(cfg.projectSlug, archiveCutoff);
331056
- const archiveDir = join15(cfg.baseDir, cfg.projectSlug, "_archive");
331010
+ const archiveDir = join14(cfg.baseDir, cfg.projectSlug, "_archive");
331057
331011
  mkdirSync10(archiveDir, { recursive: true });
331058
331012
  for (const row of cold) {
331059
- const dest = join15(archiveDir, row.path.split("/").pop() ?? `task-${row.file_id}.md`);
331060
- if (!existsSync12(dest) && existsSync12(row.path)) {
331061
- renameSync6(row.path, dest);
331013
+ const dest = join14(archiveDir, row.path.split("/").pop() ?? `task-${row.file_id}.md`);
331014
+ if (!existsSync11(dest) && existsSync11(row.path)) {
331015
+ renameSync5(row.path, dest);
331062
331016
  db2.prepare(`UPDATE files SET path = ? WHERE id = ?`).run(dest, row.file_id);
331063
331017
  result.archived_tasks++;
331064
331018
  }
@@ -331215,9 +331169,6 @@ __export(dist_exports2, {
331215
331169
  listTags: () => listTags,
331216
331170
  loadExtraPatterns: () => loadExtraPatterns,
331217
331171
  markUpgradeApplied: () => markUpgradeApplied,
331218
- migrateDataFiles: () => migrateDataFiles,
331219
- migrateEnvVars: () => migrateEnvVars,
331220
- migrateProjectConfig: () => migrateProjectConfig,
331221
331172
  moveFile: () => moveFile,
331222
331173
  openTasksForProject: () => openTasksForProject,
331223
331174
  parseMarker: () => parseMarker,
@@ -331232,6 +331183,7 @@ __export(dist_exports2, {
331232
331183
  removeTags: () => removeTags,
331233
331184
  renderMechanicalEntry: () => renderMechanicalEntry,
331234
331185
  replaceSection: () => replaceSection,
331186
+ resetDataDirCache: () => resetDataDirCache,
331235
331187
  resolveSince: () => resolveSince,
331236
331188
  restoreVersion: () => restoreVersion,
331237
331189
  runPatterns: () => runPatterns,
@@ -331274,8 +331226,6 @@ var init_dist7 = __esm({
331274
331226
  init_clip();
331275
331227
  init_whats_new();
331276
331228
  init_project_map();
331277
- init_env_shim();
331278
- init_file_migration();
331279
331229
  init_journal();
331280
331230
  }
331281
331231
  });
@@ -331372,7 +331322,7 @@ __export(executor_exports, {
331372
331322
  resolveCwd: () => resolveCwd
331373
331323
  });
331374
331324
  import { spawn } from "child_process";
331375
- import { resolve as resolvePath, sep as sep7 } from "path";
331325
+ import { resolve as resolvePath, sep as sep8 } from "path";
331376
331326
  import { realpathSync } from "fs";
331377
331327
  function resolveArgv(command, params, defs, argSeparator) {
331378
331328
  const resolvedValues = {};
@@ -331428,7 +331378,7 @@ function resolveCwd(projectRoot, workingDir) {
331428
331378
  } catch {
331429
331379
  throw new Error(`workingDir does not exist: ${workingDir}`);
331430
331380
  }
331431
- if (real !== rootReal && !real.startsWith(rootReal + sep7)) {
331381
+ if (real !== rootReal && !real.startsWith(rootReal + sep8)) {
331432
331382
  throw new Error(`workingDir resolved outside project root`);
331433
331383
  }
331434
331384
  return real;
@@ -331596,8 +331546,9 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
331596
331546
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
331597
331547
  import { z as z6 } from "zod";
331598
331548
  import RE22 from "re2";
331599
- import { isAbsolute as isAbsolute7, join as join17, resolve as resolve7, sep as sep8, dirname as dirname6 } from "path";
331600
- import { statSync as statSync5, openSync as openSync2, readSync, closeSync as closeSync2, readFileSync as readFileSync12, existsSync as existsSync14, realpathSync as realpathSync2 } from "fs";
331549
+ import { isAbsolute as isAbsolute7, join as join16, resolve as resolve7, sep as sep9, dirname as dirname6 } from "path";
331550
+ 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
+ import { createHash as createHash3 } from "crypto";
331601
331552
  import { fileURLToPath as fileURLToPath3 } from "url";
331602
331553
 
331603
331554
  // src/hands/registry.ts
@@ -331605,9 +331556,8 @@ import { z as z2 } from "zod";
331605
331556
 
331606
331557
  // src/hands/loader.ts
331607
331558
  init_sanitizer();
331608
- init_dist7();
331609
- import { readFileSync as readFileSync11, existsSync as existsSync13 } from "fs";
331610
- import { join as join16, isAbsolute as isAbsolute6, normalize as normalize2, sep as sep6 } from "path";
331559
+ import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
331560
+ import { join as join15, isAbsolute as isAbsolute6, normalize as normalize2, sep as sep7 } from "path";
331611
331561
  var TOOL_NAME_RE = /^[a-z][a-z0-9-]*$/;
331612
331562
  var PARAM_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
331613
331563
  var FORBIDDEN_ENV = /* @__PURE__ */ new Set([
@@ -331624,9 +331574,8 @@ var TIMEOUT_DEFAULT = 6e4;
331624
331574
  var OUTPUT_MAX = 1e6;
331625
331575
  var OUTPUT_DEFAULT = 1e5;
331626
331576
  function loadProjectConfig(projectRoot) {
331627
- migrateProjectConfig(projectRoot);
331628
- const file = join16(projectRoot, "kontexta.json");
331629
- if (!existsSync13(file)) {
331577
+ const file = join15(projectRoot, "kontexta.json");
331578
+ if (!existsSync12(file)) {
331630
331579
  return { found: false, tools: {}, disabled: [], warnings: [], errors: [] };
331631
331580
  }
331632
331581
  let raw;
@@ -331676,7 +331625,7 @@ function validateTool(name50, def, projectRoot) {
331676
331625
  }
331677
331626
  if (!isLiteralArgv0(def.command[0])) return reject("argv[0] must be literal (no {{param}})");
331678
331627
  const argv0 = def.command[0];
331679
- if (!isAbsolute6(argv0) && (argv0.includes("/") || argv0.includes(sep6))) {
331628
+ if (!isAbsolute6(argv0) && (argv0.includes("/") || argv0.includes(sep7))) {
331680
331629
  return reject("argv[0] must be absolute or a bare command name (no relative paths)");
331681
331630
  }
331682
331631
  const paramDefs = def.params ?? {};
@@ -331709,7 +331658,7 @@ function validateTool(name50, def, projectRoot) {
331709
331658
  if (typeof def.workingDir !== "string") return reject("workingDir must be string");
331710
331659
  if (isAbsolute6(def.workingDir)) return reject("workingDir must be relative");
331711
331660
  const norm = normalize2(def.workingDir);
331712
- if (norm.startsWith("..") || norm.split(sep6).includes("..")) return reject("workingDir must not contain ..");
331661
+ if (norm.startsWith("..") || norm.split(sep7).includes("..")) return reject("workingDir must not contain ..");
331713
331662
  }
331714
331663
  if (def.env !== void 0) {
331715
331664
  if (typeof def.env !== "object" || def.env === null) return reject("env must be object");
@@ -332547,7 +332496,7 @@ function getAgentRulesWarning(projectId) {
332547
332496
  }
332548
332497
  const outdatedProjects = [];
332549
332498
  for (const p of projects) {
332550
- if (!p || !p.path || !existsSync14(p.path)) continue;
332499
+ if (!p || !p.path || !existsSync13(p.path)) continue;
332551
332500
  const contextFiles = detectAgentContextFiles(p.path);
332552
332501
  if (contextFiles.length === 0) continue;
332553
332502
  const statuses = checkAgentRulesStatus(p.path, contextFiles);
@@ -332616,8 +332565,8 @@ var pkgVersionFound = false;
332616
332565
  try {
332617
332566
  let currentDir = dirname6(fileURLToPath3(import.meta.url));
332618
332567
  while (currentDir !== dirname6(currentDir)) {
332619
- const p = join17(currentDir, "package.json");
332620
- if (existsSync14(p)) {
332568
+ const p = join16(currentDir, "package.json");
332569
+ if (existsSync13(p)) {
332621
332570
  const pkg = JSON.parse(readFileSync12(p, "utf8"));
332622
332571
  if (pkg.name === "kontexta-mcp" && pkg.version) {
332623
332572
  pkgVersion = pkg.version;
@@ -332638,7 +332587,7 @@ var server = new McpServer({
332638
332587
  version: pkgVersion
332639
332588
  });
332640
332589
  var handsRegistry = new HandsRegistry(server);
332641
- var baseJournalDir = join17(dataDir, "knowledge", "journal");
332590
+ var baseJournalDir = join16(dataDir, "knowledge", "journal");
332642
332591
  var projectSlug = process.env.KONTEXTA_DEFAULT_PROJECT_SLUG ?? "default";
332643
332592
  var agent = process.env.KONTEXTA_AGENT ?? "unknown";
332644
332593
  var sid = `${process.pid}-${Date.now().toString(36)}`;
@@ -332696,8 +332645,8 @@ server.tool(
332696
332645
  const journalFolder = "journal";
332697
332646
  const title = `journal-${today}`;
332698
332647
  const db2 = getDatabase();
332699
- const knowledgeDir = join17(dataDir, "knowledge");
332700
- const expectedPath = join17(knowledgeDir, journalFolder, `${title}.md`);
332648
+ const knowledgeDir = join16(dataDir, "knowledge");
332649
+ const expectedPath = join16(knowledgeDir, journalFolder, `${title}.md`);
332701
332650
  const existingRow = db2.prepare("SELECT id, path FROM files WHERE path = ? AND project_id IS NULL").get(expectedPath);
332702
332651
  if (existingRow) {
332703
332652
  const existing = readFile(existingRow.id);
@@ -332827,7 +332776,7 @@ server.tool(
332827
332776
  folder = parts.length > 1 ? parts.slice(0, -1).join("/") : null;
332828
332777
  }
332829
332778
  } else {
332830
- const knowledgeRoot = join17(dataDir, "knowledge");
332779
+ const knowledgeRoot = join16(dataDir, "knowledge");
332831
332780
  if (file.path?.startsWith(knowledgeRoot)) {
332832
332781
  const rel = file.path.slice(knowledgeRoot.length).replace(/^[\/\\]+/, "");
332833
332782
  const parts = rel.split(/[\/\\]/);
@@ -333276,7 +333225,7 @@ server.tool(
333276
333225
  async () => {
333277
333226
  const result = listProjects();
333278
333227
  const augmented = result.map((p) => {
333279
- const has_hands = !!(p.path && existsSync14(p.path) && existsSync14(`${p.path}/kontexta.json`));
333228
+ const has_hands = !!(p.path && existsSync13(p.path) && existsSync13(`${p.path}/kontexta.json`));
333280
333229
  const contextFiles = p.path ? detectAgentContextFiles(p.path) : [];
333281
333230
  const statuses = p.path ? checkAgentRulesStatus(p.path, contextFiles) : [];
333282
333231
  const outdated = statuses.filter((s3) => !s3.upToDate);
@@ -333484,6 +333433,130 @@ RETURNS: { written: [{ path, action: created|updated|skipped, version }], skippe
333484
333433
  }
333485
333434
  }
333486
333435
  );
333436
+ server.tool(
333437
+ "transfer_agent_context",
333438
+ `COPY existing agent context files (CLAUDE.md, AGENTS.md, .cursor/rules/*.mdc, etc.) from a project's repo into Kontexta's per-project knowledge base so they're indexed by FTS5 and can be git-synced through Kontexta's own backup engine.
333439
+
333440
+ This tool ONLY COPIES. It never deletes or modifies the originals in your repo. After a successful transfer, the response includes the list of source paths so the user can manually remove them if desired. No tool argument, no flag, and no code path in this tool ever calls a destructive filesystem operation against \`project.path\`.
333441
+
333442
+ MANDATORY: This tool writes new files into Kontexta's data dir. You MUST seek explicit user consent before calling. Set 'confirm: true' only after the user has agreed.
333443
+
333444
+ PARAMETERS:
333445
+ - project_id: number, required. Project ID returned from register_project.
333446
+ - confirm: boolean, required. Must be true.
333447
+ - files: string[], optional. Project-relative paths to transfer. Omit or pass [] to transfer all detected agent context files (uses the same detection list as register_project / onboard_agent).
333448
+
333449
+ RETURNS: { transferred: [{ source_path, kb_id, kb_path, est_tokens }], skipped: [{ source_path, reason }], next_action }
333450
+ Skip reasons: "missing" | "symlink" | "outside_project" | "already_transferred_same_content" | "read_error" | "write_error".
333451
+
333452
+ IDEMPOTENT: re-running with the same files copies nothing if the content is unchanged \u2014 duplicate transfers are detected via SHA-256 hash comparison against existing project KB rows.`,
333453
+ {
333454
+ project_id: z6.number().describe("Project ID returned from register_project"),
333455
+ confirm: z6.boolean().describe("MANDATORY: Set to true only after obtaining explicit user consent."),
333456
+ files: z6.array(z6.string()).optional().describe("Project-relative paths to transfer. Omit to transfer all detected context files.")
333457
+ },
333458
+ async ({ project_id, confirm, files }) => {
333459
+ try {
333460
+ if (confirm !== true) {
333461
+ return {
333462
+ isError: true,
333463
+ content: [{
333464
+ type: "text",
333465
+ text: JSON.stringify({
333466
+ error: "User consent required",
333467
+ details: "This tool copies project files into Kontexta's KB. Explain the proposed transfer to the user and obtain explicit consent, then re-run with 'confirm: true'. Originals in the project repo are NOT touched."
333468
+ }, null, 2)
333469
+ }]
333470
+ };
333471
+ }
333472
+ const db2 = getDatabase();
333473
+ const project = db2.prepare("SELECT id, name, slug, path FROM projects WHERE id = ?").get(project_id);
333474
+ if (!project || !project.path) {
333475
+ return {
333476
+ isError: true,
333477
+ content: [{ type: "text", text: JSON.stringify({ error: `Project ${project_id} not found or has no path` }, null, 2) }]
333478
+ };
333479
+ }
333480
+ const projectPath = project.path;
333481
+ const targetRels = files && files.length > 0 ? files : detectAgentContextFiles(projectPath);
333482
+ const existingHashes = new Set(
333483
+ db2.prepare("SELECT content_hash FROM files WHERE project_id = ? AND storage_type = 'local' AND content_hash IS NOT NULL").all(project.id).map((r5) => r5.content_hash)
333484
+ );
333485
+ const transferred = [];
333486
+ const skipped = [];
333487
+ for (const rel of targetRels) {
333488
+ let abs2;
333489
+ try {
333490
+ abs2 = assertPathInside(projectPath, rel);
333491
+ } catch {
333492
+ skipped.push({ source_path: rel, reason: "outside_project" });
333493
+ continue;
333494
+ }
333495
+ let stat;
333496
+ try {
333497
+ stat = lstatSync5(abs2);
333498
+ } catch {
333499
+ stat = null;
333500
+ }
333501
+ if (!stat) {
333502
+ skipped.push({ source_path: rel, reason: "missing" });
333503
+ continue;
333504
+ }
333505
+ if (stat.isSymbolicLink()) {
333506
+ skipped.push({ source_path: rel, reason: "symlink" });
333507
+ continue;
333508
+ }
333509
+ if (!stat.isFile()) {
333510
+ skipped.push({ source_path: rel, reason: "missing" });
333511
+ continue;
333512
+ }
333513
+ let content;
333514
+ try {
333515
+ content = readFileSync12(abs2, "utf8");
333516
+ } catch {
333517
+ skipped.push({ source_path: rel, reason: "read_error" });
333518
+ continue;
333519
+ }
333520
+ const hash = createHash3("sha256").update(content, "utf8").digest("hex");
333521
+ if (existingHashes.has(hash)) {
333522
+ skipped.push({ source_path: rel, reason: "already_transferred_same_content" });
333523
+ continue;
333524
+ }
333525
+ const titleSource = rel.replace(/\.[^./]+$/, "");
333526
+ const title = titleSource || rel;
333527
+ try {
333528
+ const created = await createFile({
333529
+ title,
333530
+ content,
333531
+ destination: "kontexta",
333532
+ projectId: project.id,
333533
+ folder: "agent-context",
333534
+ dataDir,
333535
+ sourcePath: abs2
333536
+ });
333537
+ existingHashes.add(hash);
333538
+ transferred.push({
333539
+ source_path: rel,
333540
+ kb_id: created.id,
333541
+ kb_path: created.path,
333542
+ est_tokens: estimateTokensFromBuffer(Buffer.from(content, "utf8"))
333543
+ });
333544
+ } catch (e3) {
333545
+ skipped.push({ source_path: rel, reason: `write_error: ${e3?.message ?? String(e3)}` });
333546
+ }
333547
+ }
333548
+ const next_action = transferred.length > 0 ? `Copied ${transferred.length} file(s) into Kontexta's KB. Originals in your repo are unchanged. To remove them yourself: ${transferred.map((t3) => `rm "${join16(projectPath, t3.source_path)}"`).join(" && ")}` : "No files were transferred. See skipped[] for reasons.";
333549
+ return {
333550
+ content: [{ type: "text", text: JSON.stringify({ transferred, skipped, next_action }, null, 2) }]
333551
+ };
333552
+ } catch (e3) {
333553
+ return {
333554
+ isError: true,
333555
+ content: [{ type: "text", text: JSON.stringify({ error: e3?.message ?? String(e3) }, null, 2) }]
333556
+ };
333557
+ }
333558
+ }
333559
+ );
333487
333560
  server.tool(
333488
333561
  "commit_backup",
333489
333562
  "SIDE-EFFECTFUL \u2014 TOUCHES THE NETWORK. Sync the project's KB data into its git backup directory, create a commit, and `git push` to `origin`. AUTH: relies on the local user's git credentials (SSH agent, credential helper, etc.) \u2014 there is no in-server auth. Kontexta does not rate-limit, but the remote may. Idempotent in steady state: a no-op commit is skipped, but the push still runs. Throws if the project has no configured backup repo or if push fails (network, auth, conflict). Returns `{success, copied_files_count, copied_paths}`. Use after a batch of KB writes to get changes off-machine.",
@@ -333757,7 +333830,7 @@ server.tool(
333757
333830
  );
333758
333831
  function resolveFolderBase(projectId) {
333759
333832
  if (projectId === void 0 || projectId === null) {
333760
- return join17(dataDir, "knowledge");
333833
+ return join16(dataDir, "knowledge");
333761
333834
  }
333762
333835
  const project = getDatabase().prepare("SELECT path FROM projects WHERE id = ?").get(projectId);
333763
333836
  if (!project?.path) {
@@ -333882,7 +333955,7 @@ server.tool(
333882
333955
  if (!project?.path) throw new Error(`Project not found for file ${file_id}`);
333883
333956
  base3 = project.path;
333884
333957
  } else {
333885
- base3 = join17(dataDir, "knowledge");
333958
+ base3 = join16(dataDir, "knowledge");
333886
333959
  }
333887
333960
  let baseResolved;
333888
333961
  try {
@@ -333898,8 +333971,8 @@ server.tool(
333898
333971
  } catch {
333899
333972
  throw new Error(`Destination parent directory does not exist: ${destParent}`);
333900
333973
  }
333901
- const destResolved = join17(destParentReal, destAbs.slice(destParent.length + (destParent.endsWith(sep8) ? 0 : 1)));
333902
- if (destResolved !== baseResolved && !destResolved.startsWith(baseResolved + sep8)) {
333974
+ const destResolved = join16(destParentReal, destAbs.slice(destParent.length + (destParent.endsWith(sep9) ? 0 : 1)));
333975
+ if (destResolved !== baseResolved && !destResolved.startsWith(baseResolved + sep9)) {
333903
333976
  throw new Error(`new_path must be inside ${base3}`);
333904
333977
  }
333905
333978
  let srcResolved;
@@ -333908,7 +333981,7 @@ server.tool(
333908
333981
  } catch {
333909
333982
  srcResolved = resolve7(file.path);
333910
333983
  }
333911
- if (srcResolved !== baseResolved && !srcResolved.startsWith(baseResolved + sep8)) {
333984
+ if (srcResolved !== baseResolved && !srcResolved.startsWith(baseResolved + sep9)) {
333912
333985
  throw new Error(`source path ${file.path} is no longer inside ${base3}; refusing to move`);
333913
333986
  }
333914
333987
  const updated = moveFile(file_id, new_path);
@@ -334603,7 +334676,7 @@ server.tool(
334603
334676
  "Re-scan every registered project's `kontexta.json` and rebuild the live Hands tool registry \u2014 newly-declared tools become callable immediately, removed tools disappear from `tools/list`. SIDE EFFECT is on the running MCP session's tool inventory only (no disk writes). Idempotent. No external auth or rate limits. Takes no parameters. Returns per-project load results (counts of registered/disabled tools and any validation warnings). Use after editing a `kontexta.json` mid-session; for the schema see `describe_hands_schema`.",
334604
334677
  {},
334605
334678
  async () => {
334606
- const projects = listProjects().filter((p) => p.path && existsSync14(p.path)).map((p) => ({ name: p.name, root: p.path }));
334679
+ const projects = listProjects().filter((p) => p.path && existsSync13(p.path)).map((p) => ({ name: p.name, root: p.path }));
334607
334680
  const r5 = handsRegistry.reloadAll(projects);
334608
334681
  return { content: [{ type: "text", text: JSON.stringify(r5, null, 2) }] };
334609
334682
  }
@@ -334634,7 +334707,7 @@ server.tool(
334634
334707
  async function main() {
334635
334708
  const transport = new StdioServerTransport();
334636
334709
  {
334637
- const projects = listProjects().filter((p) => p.path && existsSync14(p.path)).map((p) => ({ name: p.name, root: p.path }));
334710
+ const projects = listProjects().filter((p) => p.path && existsSync13(p.path)).map((p) => ({ name: p.name, root: p.path }));
334638
334711
  const r5 = handsRegistry.reloadAll(projects);
334639
334712
  console.error(
334640
334713
  `Kontexta Hands: loaded ${r5.perProject.length} projects, registered ${r5.totalRegistered} tools (${r5.totalDisabled} disabled)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kontexta-mcp",
3
- "version": "2.0.7",
3
+ "version": "2.0.10",
4
4
  "description": "The local Brain and Hands for AI coding agents (MCP Server)",
5
5
  "keywords": [
6
6
  "mcp",
@@ -61,7 +61,7 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@modelcontextprotocol/sdk": "^1.12.0",
64
- "better-sqlite3": "^11.7.0",
64
+ "better-sqlite3": "^12.10.0",
65
65
  "chokidar": "^4.0.0",
66
66
  "kxta-core": "workspace:*",
67
67
  "re2": "^1.24.1",
@@ -73,5 +73,5 @@
73
73
  "tsup": "^8.3.0",
74
74
  "typescript": "^5.7.0"
75
75
  },
76
- "rulesVersion": "2.0.0"
76
+ "rulesVersion": "2.1.0"
77
77
  }