kontexta-mcp 2.0.8 → 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;
@@ -420,8 +437,8 @@ var init_extensions = __esm({
420
437
  // ../../packages/core/dist/git/index.js
421
438
  import { createHash } from "crypto";
422
439
  import simpleGit from "simple-git";
423
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, copyFileSync, mkdirSync as mkdirSync3, existsSync as existsSync3, rmSync, readdirSync as readdirSync2, statSync, lstatSync, unlinkSync, renameSync } from "fs";
424
- 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";
425
442
  import { spawnSync } from "child_process";
426
443
  function redactCredentials(s3) {
427
444
  return s3.replace(/([a-z][a-z0-9+.-]*:\/\/)([^:@\/\s]+:[^@\/\s]+)@/gi, "$1***:***@");
@@ -540,7 +557,7 @@ async function restoreVersion(repoDir, filePath, commitHash) {
540
557
  renameSync(tmpPath, filePath);
541
558
  } catch (e3) {
542
559
  try {
543
- unlinkSync(tmpPath);
560
+ unlinkSync2(tmpPath);
544
561
  } catch {
545
562
  }
546
563
  throw e3;
@@ -818,7 +835,7 @@ async function _syncBackupLocked(projectId, dataDir2, onStage) {
818
835
  }
819
836
  } else if (!expectedBackupPaths.has(fullPath)) {
820
837
  try {
821
- unlinkSync(fullPath);
838
+ unlinkSync2(fullPath);
822
839
  } catch {
823
840
  }
824
841
  }
@@ -888,7 +905,7 @@ async function _syncBackupLocked(projectId, dataDir2, onStage) {
888
905
  if (project.path) {
889
906
  const localAbsolutePath = join3(project.path, relativePath);
890
907
  const projectResolved = resolve3(project.path);
891
- if (resolve3(localAbsolutePath) !== projectResolved && !resolve3(localAbsolutePath).startsWith(projectResolved + sep2)) {
908
+ if (resolve3(localAbsolutePath) !== projectResolved && !resolve3(localAbsolutePath).startsWith(projectResolved + sep3)) {
892
909
  continue;
893
910
  }
894
911
  mkdirSync3(dirname2(localAbsolutePath), { recursive: true });
@@ -954,12 +971,12 @@ async function _syncBackupLocked(projectId, dataDir2, onStage) {
954
971
  continue;
955
972
  const localAbsolutePath = join3(project.path, insideBackup);
956
973
  const resolvedLocal = resolve3(localAbsolutePath);
957
- if (resolvedLocal !== projectResolved && !resolvedLocal.startsWith(projectResolved + sep2)) {
974
+ if (resolvedLocal !== projectResolved && !resolvedLocal.startsWith(projectResolved + sep3)) {
958
975
  continue;
959
976
  }
960
977
  try {
961
978
  if (existsSync3(localAbsolutePath)) {
962
- unlinkSync(localAbsolutePath);
979
+ unlinkSync2(localAbsolutePath);
963
980
  }
964
981
  } catch (e3) {
965
982
  console.warn(`syncBackup: failed to remove ${localAbsolutePath}:`, e3);
@@ -1001,8 +1018,8 @@ var init_git = __esm({
1001
1018
 
1002
1019
  // ../../packages/core/dist/files/index.js
1003
1020
  import { createHash as createHash2 } from "crypto";
1004
- 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";
1005
- 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";
1006
1023
  function listProjectFolders(projectPath) {
1007
1024
  const folders = [];
1008
1025
  function scan(dir, currentRel) {
@@ -1137,7 +1154,7 @@ async function createFile(opts) {
1137
1154
  }
1138
1155
  } else {
1139
1156
  try {
1140
- unlinkSync2(filePath);
1157
+ unlinkSync3(filePath);
1141
1158
  } catch {
1142
1159
  }
1143
1160
  }
@@ -1255,11 +1272,11 @@ function deleteFile(id, dataDir2) {
1255
1272
  if (file && dataDir2) {
1256
1273
  const knowledgeRoot = resolve4(dataDir2, "knowledge");
1257
1274
  const filePathResolved = isAbsolute4(file.path) ? file.path : resolve4(dataDir2, file.path);
1258
- const inKnowledge = filePathResolved === knowledgeRoot || filePathResolved.startsWith(knowledgeRoot + sep3);
1275
+ const inKnowledge = filePathResolved === knowledgeRoot || filePathResolved.startsWith(knowledgeRoot + sep4);
1259
1276
  if (file.project_id === null && inKnowledge) {
1260
1277
  try {
1261
1278
  if (existsSync4(filePathResolved)) {
1262
- unlinkSync2(filePathResolved);
1279
+ unlinkSync3(filePathResolved);
1263
1280
  }
1264
1281
  } catch (e3) {
1265
1282
  console.error("Failed to delete Knowledge Base file from disk:", e3);
@@ -1862,8 +1879,8 @@ var init_settings = __esm({
1862
1879
  });
1863
1880
 
1864
1881
  // ../../packages/core/dist/agent-rules/index.js
1865
- 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";
1866
- 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";
1867
1884
  import { fileURLToPath as fileURLToPath2 } from "url";
1868
1885
  function loadCorePackageVersion() {
1869
1886
  try {
@@ -1926,7 +1943,7 @@ function isRegularNonSymlink(absPath) {
1926
1943
  }
1927
1944
  }
1928
1945
  function toForwardSlashes(p) {
1929
- return sep4 === "/" ? p : p.split(sep4).join("/");
1946
+ return sep5 === "/" ? p : p.split(sep5).join("/");
1930
1947
  }
1931
1948
  function detectAgentContextFiles(projectPath) {
1932
1949
  const out = [];
@@ -1975,8 +1992,8 @@ function parseMarker(content) {
1975
1992
  function injectOrUpdate(content, block, version2) {
1976
1993
  const parsed = parseMarker(content);
1977
1994
  if (parsed === null) {
1978
- const sep9 = content.length === 0 ? "" : content.endsWith("\n\n") ? "" : content.endsWith("\n") ? "\n" : "\n\n";
1979
- 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 };
1980
1997
  }
1981
1998
  if (parsed.kind === "malformed") {
1982
1999
  throw new InjectError("malformed", "kontexta rules marker is malformed (BEGIN with no matching END)");
@@ -1999,7 +2016,7 @@ function atomicWrite(absPath, content) {
1999
2016
  renameSync3(tmp, absPath);
2000
2017
  } catch (e3) {
2001
2018
  try {
2002
- unlinkSync3(tmp);
2019
+ unlinkSync4(tmp);
2003
2020
  } catch {
2004
2021
  }
2005
2022
  throw e3;
@@ -137503,7 +137520,7 @@ var require_util7 = __commonJS({
137503
137520
  return path3;
137504
137521
  });
137505
137522
  exports.normalize = normalize3;
137506
- function join18(aRoot, aPath) {
137523
+ function join17(aRoot, aPath) {
137507
137524
  if (aRoot === "") {
137508
137525
  aRoot = ".";
137509
137526
  }
@@ -137535,7 +137552,7 @@ var require_util7 = __commonJS({
137535
137552
  }
137536
137553
  return joined;
137537
137554
  }
137538
- exports.join = join18;
137555
+ exports.join = join17;
137539
137556
  exports.isAbsolute = function(aPath) {
137540
137557
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
137541
137558
  };
@@ -137749,7 +137766,7 @@ var require_util7 = __commonJS({
137749
137766
  parsed.path = parsed.path.substring(0, index + 1);
137750
137767
  }
137751
137768
  }
137752
- sourceURL = join18(urlGenerate(parsed), sourceURL);
137769
+ sourceURL = join17(urlGenerate(parsed), sourceURL);
137753
137770
  }
137754
137771
  return normalize3(sourceURL);
137755
137772
  }
@@ -329707,17 +329724,17 @@ var init_whats_new = __esm({
329707
329724
  });
329708
329725
 
329709
329726
  // ../../packages/core/dist/project-map/index.js
329710
- 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";
329711
329728
  function emptyNode() {
329712
329729
  return { dirs: /* @__PURE__ */ new Map(), files: [] };
329713
329730
  }
329714
329731
  function stripRoot(filePath, root2) {
329715
- const rootWithSep = root2.endsWith(sep5) ? root2 : root2 + sep5;
329732
+ const rootWithSep = root2.endsWith(sep6) ? root2 : root2 + sep6;
329716
329733
  if (filePath === root2)
329717
329734
  return [];
329718
329735
  if (!filePath.startsWith(rootWithSep))
329719
329736
  return null;
329720
- return filePath.slice(rootWithSep.length).split(sep5).filter(Boolean);
329737
+ return filePath.slice(rootWithSep.length).split(sep6).filter(Boolean);
329721
329738
  }
329722
329739
  function locateFile(file, projectsById, dataDir2) {
329723
329740
  if (file.project_id == null) {
@@ -329860,73 +329877,6 @@ var init_project_map = __esm({
329860
329877
  }
329861
329878
  });
329862
329879
 
329863
- // ../../packages/core/dist/compat/env-shim.js
329864
- function migrateEnvVars() {
329865
- const migrated = [];
329866
- for (const [oldKey, newKey] of RENAMED_VARS) {
329867
- if (process.env[oldKey] && !process.env[newKey]) {
329868
- process.env[newKey] = process.env[oldKey];
329869
- migrated.push(oldKey);
329870
- }
329871
- }
329872
- if (migrated.length > 0) {
329873
- console.warn(`[Kontexta] Deprecated env vars migrated: ${migrated.join(", ")}. Rename to KONTEXTA_* prefix. Support removed in v2.0.`);
329874
- }
329875
- return migrated;
329876
- }
329877
- var RENAMED_VARS;
329878
- var init_env_shim = __esm({
329879
- "../../packages/core/dist/compat/env-shim.js"() {
329880
- "use strict";
329881
- RENAMED_VARS = [
329882
- ["MNEXIS_DATA_DIR", "KONTEXTA_DATA_DIR"],
329883
- ["MNEXIS_DB_PATH", "KONTEXTA_DB_PATH"],
329884
- ["MNEXIS_WS_HOST", "KONTEXTA_WS_HOST"],
329885
- ["MNEXIS_WS_ORIGINS", "KONTEXTA_WS_ORIGINS"],
329886
- ["MNEXIS_WS_TOKEN", "KONTEXTA_WS_TOKEN"],
329887
- ["MNEXIS_EXPORT_MAX_BYTES", "KONTEXTA_EXPORT_MAX_BYTES"],
329888
- ["MNEXIS_INSTALL_HINT", "KONTEXTA_INSTALL_HINT"],
329889
- ["MNEXIS_PROJECT_TOKEN_WARN", "KONTEXTA_PROJECT_TOKEN_WARN"],
329890
- ["MNEXIS_SHUTDOWN_DRAIN_MS", "KONTEXTA_SHUTDOWN_DRAIN_MS"]
329891
- ];
329892
- }
329893
- });
329894
-
329895
- // ../../packages/core/dist/compat/file-migration.js
329896
- import { existsSync as existsSync7, renameSync as renameSync4, copyFileSync as copyFileSync2 } from "fs";
329897
- import { join as join9 } from "path";
329898
- function migrateDataFiles(dataDir2) {
329899
- for (const [oldName, newName] of DB_RENAMES) {
329900
- const oldPath = join9(dataDir2, oldName);
329901
- const newPath = join9(dataDir2, newName);
329902
- if (existsSync7(oldPath) && !existsSync7(newPath)) {
329903
- copyFileSync2(oldPath, oldPath + ".bak");
329904
- renameSync4(oldPath, newPath);
329905
- console.warn(`[Kontexta] Migrated ${oldName} \u2192 ${newName} (backup: ${oldName}.bak). Automatic migration removed in v2.0.`);
329906
- }
329907
- }
329908
- }
329909
- function migrateProjectConfig(projectRoot) {
329910
- const oldConfig = join9(projectRoot, "mnexis.json");
329911
- const newConfig = join9(projectRoot, "kontexta.json");
329912
- if (existsSync7(oldConfig) && !existsSync7(newConfig)) {
329913
- copyFileSync2(oldConfig, oldConfig + ".bak");
329914
- renameSync4(oldConfig, newConfig);
329915
- console.warn(`[Kontexta] Migrated mnexis.json \u2192 kontexta.json in ${projectRoot} (backup: mnexis.json.bak). Automatic migration removed in v2.0.`);
329916
- }
329917
- }
329918
- var DB_RENAMES;
329919
- var init_file_migration = __esm({
329920
- "../../packages/core/dist/compat/file-migration.js"() {
329921
- "use strict";
329922
- DB_RENAMES = [
329923
- ["mnexis.db", "kontexta.db"],
329924
- ["mnexis.db-wal", "kontexta.db-wal"],
329925
- ["mnexis.db-shm", "kontexta.db-shm"]
329926
- ];
329927
- }
329928
- });
329929
-
329930
329880
  // ../../packages/core/dist/journal/types.js
329931
329881
  var init_types2 = __esm({
329932
329882
  "../../packages/core/dist/journal/types.js"() {
@@ -329936,7 +329886,7 @@ var init_types2 = __esm({
329936
329886
 
329937
329887
  // ../../packages/core/dist/journal/writer.js
329938
329888
  import { mkdirSync as mkdirSync6, openSync, closeSync, writeSync, fsyncSync } from "fs";
329939
- import { join as join10, dirname as dirname5 } from "path";
329889
+ import { join as join9, dirname as dirname5 } from "path";
329940
329890
  var JournalWriter;
329941
329891
  var init_writer = __esm({
329942
329892
  "../../packages/core/dist/journal/writer.js"() {
@@ -329946,7 +329896,7 @@ var init_writer = __esm({
329946
329896
  currentDay = null;
329947
329897
  rawDir;
329948
329898
  constructor(opts) {
329949
- this.rawDir = join10(opts.baseDir, opts.projectSlug, "raw");
329899
+ this.rawDir = join9(opts.baseDir, opts.projectSlug, "raw");
329950
329900
  }
329951
329901
  append(event) {
329952
329902
  const day = event.ts.slice(0, 10);
@@ -329968,7 +329918,7 @@ var init_writer = __esm({
329968
329918
  closeSync(this.fd);
329969
329919
  this.fd = null;
329970
329920
  }
329971
- const path3 = join10(this.rawDir, `${day}.jsonl`);
329921
+ const path3 = join9(this.rawDir, `${day}.jsonl`);
329972
329922
  mkdirSync6(dirname5(path3), { recursive: true });
329973
329923
  this.fd = openSync(path3, "a");
329974
329924
  this.currentDay = day;
@@ -330023,14 +329973,14 @@ var init_redact = __esm({
330023
329973
  });
330024
329974
 
330025
329975
  // ../../packages/core/dist/journal/high-water.js
330026
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync5, mkdirSync as mkdirSync7, existsSync as existsSync8 } from "fs";
330027
- 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";
330028
329978
  function pathFor(baseDir, projectSlug2) {
330029
- return join11(baseDir, projectSlug2, ".distilled-up-to.json");
329979
+ return join10(baseDir, projectSlug2, ".distilled-up-to.json");
330030
329980
  }
330031
329981
  function readHighWater(baseDir, projectSlug2) {
330032
329982
  const p = pathFor(baseDir, projectSlug2);
330033
- if (!existsSync8(p))
329983
+ if (!existsSync7(p))
330034
329984
  return null;
330035
329985
  try {
330036
329986
  const raw = readFileSync7(p, "utf8");
@@ -330044,12 +329994,12 @@ function readHighWater(baseDir, projectSlug2) {
330044
329994
  }
330045
329995
  }
330046
329996
  function writeHighWater(baseDir, projectSlug2, hw) {
330047
- const dir = join11(baseDir, projectSlug2);
329997
+ const dir = join10(baseDir, projectSlug2);
330048
329998
  mkdirSync7(dir, { recursive: true });
330049
329999
  const final = pathFor(baseDir, projectSlug2);
330050
330000
  const tmp = `${final}.tmp`;
330051
330001
  writeFileSync5(tmp, JSON.stringify(hw, null, 2));
330052
- renameSync5(tmp, final);
330002
+ renameSync4(tmp, final);
330053
330003
  }
330054
330004
  var init_high_water = __esm({
330055
330005
  "../../packages/core/dist/journal/high-water.js"() {
@@ -330669,12 +330619,12 @@ var init_repository = __esm({
330669
330619
  });
330670
330620
 
330671
330621
  // ../../packages/core/dist/journal/cooldown.js
330672
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
330673
- 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";
330674
330624
  function acquireCooldown(baseDir, projectSlug2, cooldownSeconds) {
330675
- mkdirSync8(join12(baseDir, projectSlug2), { recursive: true });
330676
- const lockPath = join12(baseDir, projectSlug2, ".distill.lock");
330677
- if (existsSync9(lockPath)) {
330625
+ mkdirSync8(join11(baseDir, projectSlug2), { recursive: true });
330626
+ const lockPath = join11(baseDir, projectSlug2, ".distill.lock");
330627
+ if (existsSync8(lockPath)) {
330678
330628
  try {
330679
330629
  const ts = Number(readFileSync8(lockPath, "utf8"));
330680
330630
  if (!isNaN(ts) && Date.now() - ts < cooldownSeconds * 1e3)
@@ -330686,10 +330636,10 @@ function acquireCooldown(baseDir, projectSlug2, cooldownSeconds) {
330686
330636
  return true;
330687
330637
  }
330688
330638
  function releaseCooldown(baseDir, projectSlug2) {
330689
- const lockPath = join12(baseDir, projectSlug2, ".distill.lock");
330690
- if (existsSync9(lockPath)) {
330639
+ const lockPath = join11(baseDir, projectSlug2, ".distill.lock");
330640
+ if (existsSync8(lockPath)) {
330691
330641
  try {
330692
- unlinkSync4(lockPath);
330642
+ unlinkSync5(lockPath);
330693
330643
  } catch {
330694
330644
  }
330695
330645
  }
@@ -330701,16 +330651,16 @@ var init_cooldown = __esm({
330701
330651
  });
330702
330652
 
330703
330653
  // ../../packages/core/dist/journal/distill.js
330704
- import { readFileSync as readFileSync9, readdirSync as readdirSync6, existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
330705
- 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";
330706
330656
  function rawDir(opts) {
330707
- return join13(opts.dataDir, ...REL_BASE, opts.projectSlug, "raw");
330657
+ return join12(opts.dataDir, ...REL_BASE, opts.projectSlug, "raw");
330708
330658
  }
330709
330659
  function distilledDir(opts, ts) {
330710
- 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));
330711
330661
  }
330712
330662
  async function distillJournal(opts) {
330713
- const cooldownBase = join13(opts.dataDir, ...REL_BASE);
330663
+ const cooldownBase = join12(opts.dataDir, ...REL_BASE);
330714
330664
  const cooldownSec = opts.cooldownSeconds ?? 0;
330715
330665
  if (!acquireCooldown(cooldownBase, opts.projectSlug, cooldownSec)) {
330716
330666
  return {
@@ -330722,7 +330672,7 @@ async function distillJournal(opts) {
330722
330672
  };
330723
330673
  }
330724
330674
  try {
330725
- const hw = readHighWater(join13(opts.dataDir, ...REL_BASE), opts.projectSlug);
330675
+ const hw = readHighWater(join12(opts.dataDir, ...REL_BASE), opts.projectSlug);
330726
330676
  const since = hw?.last_event_ts ?? "0000-01-01T00:00:00Z";
330727
330677
  const cutoff = new Date(opts.now.getTime() - opts.inFlightWindowSeconds * 1e3).toISOString();
330728
330678
  const events = readRawEvents(opts, since, cutoff, opts.maxEvents);
@@ -330738,7 +330688,7 @@ async function distillJournal(opts) {
330738
330688
  const dir = distilledDir(opts, lastEvent.ts);
330739
330689
  mkdirSync9(dir, { recursive: true });
330740
330690
  const filename = `task-${bucket.task_slug}.md`;
330741
- const filePath = join13(dir, filename);
330691
+ const filePath = join12(dir, filename);
330742
330692
  const fm = buildFrontmatter2(bucket, opts.projectSlug);
330743
330693
  const entry = renderMechanicalEntry({
330744
330694
  task_slug: bucket.task_slug,
@@ -330746,7 +330696,7 @@ async function distillJournal(opts) {
330746
330696
  now: lastEvent.ts,
330747
330697
  extraPatterns: opts.extraPatterns
330748
330698
  });
330749
- if (existsSync10(filePath)) {
330699
+ if (existsSync9(filePath)) {
330750
330700
  const existing = readFileSync9(filePath, "utf8");
330751
330701
  writeFileSync7(filePath, replaceOrAppendEntry(existing, fm, entry));
330752
330702
  } else {
@@ -330768,7 +330718,7 @@ async function distillJournal(opts) {
330768
330718
  });
330769
330719
  }
330770
330720
  const newHw = events[events.length - 1].ts;
330771
- writeHighWater(join13(opts.dataDir, ...REL_BASE), opts.projectSlug, {
330721
+ writeHighWater(join12(opts.dataDir, ...REL_BASE), opts.projectSlug, {
330772
330722
  last_event_ts: newHw,
330773
330723
  last_distilled_at: opts.now.toISOString(),
330774
330724
  events_processed: (hw?.events_processed ?? 0) + events.length
@@ -330786,17 +330736,17 @@ async function distillJournal(opts) {
330786
330736
  }
330787
330737
  function readRawEvents(opts, sinceTs, untilTs, max2) {
330788
330738
  const dirs = [rawDir(opts)];
330789
- const defaultDir = join13(opts.dataDir, ...REL_BASE, "default", "raw");
330790
- if (defaultDir !== dirs[0] && existsSync10(defaultDir)) {
330739
+ const defaultDir = join12(opts.dataDir, ...REL_BASE, "default", "raw");
330740
+ if (defaultDir !== dirs[0] && existsSync9(defaultDir)) {
330791
330741
  dirs.push(defaultDir);
330792
330742
  }
330793
330743
  const out = [];
330794
330744
  for (const dir of dirs) {
330795
- if (!existsSync10(dir))
330745
+ if (!existsSync9(dir))
330796
330746
  continue;
330797
330747
  const files = readdirSync6(dir).filter((f3) => f3.endsWith(".jsonl")).sort();
330798
330748
  for (const f3 of files) {
330799
- const lines = readFileSync9(join13(dir, f3), "utf8").split("\n").filter(Boolean);
330749
+ const lines = readFileSync9(join12(dir, f3), "utf8").split("\n").filter(Boolean);
330800
330750
  for (const line of lines) {
330801
330751
  try {
330802
330752
  const ev = JSON.parse(line);
@@ -330983,17 +330933,17 @@ var init_git_watcher = __esm({
330983
330933
  });
330984
330934
 
330985
330935
  // ../../packages/core/dist/journal/presence.js
330986
- import { readdirSync as readdirSync7, statSync as statSync3, existsSync as existsSync11 } from "fs";
330987
- 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";
330988
330938
  function isMcpActive(baseDir, projectSlug2, windowSec) {
330989
- const rawDir2 = join14(baseDir, projectSlug2, "raw");
330990
- if (!existsSync11(rawDir2))
330939
+ const rawDir2 = join13(baseDir, projectSlug2, "raw");
330940
+ if (!existsSync10(rawDir2))
330991
330941
  return false;
330992
330942
  const cutoff = Date.now() - windowSec * 1e3;
330993
330943
  for (const f3 of readdirSync7(rawDir2)) {
330994
330944
  if (!f3.endsWith(".jsonl"))
330995
330945
  continue;
330996
- if (statSync3(join14(rawDir2, f3)).mtimeMs >= cutoff)
330946
+ if (statSync3(join13(rawDir2, f3)).mtimeMs >= cutoff)
330997
330947
  return true;
330998
330948
  }
330999
330949
  return false;
@@ -331005,8 +330955,8 @@ var init_presence = __esm({
331005
330955
  });
331006
330956
 
331007
330957
  // ../../packages/core/dist/journal/housekeep.js
331008
- import { readdirSync as readdirSync8, statSync as statSync4, unlinkSync as unlinkSync5, renameSync as renameSync6, mkdirSync as mkdirSync10, existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
331009
- 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";
331010
330960
  function lastEventTs(path3) {
331011
330961
  try {
331012
330962
  const content = readFileSync10(path3, "utf8");
@@ -331030,12 +330980,12 @@ function housekeepJournal(cfg) {
331030
330980
  };
331031
330981
  if (cfg.retention.raw_days > 0) {
331032
330982
  const cutoff = now.getTime() - cfg.retention.raw_days * 864e5;
331033
- const rawDir2 = join15(cfg.baseDir, cfg.projectSlug, "raw");
331034
- if (existsSync12(rawDir2)) {
330983
+ const rawDir2 = join14(cfg.baseDir, cfg.projectSlug, "raw");
330984
+ if (existsSync11(rawDir2)) {
331035
330985
  const hw = readHighWater(cfg.baseDir, cfg.projectSlug);
331036
330986
  const highWaterTs = hw?.last_event_ts ?? null;
331037
330987
  for (const f3 of readdirSync8(rawDir2).filter((n3) => n3.endsWith(".jsonl"))) {
331038
- const p = join15(rawDir2, f3);
330988
+ const p = join14(rawDir2, f3);
331039
330989
  if (statSync4(p).mtimeMs >= cutoff)
331040
330990
  continue;
331041
330991
  const fileLastTs = lastEventTs(p);
@@ -331043,7 +330993,7 @@ function housekeepJournal(cfg) {
331043
330993
  result.raw_files_skipped_undistilled++;
331044
330994
  continue;
331045
330995
  }
331046
- unlinkSync5(p);
330996
+ unlinkSync6(p);
331047
330997
  result.raw_files_pruned++;
331048
330998
  }
331049
330999
  }
@@ -331057,12 +331007,12 @@ function housekeepJournal(cfg) {
331057
331007
  JOIN projects p ON p.id = jm.project_id
331058
331008
  WHERE p.slug = ? AND jm.last_active_at < ?
331059
331009
  `).all(cfg.projectSlug, archiveCutoff);
331060
- const archiveDir = join15(cfg.baseDir, cfg.projectSlug, "_archive");
331010
+ const archiveDir = join14(cfg.baseDir, cfg.projectSlug, "_archive");
331061
331011
  mkdirSync10(archiveDir, { recursive: true });
331062
331012
  for (const row of cold) {
331063
- const dest = join15(archiveDir, row.path.split("/").pop() ?? `task-${row.file_id}.md`);
331064
- if (!existsSync12(dest) && existsSync12(row.path)) {
331065
- 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);
331066
331016
  db2.prepare(`UPDATE files SET path = ? WHERE id = ?`).run(dest, row.file_id);
331067
331017
  result.archived_tasks++;
331068
331018
  }
@@ -331219,9 +331169,6 @@ __export(dist_exports2, {
331219
331169
  listTags: () => listTags,
331220
331170
  loadExtraPatterns: () => loadExtraPatterns,
331221
331171
  markUpgradeApplied: () => markUpgradeApplied,
331222
- migrateDataFiles: () => migrateDataFiles,
331223
- migrateEnvVars: () => migrateEnvVars,
331224
- migrateProjectConfig: () => migrateProjectConfig,
331225
331172
  moveFile: () => moveFile,
331226
331173
  openTasksForProject: () => openTasksForProject,
331227
331174
  parseMarker: () => parseMarker,
@@ -331236,6 +331183,7 @@ __export(dist_exports2, {
331236
331183
  removeTags: () => removeTags,
331237
331184
  renderMechanicalEntry: () => renderMechanicalEntry,
331238
331185
  replaceSection: () => replaceSection,
331186
+ resetDataDirCache: () => resetDataDirCache,
331239
331187
  resolveSince: () => resolveSince,
331240
331188
  restoreVersion: () => restoreVersion,
331241
331189
  runPatterns: () => runPatterns,
@@ -331278,8 +331226,6 @@ var init_dist7 = __esm({
331278
331226
  init_clip();
331279
331227
  init_whats_new();
331280
331228
  init_project_map();
331281
- init_env_shim();
331282
- init_file_migration();
331283
331229
  init_journal();
331284
331230
  }
331285
331231
  });
@@ -331376,7 +331322,7 @@ __export(executor_exports, {
331376
331322
  resolveCwd: () => resolveCwd
331377
331323
  });
331378
331324
  import { spawn } from "child_process";
331379
- import { resolve as resolvePath, sep as sep7 } from "path";
331325
+ import { resolve as resolvePath, sep as sep8 } from "path";
331380
331326
  import { realpathSync } from "fs";
331381
331327
  function resolveArgv(command, params, defs, argSeparator) {
331382
331328
  const resolvedValues = {};
@@ -331432,7 +331378,7 @@ function resolveCwd(projectRoot, workingDir) {
331432
331378
  } catch {
331433
331379
  throw new Error(`workingDir does not exist: ${workingDir}`);
331434
331380
  }
331435
- if (real !== rootReal && !real.startsWith(rootReal + sep7)) {
331381
+ if (real !== rootReal && !real.startsWith(rootReal + sep8)) {
331436
331382
  throw new Error(`workingDir resolved outside project root`);
331437
331383
  }
331438
331384
  return real;
@@ -331600,8 +331546,9 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
331600
331546
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
331601
331547
  import { z as z6 } from "zod";
331602
331548
  import RE22 from "re2";
331603
- import { isAbsolute as isAbsolute7, join as join17, resolve as resolve7, sep as sep8, dirname as dirname6 } from "path";
331604
- 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";
331605
331552
  import { fileURLToPath as fileURLToPath3 } from "url";
331606
331553
 
331607
331554
  // src/hands/registry.ts
@@ -331609,9 +331556,8 @@ import { z as z2 } from "zod";
331609
331556
 
331610
331557
  // src/hands/loader.ts
331611
331558
  init_sanitizer();
331612
- init_dist7();
331613
- import { readFileSync as readFileSync11, existsSync as existsSync13 } from "fs";
331614
- 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";
331615
331561
  var TOOL_NAME_RE = /^[a-z][a-z0-9-]*$/;
331616
331562
  var PARAM_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
331617
331563
  var FORBIDDEN_ENV = /* @__PURE__ */ new Set([
@@ -331628,9 +331574,8 @@ var TIMEOUT_DEFAULT = 6e4;
331628
331574
  var OUTPUT_MAX = 1e6;
331629
331575
  var OUTPUT_DEFAULT = 1e5;
331630
331576
  function loadProjectConfig(projectRoot) {
331631
- migrateProjectConfig(projectRoot);
331632
- const file = join16(projectRoot, "kontexta.json");
331633
- if (!existsSync13(file)) {
331577
+ const file = join15(projectRoot, "kontexta.json");
331578
+ if (!existsSync12(file)) {
331634
331579
  return { found: false, tools: {}, disabled: [], warnings: [], errors: [] };
331635
331580
  }
331636
331581
  let raw;
@@ -331680,7 +331625,7 @@ function validateTool(name50, def, projectRoot) {
331680
331625
  }
331681
331626
  if (!isLiteralArgv0(def.command[0])) return reject("argv[0] must be literal (no {{param}})");
331682
331627
  const argv0 = def.command[0];
331683
- if (!isAbsolute6(argv0) && (argv0.includes("/") || argv0.includes(sep6))) {
331628
+ if (!isAbsolute6(argv0) && (argv0.includes("/") || argv0.includes(sep7))) {
331684
331629
  return reject("argv[0] must be absolute or a bare command name (no relative paths)");
331685
331630
  }
331686
331631
  const paramDefs = def.params ?? {};
@@ -331713,7 +331658,7 @@ function validateTool(name50, def, projectRoot) {
331713
331658
  if (typeof def.workingDir !== "string") return reject("workingDir must be string");
331714
331659
  if (isAbsolute6(def.workingDir)) return reject("workingDir must be relative");
331715
331660
  const norm = normalize2(def.workingDir);
331716
- 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 ..");
331717
331662
  }
331718
331663
  if (def.env !== void 0) {
331719
331664
  if (typeof def.env !== "object" || def.env === null) return reject("env must be object");
@@ -332551,7 +332496,7 @@ function getAgentRulesWarning(projectId) {
332551
332496
  }
332552
332497
  const outdatedProjects = [];
332553
332498
  for (const p of projects) {
332554
- if (!p || !p.path || !existsSync14(p.path)) continue;
332499
+ if (!p || !p.path || !existsSync13(p.path)) continue;
332555
332500
  const contextFiles = detectAgentContextFiles(p.path);
332556
332501
  if (contextFiles.length === 0) continue;
332557
332502
  const statuses = checkAgentRulesStatus(p.path, contextFiles);
@@ -332620,8 +332565,8 @@ var pkgVersionFound = false;
332620
332565
  try {
332621
332566
  let currentDir = dirname6(fileURLToPath3(import.meta.url));
332622
332567
  while (currentDir !== dirname6(currentDir)) {
332623
- const p = join17(currentDir, "package.json");
332624
- if (existsSync14(p)) {
332568
+ const p = join16(currentDir, "package.json");
332569
+ if (existsSync13(p)) {
332625
332570
  const pkg = JSON.parse(readFileSync12(p, "utf8"));
332626
332571
  if (pkg.name === "kontexta-mcp" && pkg.version) {
332627
332572
  pkgVersion = pkg.version;
@@ -332642,7 +332587,7 @@ var server = new McpServer({
332642
332587
  version: pkgVersion
332643
332588
  });
332644
332589
  var handsRegistry = new HandsRegistry(server);
332645
- var baseJournalDir = join17(dataDir, "knowledge", "journal");
332590
+ var baseJournalDir = join16(dataDir, "knowledge", "journal");
332646
332591
  var projectSlug = process.env.KONTEXTA_DEFAULT_PROJECT_SLUG ?? "default";
332647
332592
  var agent = process.env.KONTEXTA_AGENT ?? "unknown";
332648
332593
  var sid = `${process.pid}-${Date.now().toString(36)}`;
@@ -332700,8 +332645,8 @@ server.tool(
332700
332645
  const journalFolder = "journal";
332701
332646
  const title = `journal-${today}`;
332702
332647
  const db2 = getDatabase();
332703
- const knowledgeDir = join17(dataDir, "knowledge");
332704
- const expectedPath = join17(knowledgeDir, journalFolder, `${title}.md`);
332648
+ const knowledgeDir = join16(dataDir, "knowledge");
332649
+ const expectedPath = join16(knowledgeDir, journalFolder, `${title}.md`);
332705
332650
  const existingRow = db2.prepare("SELECT id, path FROM files WHERE path = ? AND project_id IS NULL").get(expectedPath);
332706
332651
  if (existingRow) {
332707
332652
  const existing = readFile(existingRow.id);
@@ -332831,7 +332776,7 @@ server.tool(
332831
332776
  folder = parts.length > 1 ? parts.slice(0, -1).join("/") : null;
332832
332777
  }
332833
332778
  } else {
332834
- const knowledgeRoot = join17(dataDir, "knowledge");
332779
+ const knowledgeRoot = join16(dataDir, "knowledge");
332835
332780
  if (file.path?.startsWith(knowledgeRoot)) {
332836
332781
  const rel = file.path.slice(knowledgeRoot.length).replace(/^[\/\\]+/, "");
332837
332782
  const parts = rel.split(/[\/\\]/);
@@ -333280,7 +333225,7 @@ server.tool(
333280
333225
  async () => {
333281
333226
  const result = listProjects();
333282
333227
  const augmented = result.map((p) => {
333283
- 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`));
333284
333229
  const contextFiles = p.path ? detectAgentContextFiles(p.path) : [];
333285
333230
  const statuses = p.path ? checkAgentRulesStatus(p.path, contextFiles) : [];
333286
333231
  const outdated = statuses.filter((s3) => !s3.upToDate);
@@ -333488,6 +333433,130 @@ RETURNS: { written: [{ path, action: created|updated|skipped, version }], skippe
333488
333433
  }
333489
333434
  }
333490
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
+ );
333491
333560
  server.tool(
333492
333561
  "commit_backup",
333493
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.",
@@ -333761,7 +333830,7 @@ server.tool(
333761
333830
  );
333762
333831
  function resolveFolderBase(projectId) {
333763
333832
  if (projectId === void 0 || projectId === null) {
333764
- return join17(dataDir, "knowledge");
333833
+ return join16(dataDir, "knowledge");
333765
333834
  }
333766
333835
  const project = getDatabase().prepare("SELECT path FROM projects WHERE id = ?").get(projectId);
333767
333836
  if (!project?.path) {
@@ -333886,7 +333955,7 @@ server.tool(
333886
333955
  if (!project?.path) throw new Error(`Project not found for file ${file_id}`);
333887
333956
  base3 = project.path;
333888
333957
  } else {
333889
- base3 = join17(dataDir, "knowledge");
333958
+ base3 = join16(dataDir, "knowledge");
333890
333959
  }
333891
333960
  let baseResolved;
333892
333961
  try {
@@ -333902,8 +333971,8 @@ server.tool(
333902
333971
  } catch {
333903
333972
  throw new Error(`Destination parent directory does not exist: ${destParent}`);
333904
333973
  }
333905
- const destResolved = join17(destParentReal, destAbs.slice(destParent.length + (destParent.endsWith(sep8) ? 0 : 1)));
333906
- 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)) {
333907
333976
  throw new Error(`new_path must be inside ${base3}`);
333908
333977
  }
333909
333978
  let srcResolved;
@@ -333912,7 +333981,7 @@ server.tool(
333912
333981
  } catch {
333913
333982
  srcResolved = resolve7(file.path);
333914
333983
  }
333915
- if (srcResolved !== baseResolved && !srcResolved.startsWith(baseResolved + sep8)) {
333984
+ if (srcResolved !== baseResolved && !srcResolved.startsWith(baseResolved + sep9)) {
333916
333985
  throw new Error(`source path ${file.path} is no longer inside ${base3}; refusing to move`);
333917
333986
  }
333918
333987
  const updated = moveFile(file_id, new_path);
@@ -334607,7 +334676,7 @@ server.tool(
334607
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`.",
334608
334677
  {},
334609
334678
  async () => {
334610
- 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 }));
334611
334680
  const r5 = handsRegistry.reloadAll(projects);
334612
334681
  return { content: [{ type: "text", text: JSON.stringify(r5, null, 2) }] };
334613
334682
  }
@@ -334638,7 +334707,7 @@ server.tool(
334638
334707
  async function main() {
334639
334708
  const transport = new StdioServerTransport();
334640
334709
  {
334641
- 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 }));
334642
334711
  const r5 = handsRegistry.reloadAll(projects);
334643
334712
  console.error(
334644
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.8",
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
  }