kontexta-mcp 2.0.8 → 3.0.0
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.
- package/dist/agent-rules/rules-block.md +1 -0
- package/dist/index.js +367 -219
- package/package.json +4 -4
|
@@ -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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
if (!isTempOrTestPath(_resolvedDataDir)) {
|
|
78
|
+
try {
|
|
79
|
+
fs.writeFileSync(cacheFile, _resolvedDataDir, "utf8");
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
72
82
|
}
|
|
73
83
|
return _resolvedDataDir;
|
|
74
84
|
}
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return _resolvedDataDir;
|
|
95
|
+
}
|
|
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 +
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 +
|
|
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 +
|
|
974
|
+
if (resolvedLocal !== projectResolved && !resolvedLocal.startsWith(projectResolved + sep3)) {
|
|
958
975
|
continue;
|
|
959
976
|
}
|
|
960
977
|
try {
|
|
961
978
|
if (existsSync3(localAbsolutePath)) {
|
|
962
|
-
|
|
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
|
|
1005
|
-
import { join as join4, dirname as dirname3, resolve as resolve4, sep as
|
|
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) {
|
|
@@ -1031,6 +1048,47 @@ function listProjectFolders(projectPath) {
|
|
|
1031
1048
|
}
|
|
1032
1049
|
return folders;
|
|
1033
1050
|
}
|
|
1051
|
+
function listProjectFoldersWithFiles(projectPath) {
|
|
1052
|
+
const folderFileCount = /* @__PURE__ */ new Map();
|
|
1053
|
+
function scan(dir, currentRel) {
|
|
1054
|
+
const entries = readdirSync3(dir);
|
|
1055
|
+
for (const entry of entries) {
|
|
1056
|
+
if (entry.startsWith(".") || entry === "node_modules")
|
|
1057
|
+
continue;
|
|
1058
|
+
const fullPath = join4(dir, entry);
|
|
1059
|
+
const relPath = currentRel ? join4(currentRel, entry) : entry;
|
|
1060
|
+
try {
|
|
1061
|
+
const lst = lstatSync2(fullPath);
|
|
1062
|
+
if (lst.isSymbolicLink())
|
|
1063
|
+
continue;
|
|
1064
|
+
if (lst.isDirectory()) {
|
|
1065
|
+
scan(fullPath, relPath);
|
|
1066
|
+
} else if (lst.isFile()) {
|
|
1067
|
+
const ext = entry.endsWith(".mmd") ? ".mmd" : entry.endsWith(".md") ? ".md" : "";
|
|
1068
|
+
if (ext) {
|
|
1069
|
+
const parts = relPath.split("/");
|
|
1070
|
+
for (let i4 = 0; i4 < parts.length - 1; i4++) {
|
|
1071
|
+
const ancestor = parts.slice(0, i4 + 1).join("/");
|
|
1072
|
+
folderFileCount.set(ancestor, (folderFileCount.get(ancestor) ?? 0) + 1);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
} catch (e3) {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
scan(projectPath, "");
|
|
1082
|
+
} catch (e3) {
|
|
1083
|
+
console.error("Failed to scan project folders with files:", e3);
|
|
1084
|
+
}
|
|
1085
|
+
const result = [];
|
|
1086
|
+
for (const [folder, count] of folderFileCount.entries()) {
|
|
1087
|
+
if (count > 0)
|
|
1088
|
+
result.push(folder);
|
|
1089
|
+
}
|
|
1090
|
+
return result.sort();
|
|
1091
|
+
}
|
|
1034
1092
|
function computeHash(content) {
|
|
1035
1093
|
return createHash2("sha256").update(content, "utf8").digest("hex");
|
|
1036
1094
|
}
|
|
@@ -1137,7 +1195,7 @@ async function createFile(opts) {
|
|
|
1137
1195
|
}
|
|
1138
1196
|
} else {
|
|
1139
1197
|
try {
|
|
1140
|
-
|
|
1198
|
+
unlinkSync3(filePath);
|
|
1141
1199
|
} catch {
|
|
1142
1200
|
}
|
|
1143
1201
|
}
|
|
@@ -1255,11 +1313,11 @@ function deleteFile(id, dataDir2) {
|
|
|
1255
1313
|
if (file && dataDir2) {
|
|
1256
1314
|
const knowledgeRoot = resolve4(dataDir2, "knowledge");
|
|
1257
1315
|
const filePathResolved = isAbsolute4(file.path) ? file.path : resolve4(dataDir2, file.path);
|
|
1258
|
-
const inKnowledge = filePathResolved === knowledgeRoot || filePathResolved.startsWith(knowledgeRoot +
|
|
1316
|
+
const inKnowledge = filePathResolved === knowledgeRoot || filePathResolved.startsWith(knowledgeRoot + sep4);
|
|
1259
1317
|
if (file.project_id === null && inKnowledge) {
|
|
1260
1318
|
try {
|
|
1261
1319
|
if (existsSync4(filePathResolved)) {
|
|
1262
|
-
|
|
1320
|
+
unlinkSync3(filePathResolved);
|
|
1263
1321
|
}
|
|
1264
1322
|
} catch (e3) {
|
|
1265
1323
|
console.error("Failed to delete Knowledge Base file from disk:", e3);
|
|
@@ -1316,9 +1374,15 @@ function listFiles(opts) {
|
|
|
1316
1374
|
}
|
|
1317
1375
|
if (filters.folder !== void 0) {
|
|
1318
1376
|
const segment = escapeLike(filters.folder);
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1377
|
+
if (filters.project_path) {
|
|
1378
|
+
sql += " AND (path LIKE ? ESCAPE '\\' OR path LIKE ? ESCAPE '\\')";
|
|
1379
|
+
params.push(`${filters.project_path}/${segment}/%`);
|
|
1380
|
+
params.push(`${filters.project_path}\\${segment}\\%`);
|
|
1381
|
+
} else {
|
|
1382
|
+
sql += " AND (path LIKE ? ESCAPE '\\' OR path LIKE ? ESCAPE '\\')";
|
|
1383
|
+
params.push(`%/${segment}/%`);
|
|
1384
|
+
params.push(`%\\${segment}\\%`);
|
|
1385
|
+
}
|
|
1322
1386
|
}
|
|
1323
1387
|
}
|
|
1324
1388
|
sql += " ORDER BY updated_at DESC";
|
|
@@ -1565,27 +1629,33 @@ function registerProject(name50, path3, description, remoteUrl) {
|
|
|
1565
1629
|
`);
|
|
1566
1630
|
const absolutePath = resolve5(path3);
|
|
1567
1631
|
const result = insertStmt.run(name50, slug, absolutePath, description || null, remoteUrl || null);
|
|
1632
|
+
let projectId;
|
|
1568
1633
|
if (result.changes > 0) {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1634
|
+
projectId = Number(result.lastInsertRowid);
|
|
1635
|
+
} else {
|
|
1636
|
+
const byName = db2.prepare("SELECT * FROM projects WHERE name = ?").get(name50);
|
|
1637
|
+
const bySlug = db2.prepare("SELECT * FROM projects WHERE slug = ?").get(slug);
|
|
1638
|
+
const existing = byName ?? bySlug;
|
|
1639
|
+
if (!existing) {
|
|
1640
|
+
throw new Error(`registerProject: insert ignored but no matching project found for name='${name50}'`);
|
|
1641
|
+
}
|
|
1642
|
+
if (existing.path !== absolutePath) {
|
|
1643
|
+
const conflicts = [];
|
|
1644
|
+
if (byName)
|
|
1645
|
+
conflicts.push(`name conflicts with '${byName.name}' at '${byName.path}'`);
|
|
1646
|
+
if (bySlug && bySlug.id !== byName?.id)
|
|
1647
|
+
conflicts.push(`slug '${slug}' conflicts with '${bySlug.name}' at '${bySlug.path}'`);
|
|
1648
|
+
const err = new Error(`Cannot register '${name50}' at '${path3}': ${conflicts.join("; ")}. Pick a different name or unregister the existing project first.`);
|
|
1649
|
+
err.code = "PROJECT_CONFLICT";
|
|
1650
|
+
throw err;
|
|
1651
|
+
}
|
|
1652
|
+
projectId = existing.id;
|
|
1653
|
+
}
|
|
1654
|
+
const newlyIndexed = reconcileIndex({ projectId, dataDir: getDataDir() }).newRecords.length;
|
|
1655
|
+
return {
|
|
1656
|
+
...db2.prepare("SELECT * FROM projects WHERE id = ?").get(projectId),
|
|
1657
|
+
newlyIndexed
|
|
1658
|
+
};
|
|
1589
1659
|
}
|
|
1590
1660
|
function unregisterProject(projectId, dataDir2) {
|
|
1591
1661
|
const db2 = getDatabase();
|
|
@@ -1661,9 +1731,14 @@ function reconcileIndex(opts) {
|
|
|
1661
1731
|
} catch (e3) {
|
|
1662
1732
|
if (isTopLevel) {
|
|
1663
1733
|
if (typeof projectId === "number") {
|
|
1664
|
-
|
|
1734
|
+
if (e3?.code === "ENOENT") {
|
|
1735
|
+
console.warn(`reconcileIndex: project root does not exist yet: ${dir}`);
|
|
1736
|
+
} else {
|
|
1737
|
+
throw new Error(`Failed to read project root ${dir}: ${e3?.message ?? e3}`);
|
|
1738
|
+
}
|
|
1739
|
+
} else {
|
|
1740
|
+
console.warn(`reconcileIndex: failed to read KB root ${dir}: ${e3?.message ?? e3}`);
|
|
1665
1741
|
}
|
|
1666
|
-
console.warn(`reconcileIndex: failed to read KB root ${dir}: ${e3?.message ?? e3}`);
|
|
1667
1742
|
}
|
|
1668
1743
|
return;
|
|
1669
1744
|
}
|
|
@@ -1758,7 +1833,9 @@ function reconcileIndex(opts) {
|
|
|
1758
1833
|
return { scope, newly_indexed, refreshed, pruned, newRecords };
|
|
1759
1834
|
}
|
|
1760
1835
|
function discoverFiles(projectId, dataDir2) {
|
|
1761
|
-
|
|
1836
|
+
reconcileIndex({ projectId, dataDir: dataDir2 });
|
|
1837
|
+
const db2 = getDatabase();
|
|
1838
|
+
return db2.prepare("SELECT * FROM files WHERE project_id = ? ORDER BY path").all(projectId);
|
|
1762
1839
|
}
|
|
1763
1840
|
function getTagsForFiles(fileIds) {
|
|
1764
1841
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -1805,6 +1882,7 @@ var init_metadata = __esm({
|
|
|
1805
1882
|
init_safety();
|
|
1806
1883
|
init_files();
|
|
1807
1884
|
init_extensions();
|
|
1885
|
+
init_paths();
|
|
1808
1886
|
FtsQueryError = class extends Error {
|
|
1809
1887
|
code = "FTS_PARSE";
|
|
1810
1888
|
constructor(message) {
|
|
@@ -1862,8 +1940,8 @@ var init_settings = __esm({
|
|
|
1862
1940
|
});
|
|
1863
1941
|
|
|
1864
1942
|
// ../../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
|
|
1866
|
-
import { join as join6, sep as
|
|
1943
|
+
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";
|
|
1944
|
+
import { join as join6, sep as sep5, dirname as dirname4, isAbsolute as isAbsolute5 } from "path";
|
|
1867
1945
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1868
1946
|
function loadCorePackageVersion() {
|
|
1869
1947
|
try {
|
|
@@ -1926,7 +2004,7 @@ function isRegularNonSymlink(absPath) {
|
|
|
1926
2004
|
}
|
|
1927
2005
|
}
|
|
1928
2006
|
function toForwardSlashes(p) {
|
|
1929
|
-
return
|
|
2007
|
+
return sep5 === "/" ? p : p.split(sep5).join("/");
|
|
1930
2008
|
}
|
|
1931
2009
|
function detectAgentContextFiles(projectPath) {
|
|
1932
2010
|
const out = [];
|
|
@@ -1975,8 +2053,8 @@ function parseMarker(content) {
|
|
|
1975
2053
|
function injectOrUpdate(content, block, version2) {
|
|
1976
2054
|
const parsed = parseMarker(content);
|
|
1977
2055
|
if (parsed === null) {
|
|
1978
|
-
const
|
|
1979
|
-
return { action: "updated", content: content +
|
|
2056
|
+
const sep10 = content.length === 0 ? "" : content.endsWith("\n\n") ? "" : content.endsWith("\n") ? "\n" : "\n\n";
|
|
2057
|
+
return { action: "updated", content: content + sep10 + block };
|
|
1980
2058
|
}
|
|
1981
2059
|
if (parsed.kind === "malformed") {
|
|
1982
2060
|
throw new InjectError("malformed", "kontexta rules marker is malformed (BEGIN with no matching END)");
|
|
@@ -1999,7 +2077,7 @@ function atomicWrite(absPath, content) {
|
|
|
1999
2077
|
renameSync3(tmp, absPath);
|
|
2000
2078
|
} catch (e3) {
|
|
2001
2079
|
try {
|
|
2002
|
-
|
|
2080
|
+
unlinkSync4(tmp);
|
|
2003
2081
|
} catch {
|
|
2004
2082
|
}
|
|
2005
2083
|
throw e3;
|
|
@@ -130556,7 +130634,7 @@ var init_dist3 = __esm({
|
|
|
130556
130634
|
}
|
|
130557
130635
|
});
|
|
130558
130636
|
|
|
130559
|
-
// ../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@
|
|
130637
|
+
// ../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@_3b33f237655dfa7a35d4b0433ee15cf1/node_modules/@csstools/css-calc/dist/index.mjs
|
|
130560
130638
|
function toLowerCaseAZ(e3) {
|
|
130561
130639
|
return e3.replace(M, (e4) => String.fromCharCode(e4.charCodeAt(0) + 32));
|
|
130562
130640
|
}
|
|
@@ -131315,7 +131393,7 @@ function replaceComponentValues2(n3, r5) {
|
|
|
131315
131393
|
}
|
|
131316
131394
|
var ParseError2, ParseErrorWithComponentValues, y, M, T, x, P, k, W, O, U, L, $2, V, Z, z, q, G, R, j, Y, _, H, J, K, Q;
|
|
131317
131395
|
var init_dist4 = __esm({
|
|
131318
|
-
"../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@
|
|
131396
|
+
"../../node_modules/.pnpm/@csstools+css-calc@3.2.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-tokenizer@_3b33f237655dfa7a35d4b0433ee15cf1/node_modules/@csstools/css-calc/dist/index.mjs"() {
|
|
131319
131397
|
"use strict";
|
|
131320
131398
|
init_dist3();
|
|
131321
131399
|
init_dist2();
|
|
@@ -132439,7 +132517,7 @@ var init_dist5 = __esm({
|
|
|
132439
132517
|
}
|
|
132440
132518
|
});
|
|
132441
132519
|
|
|
132442
|
-
// ../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-
|
|
132520
|
+
// ../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-to_402b4b31fb8b001854d943f6a3e18a48/node_modules/@csstools/css-color-parser/dist/index.mjs
|
|
132443
132521
|
function convertNaNToZero(e3) {
|
|
132444
132522
|
return [Number.isNaN(e3[0]) ? 0 : e3[0], Number.isNaN(e3[1]) ? 0 : e3[1], Number.isNaN(e3[2]) ? 0 : e3[2]];
|
|
132445
132523
|
}
|
|
@@ -133504,7 +133582,7 @@ function color(e3) {
|
|
|
133504
133582
|
}
|
|
133505
133583
|
var he, me, pe, Ne, be, ge, ve, fe, ye;
|
|
133506
133584
|
var init_dist6 = __esm({
|
|
133507
|
-
"../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-
|
|
133585
|
+
"../../node_modules/.pnpm/@csstools+css-color-parser@4.1.1_@csstools+css-parser-algorithms@4.0.0_@csstools+css-to_402b4b31fb8b001854d943f6a3e18a48/node_modules/@csstools/css-color-parser/dist/index.mjs"() {
|
|
133508
133586
|
"use strict";
|
|
133509
133587
|
init_dist2();
|
|
133510
133588
|
init_dist5();
|
|
@@ -137503,7 +137581,7 @@ var require_util7 = __commonJS({
|
|
|
137503
137581
|
return path3;
|
|
137504
137582
|
});
|
|
137505
137583
|
exports.normalize = normalize3;
|
|
137506
|
-
function
|
|
137584
|
+
function join17(aRoot, aPath) {
|
|
137507
137585
|
if (aRoot === "") {
|
|
137508
137586
|
aRoot = ".";
|
|
137509
137587
|
}
|
|
@@ -137535,7 +137613,7 @@ var require_util7 = __commonJS({
|
|
|
137535
137613
|
}
|
|
137536
137614
|
return joined;
|
|
137537
137615
|
}
|
|
137538
|
-
exports.join =
|
|
137616
|
+
exports.join = join17;
|
|
137539
137617
|
exports.isAbsolute = function(aPath) {
|
|
137540
137618
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
137541
137619
|
};
|
|
@@ -137749,7 +137827,7 @@ var require_util7 = __commonJS({
|
|
|
137749
137827
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
137750
137828
|
}
|
|
137751
137829
|
}
|
|
137752
|
-
sourceURL =
|
|
137830
|
+
sourceURL = join17(urlGenerate(parsed), sourceURL);
|
|
137753
137831
|
}
|
|
137754
137832
|
return normalize3(sourceURL);
|
|
137755
137833
|
}
|
|
@@ -329707,17 +329785,17 @@ var init_whats_new = __esm({
|
|
|
329707
329785
|
});
|
|
329708
329786
|
|
|
329709
329787
|
// ../../packages/core/dist/project-map/index.js
|
|
329710
|
-
import { sep as
|
|
329788
|
+
import { sep as sep6, join as join8, basename as basename2 } from "path";
|
|
329711
329789
|
function emptyNode() {
|
|
329712
329790
|
return { dirs: /* @__PURE__ */ new Map(), files: [] };
|
|
329713
329791
|
}
|
|
329714
329792
|
function stripRoot(filePath, root2) {
|
|
329715
|
-
const rootWithSep = root2.endsWith(
|
|
329793
|
+
const rootWithSep = root2.endsWith(sep6) ? root2 : root2 + sep6;
|
|
329716
329794
|
if (filePath === root2)
|
|
329717
329795
|
return [];
|
|
329718
329796
|
if (!filePath.startsWith(rootWithSep))
|
|
329719
329797
|
return null;
|
|
329720
|
-
return filePath.slice(rootWithSep.length).split(
|
|
329798
|
+
return filePath.slice(rootWithSep.length).split(sep6).filter(Boolean);
|
|
329721
329799
|
}
|
|
329722
329800
|
function locateFile(file, projectsById, dataDir2) {
|
|
329723
329801
|
if (file.project_id == null) {
|
|
@@ -329860,73 +329938,6 @@ var init_project_map = __esm({
|
|
|
329860
329938
|
}
|
|
329861
329939
|
});
|
|
329862
329940
|
|
|
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
329941
|
// ../../packages/core/dist/journal/types.js
|
|
329931
329942
|
var init_types2 = __esm({
|
|
329932
329943
|
"../../packages/core/dist/journal/types.js"() {
|
|
@@ -329936,7 +329947,7 @@ var init_types2 = __esm({
|
|
|
329936
329947
|
|
|
329937
329948
|
// ../../packages/core/dist/journal/writer.js
|
|
329938
329949
|
import { mkdirSync as mkdirSync6, openSync, closeSync, writeSync, fsyncSync } from "fs";
|
|
329939
|
-
import { join as
|
|
329950
|
+
import { join as join9, dirname as dirname5 } from "path";
|
|
329940
329951
|
var JournalWriter;
|
|
329941
329952
|
var init_writer = __esm({
|
|
329942
329953
|
"../../packages/core/dist/journal/writer.js"() {
|
|
@@ -329946,7 +329957,7 @@ var init_writer = __esm({
|
|
|
329946
329957
|
currentDay = null;
|
|
329947
329958
|
rawDir;
|
|
329948
329959
|
constructor(opts) {
|
|
329949
|
-
this.rawDir =
|
|
329960
|
+
this.rawDir = join9(opts.baseDir, opts.projectSlug, "raw");
|
|
329950
329961
|
}
|
|
329951
329962
|
append(event) {
|
|
329952
329963
|
const day = event.ts.slice(0, 10);
|
|
@@ -329968,7 +329979,7 @@ var init_writer = __esm({
|
|
|
329968
329979
|
closeSync(this.fd);
|
|
329969
329980
|
this.fd = null;
|
|
329970
329981
|
}
|
|
329971
|
-
const path3 =
|
|
329982
|
+
const path3 = join9(this.rawDir, `${day}.jsonl`);
|
|
329972
329983
|
mkdirSync6(dirname5(path3), { recursive: true });
|
|
329973
329984
|
this.fd = openSync(path3, "a");
|
|
329974
329985
|
this.currentDay = day;
|
|
@@ -330023,14 +330034,14 @@ var init_redact = __esm({
|
|
|
330023
330034
|
});
|
|
330024
330035
|
|
|
330025
330036
|
// ../../packages/core/dist/journal/high-water.js
|
|
330026
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as
|
|
330027
|
-
import { join as
|
|
330037
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
|
|
330038
|
+
import { join as join10 } from "path";
|
|
330028
330039
|
function pathFor(baseDir, projectSlug2) {
|
|
330029
|
-
return
|
|
330040
|
+
return join10(baseDir, projectSlug2, ".distilled-up-to.json");
|
|
330030
330041
|
}
|
|
330031
330042
|
function readHighWater(baseDir, projectSlug2) {
|
|
330032
330043
|
const p = pathFor(baseDir, projectSlug2);
|
|
330033
|
-
if (!
|
|
330044
|
+
if (!existsSync7(p))
|
|
330034
330045
|
return null;
|
|
330035
330046
|
try {
|
|
330036
330047
|
const raw = readFileSync7(p, "utf8");
|
|
@@ -330044,12 +330055,12 @@ function readHighWater(baseDir, projectSlug2) {
|
|
|
330044
330055
|
}
|
|
330045
330056
|
}
|
|
330046
330057
|
function writeHighWater(baseDir, projectSlug2, hw) {
|
|
330047
|
-
const dir =
|
|
330058
|
+
const dir = join10(baseDir, projectSlug2);
|
|
330048
330059
|
mkdirSync7(dir, { recursive: true });
|
|
330049
330060
|
const final = pathFor(baseDir, projectSlug2);
|
|
330050
330061
|
const tmp = `${final}.tmp`;
|
|
330051
330062
|
writeFileSync5(tmp, JSON.stringify(hw, null, 2));
|
|
330052
|
-
|
|
330063
|
+
renameSync4(tmp, final);
|
|
330053
330064
|
}
|
|
330054
330065
|
var init_high_water = __esm({
|
|
330055
330066
|
"../../packages/core/dist/journal/high-water.js"() {
|
|
@@ -330669,12 +330680,12 @@ var init_repository = __esm({
|
|
|
330669
330680
|
});
|
|
330670
330681
|
|
|
330671
330682
|
// ../../packages/core/dist/journal/cooldown.js
|
|
330672
|
-
import { existsSync as
|
|
330673
|
-
import { join as
|
|
330683
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, mkdirSync as mkdirSync8 } from "fs";
|
|
330684
|
+
import { join as join11 } from "path";
|
|
330674
330685
|
function acquireCooldown(baseDir, projectSlug2, cooldownSeconds) {
|
|
330675
|
-
mkdirSync8(
|
|
330676
|
-
const lockPath =
|
|
330677
|
-
if (
|
|
330686
|
+
mkdirSync8(join11(baseDir, projectSlug2), { recursive: true });
|
|
330687
|
+
const lockPath = join11(baseDir, projectSlug2, ".distill.lock");
|
|
330688
|
+
if (existsSync8(lockPath)) {
|
|
330678
330689
|
try {
|
|
330679
330690
|
const ts = Number(readFileSync8(lockPath, "utf8"));
|
|
330680
330691
|
if (!isNaN(ts) && Date.now() - ts < cooldownSeconds * 1e3)
|
|
@@ -330686,10 +330697,10 @@ function acquireCooldown(baseDir, projectSlug2, cooldownSeconds) {
|
|
|
330686
330697
|
return true;
|
|
330687
330698
|
}
|
|
330688
330699
|
function releaseCooldown(baseDir, projectSlug2) {
|
|
330689
|
-
const lockPath =
|
|
330690
|
-
if (
|
|
330700
|
+
const lockPath = join11(baseDir, projectSlug2, ".distill.lock");
|
|
330701
|
+
if (existsSync8(lockPath)) {
|
|
330691
330702
|
try {
|
|
330692
|
-
|
|
330703
|
+
unlinkSync5(lockPath);
|
|
330693
330704
|
} catch {
|
|
330694
330705
|
}
|
|
330695
330706
|
}
|
|
@@ -330701,16 +330712,16 @@ var init_cooldown = __esm({
|
|
|
330701
330712
|
});
|
|
330702
330713
|
|
|
330703
330714
|
// ../../packages/core/dist/journal/distill.js
|
|
330704
|
-
import { readFileSync as readFileSync9, readdirSync as readdirSync6, existsSync as
|
|
330705
|
-
import { join as
|
|
330715
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync6, existsSync as existsSync9, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
330716
|
+
import { join as join12 } from "path";
|
|
330706
330717
|
function rawDir(opts) {
|
|
330707
|
-
return
|
|
330718
|
+
return join12(opts.dataDir, ...REL_BASE, opts.projectSlug, "raw");
|
|
330708
330719
|
}
|
|
330709
330720
|
function distilledDir(opts, ts) {
|
|
330710
|
-
return
|
|
330721
|
+
return join12(opts.dataDir, ...REL_BASE, opts.projectSlug, ts.slice(0, 4), ts.slice(5, 7), ts.slice(8, 10));
|
|
330711
330722
|
}
|
|
330712
330723
|
async function distillJournal(opts) {
|
|
330713
|
-
const cooldownBase =
|
|
330724
|
+
const cooldownBase = join12(opts.dataDir, ...REL_BASE);
|
|
330714
330725
|
const cooldownSec = opts.cooldownSeconds ?? 0;
|
|
330715
330726
|
if (!acquireCooldown(cooldownBase, opts.projectSlug, cooldownSec)) {
|
|
330716
330727
|
return {
|
|
@@ -330722,7 +330733,7 @@ async function distillJournal(opts) {
|
|
|
330722
330733
|
};
|
|
330723
330734
|
}
|
|
330724
330735
|
try {
|
|
330725
|
-
const hw = readHighWater(
|
|
330736
|
+
const hw = readHighWater(join12(opts.dataDir, ...REL_BASE), opts.projectSlug);
|
|
330726
330737
|
const since = hw?.last_event_ts ?? "0000-01-01T00:00:00Z";
|
|
330727
330738
|
const cutoff = new Date(opts.now.getTime() - opts.inFlightWindowSeconds * 1e3).toISOString();
|
|
330728
330739
|
const events = readRawEvents(opts, since, cutoff, opts.maxEvents);
|
|
@@ -330738,7 +330749,7 @@ async function distillJournal(opts) {
|
|
|
330738
330749
|
const dir = distilledDir(opts, lastEvent.ts);
|
|
330739
330750
|
mkdirSync9(dir, { recursive: true });
|
|
330740
330751
|
const filename = `task-${bucket.task_slug}.md`;
|
|
330741
|
-
const filePath =
|
|
330752
|
+
const filePath = join12(dir, filename);
|
|
330742
330753
|
const fm = buildFrontmatter2(bucket, opts.projectSlug);
|
|
330743
330754
|
const entry = renderMechanicalEntry({
|
|
330744
330755
|
task_slug: bucket.task_slug,
|
|
@@ -330746,7 +330757,7 @@ async function distillJournal(opts) {
|
|
|
330746
330757
|
now: lastEvent.ts,
|
|
330747
330758
|
extraPatterns: opts.extraPatterns
|
|
330748
330759
|
});
|
|
330749
|
-
if (
|
|
330760
|
+
if (existsSync9(filePath)) {
|
|
330750
330761
|
const existing = readFileSync9(filePath, "utf8");
|
|
330751
330762
|
writeFileSync7(filePath, replaceOrAppendEntry(existing, fm, entry));
|
|
330752
330763
|
} else {
|
|
@@ -330768,7 +330779,7 @@ async function distillJournal(opts) {
|
|
|
330768
330779
|
});
|
|
330769
330780
|
}
|
|
330770
330781
|
const newHw = events[events.length - 1].ts;
|
|
330771
|
-
writeHighWater(
|
|
330782
|
+
writeHighWater(join12(opts.dataDir, ...REL_BASE), opts.projectSlug, {
|
|
330772
330783
|
last_event_ts: newHw,
|
|
330773
330784
|
last_distilled_at: opts.now.toISOString(),
|
|
330774
330785
|
events_processed: (hw?.events_processed ?? 0) + events.length
|
|
@@ -330786,17 +330797,17 @@ async function distillJournal(opts) {
|
|
|
330786
330797
|
}
|
|
330787
330798
|
function readRawEvents(opts, sinceTs, untilTs, max2) {
|
|
330788
330799
|
const dirs = [rawDir(opts)];
|
|
330789
|
-
const defaultDir =
|
|
330790
|
-
if (defaultDir !== dirs[0] &&
|
|
330800
|
+
const defaultDir = join12(opts.dataDir, ...REL_BASE, "default", "raw");
|
|
330801
|
+
if (defaultDir !== dirs[0] && existsSync9(defaultDir)) {
|
|
330791
330802
|
dirs.push(defaultDir);
|
|
330792
330803
|
}
|
|
330793
330804
|
const out = [];
|
|
330794
330805
|
for (const dir of dirs) {
|
|
330795
|
-
if (!
|
|
330806
|
+
if (!existsSync9(dir))
|
|
330796
330807
|
continue;
|
|
330797
330808
|
const files = readdirSync6(dir).filter((f3) => f3.endsWith(".jsonl")).sort();
|
|
330798
330809
|
for (const f3 of files) {
|
|
330799
|
-
const lines = readFileSync9(
|
|
330810
|
+
const lines = readFileSync9(join12(dir, f3), "utf8").split("\n").filter(Boolean);
|
|
330800
330811
|
for (const line of lines) {
|
|
330801
330812
|
try {
|
|
330802
330813
|
const ev = JSON.parse(line);
|
|
@@ -330983,17 +330994,17 @@ var init_git_watcher = __esm({
|
|
|
330983
330994
|
});
|
|
330984
330995
|
|
|
330985
330996
|
// ../../packages/core/dist/journal/presence.js
|
|
330986
|
-
import { readdirSync as readdirSync7, statSync as statSync3, existsSync as
|
|
330987
|
-
import { join as
|
|
330997
|
+
import { readdirSync as readdirSync7, statSync as statSync3, existsSync as existsSync10 } from "fs";
|
|
330998
|
+
import { join as join13 } from "path";
|
|
330988
330999
|
function isMcpActive(baseDir, projectSlug2, windowSec) {
|
|
330989
|
-
const rawDir2 =
|
|
330990
|
-
if (!
|
|
331000
|
+
const rawDir2 = join13(baseDir, projectSlug2, "raw");
|
|
331001
|
+
if (!existsSync10(rawDir2))
|
|
330991
331002
|
return false;
|
|
330992
331003
|
const cutoff = Date.now() - windowSec * 1e3;
|
|
330993
331004
|
for (const f3 of readdirSync7(rawDir2)) {
|
|
330994
331005
|
if (!f3.endsWith(".jsonl"))
|
|
330995
331006
|
continue;
|
|
330996
|
-
if (statSync3(
|
|
331007
|
+
if (statSync3(join13(rawDir2, f3)).mtimeMs >= cutoff)
|
|
330997
331008
|
return true;
|
|
330998
331009
|
}
|
|
330999
331010
|
return false;
|
|
@@ -331005,8 +331016,8 @@ var init_presence = __esm({
|
|
|
331005
331016
|
});
|
|
331006
331017
|
|
|
331007
331018
|
// ../../packages/core/dist/journal/housekeep.js
|
|
331008
|
-
import { readdirSync as readdirSync8, statSync as statSync4, unlinkSync as
|
|
331009
|
-
import { join as
|
|
331019
|
+
import { readdirSync as readdirSync8, statSync as statSync4, unlinkSync as unlinkSync6, renameSync as renameSync5, mkdirSync as mkdirSync10, existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
331020
|
+
import { join as join14 } from "path";
|
|
331010
331021
|
function lastEventTs(path3) {
|
|
331011
331022
|
try {
|
|
331012
331023
|
const content = readFileSync10(path3, "utf8");
|
|
@@ -331030,12 +331041,12 @@ function housekeepJournal(cfg) {
|
|
|
331030
331041
|
};
|
|
331031
331042
|
if (cfg.retention.raw_days > 0) {
|
|
331032
331043
|
const cutoff = now.getTime() - cfg.retention.raw_days * 864e5;
|
|
331033
|
-
const rawDir2 =
|
|
331034
|
-
if (
|
|
331044
|
+
const rawDir2 = join14(cfg.baseDir, cfg.projectSlug, "raw");
|
|
331045
|
+
if (existsSync11(rawDir2)) {
|
|
331035
331046
|
const hw = readHighWater(cfg.baseDir, cfg.projectSlug);
|
|
331036
331047
|
const highWaterTs = hw?.last_event_ts ?? null;
|
|
331037
331048
|
for (const f3 of readdirSync8(rawDir2).filter((n3) => n3.endsWith(".jsonl"))) {
|
|
331038
|
-
const p =
|
|
331049
|
+
const p = join14(rawDir2, f3);
|
|
331039
331050
|
if (statSync4(p).mtimeMs >= cutoff)
|
|
331040
331051
|
continue;
|
|
331041
331052
|
const fileLastTs = lastEventTs(p);
|
|
@@ -331043,7 +331054,7 @@ function housekeepJournal(cfg) {
|
|
|
331043
331054
|
result.raw_files_skipped_undistilled++;
|
|
331044
331055
|
continue;
|
|
331045
331056
|
}
|
|
331046
|
-
|
|
331057
|
+
unlinkSync6(p);
|
|
331047
331058
|
result.raw_files_pruned++;
|
|
331048
331059
|
}
|
|
331049
331060
|
}
|
|
@@ -331057,12 +331068,12 @@ function housekeepJournal(cfg) {
|
|
|
331057
331068
|
JOIN projects p ON p.id = jm.project_id
|
|
331058
331069
|
WHERE p.slug = ? AND jm.last_active_at < ?
|
|
331059
331070
|
`).all(cfg.projectSlug, archiveCutoff);
|
|
331060
|
-
const archiveDir =
|
|
331071
|
+
const archiveDir = join14(cfg.baseDir, cfg.projectSlug, "_archive");
|
|
331061
331072
|
mkdirSync10(archiveDir, { recursive: true });
|
|
331062
331073
|
for (const row of cold) {
|
|
331063
|
-
const dest =
|
|
331064
|
-
if (!
|
|
331065
|
-
|
|
331074
|
+
const dest = join14(archiveDir, row.path.split("/").pop() ?? `task-${row.file_id}.md`);
|
|
331075
|
+
if (!existsSync11(dest) && existsSync11(row.path)) {
|
|
331076
|
+
renameSync5(row.path, dest);
|
|
331066
331077
|
db2.prepare(`UPDATE files SET path = ? WHERE id = ?`).run(dest, row.file_id);
|
|
331067
331078
|
result.archived_tasks++;
|
|
331068
331079
|
}
|
|
@@ -331215,13 +331226,11 @@ __export(dist_exports2, {
|
|
|
331215
331226
|
journalRefsByValue: () => journalRefsByValue,
|
|
331216
331227
|
listFiles: () => listFiles,
|
|
331217
331228
|
listProjectFolders: () => listProjectFolders,
|
|
331229
|
+
listProjectFoldersWithFiles: () => listProjectFoldersWithFiles,
|
|
331218
331230
|
listProjects: () => listProjects,
|
|
331219
331231
|
listTags: () => listTags,
|
|
331220
331232
|
loadExtraPatterns: () => loadExtraPatterns,
|
|
331221
331233
|
markUpgradeApplied: () => markUpgradeApplied,
|
|
331222
|
-
migrateDataFiles: () => migrateDataFiles,
|
|
331223
|
-
migrateEnvVars: () => migrateEnvVars,
|
|
331224
|
-
migrateProjectConfig: () => migrateProjectConfig,
|
|
331225
331234
|
moveFile: () => moveFile,
|
|
331226
331235
|
openTasksForProject: () => openTasksForProject,
|
|
331227
331236
|
parseMarker: () => parseMarker,
|
|
@@ -331236,6 +331245,7 @@ __export(dist_exports2, {
|
|
|
331236
331245
|
removeTags: () => removeTags,
|
|
331237
331246
|
renderMechanicalEntry: () => renderMechanicalEntry,
|
|
331238
331247
|
replaceSection: () => replaceSection,
|
|
331248
|
+
resetDataDirCache: () => resetDataDirCache,
|
|
331239
331249
|
resolveSince: () => resolveSince,
|
|
331240
331250
|
restoreVersion: () => restoreVersion,
|
|
331241
331251
|
runPatterns: () => runPatterns,
|
|
@@ -331278,14 +331288,28 @@ var init_dist7 = __esm({
|
|
|
331278
331288
|
init_clip();
|
|
331279
331289
|
init_whats_new();
|
|
331280
331290
|
init_project_map();
|
|
331281
|
-
init_env_shim();
|
|
331282
|
-
init_file_migration();
|
|
331283
331291
|
init_journal();
|
|
331284
331292
|
}
|
|
331285
331293
|
});
|
|
331286
331294
|
|
|
331295
|
+
// src/re2-compat.ts
|
|
331296
|
+
import { createRequire as createRequire4 } from "module";
|
|
331297
|
+
var require5, RE2Class, re2_compat_default;
|
|
331298
|
+
var init_re2_compat = __esm({
|
|
331299
|
+
"src/re2-compat.ts"() {
|
|
331300
|
+
"use strict";
|
|
331301
|
+
require5 = createRequire4(import.meta.url);
|
|
331302
|
+
try {
|
|
331303
|
+
RE2Class = require5("re2");
|
|
331304
|
+
} catch (e3) {
|
|
331305
|
+
console.warn("kontexta-mcp: Failed to load native 're2' module, falling back to standard RegExp.");
|
|
331306
|
+
RE2Class = RegExp;
|
|
331307
|
+
}
|
|
331308
|
+
re2_compat_default = RE2Class;
|
|
331309
|
+
}
|
|
331310
|
+
});
|
|
331311
|
+
|
|
331287
331312
|
// src/hands/sanitizer.ts
|
|
331288
|
-
import RE2 from "re2";
|
|
331289
331313
|
function rejectNul(s3) {
|
|
331290
331314
|
if (s3.includes("\0")) {
|
|
331291
331315
|
throw new Error("NUL byte not permitted in param value");
|
|
@@ -331321,7 +331345,7 @@ function isLiteralArgv0(s3) {
|
|
|
331321
331345
|
function compilePattern(src) {
|
|
331322
331346
|
let r5;
|
|
331323
331347
|
try {
|
|
331324
|
-
r5 = new
|
|
331348
|
+
r5 = new re2_compat_default(src);
|
|
331325
331349
|
} catch (e3) {
|
|
331326
331350
|
throw new Error(`invalid pattern: ${e3?.message ?? e3}`);
|
|
331327
331351
|
}
|
|
@@ -331358,6 +331382,7 @@ var SAFE_INT_MIN, SAFE_INT_MAX, DEFAULT_STRING_PATTERN, MAX_STRING_LENGTH, CONTR
|
|
|
331358
331382
|
var init_sanitizer = __esm({
|
|
331359
331383
|
"src/hands/sanitizer.ts"() {
|
|
331360
331384
|
"use strict";
|
|
331385
|
+
init_re2_compat();
|
|
331361
331386
|
SAFE_INT_MIN = Number.MIN_SAFE_INTEGER;
|
|
331362
331387
|
SAFE_INT_MAX = Number.MAX_SAFE_INTEGER;
|
|
331363
331388
|
DEFAULT_STRING_PATTERN = "^[^-\\s][^\\n\\r]*$";
|
|
@@ -331376,7 +331401,7 @@ __export(executor_exports, {
|
|
|
331376
331401
|
resolveCwd: () => resolveCwd
|
|
331377
331402
|
});
|
|
331378
331403
|
import { spawn } from "child_process";
|
|
331379
|
-
import { resolve as resolvePath, sep as
|
|
331404
|
+
import { resolve as resolvePath, sep as sep8 } from "path";
|
|
331380
331405
|
import { realpathSync } from "fs";
|
|
331381
331406
|
function resolveArgv(command, params, defs, argSeparator) {
|
|
331382
331407
|
const resolvedValues = {};
|
|
@@ -331432,7 +331457,7 @@ function resolveCwd(projectRoot, workingDir) {
|
|
|
331432
331457
|
} catch {
|
|
331433
331458
|
throw new Error(`workingDir does not exist: ${workingDir}`);
|
|
331434
331459
|
}
|
|
331435
|
-
if (real !== rootReal && !real.startsWith(rootReal +
|
|
331460
|
+
if (real !== rootReal && !real.startsWith(rootReal + sep8)) {
|
|
331436
331461
|
throw new Error(`workingDir resolved outside project root`);
|
|
331437
331462
|
}
|
|
331438
331463
|
return real;
|
|
@@ -331596,12 +331621,13 @@ ${tailStr}`;
|
|
|
331596
331621
|
|
|
331597
331622
|
// src/index.ts
|
|
331598
331623
|
init_dist7();
|
|
331624
|
+
init_re2_compat();
|
|
331599
331625
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
331600
331626
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
331601
331627
|
import { z as z6 } from "zod";
|
|
331602
|
-
import
|
|
331603
|
-
import {
|
|
331604
|
-
import {
|
|
331628
|
+
import { isAbsolute as isAbsolute7, join as join16, resolve as resolve7, sep as sep9, dirname as dirname6 } from "path";
|
|
331629
|
+
import { statSync as statSync5, lstatSync as lstatSync5, openSync as openSync2, readSync, closeSync as closeSync2, readFileSync as readFileSync12, existsSync as existsSync13, realpathSync as realpathSync2 } from "fs";
|
|
331630
|
+
import { createHash as createHash3 } from "crypto";
|
|
331605
331631
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
331606
331632
|
|
|
331607
331633
|
// src/hands/registry.ts
|
|
@@ -331609,9 +331635,8 @@ import { z as z2 } from "zod";
|
|
|
331609
331635
|
|
|
331610
331636
|
// src/hands/loader.ts
|
|
331611
331637
|
init_sanitizer();
|
|
331612
|
-
|
|
331613
|
-
import {
|
|
331614
|
-
import { join as join16, isAbsolute as isAbsolute6, normalize as normalize2, sep as sep6 } from "path";
|
|
331638
|
+
import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
|
|
331639
|
+
import { join as join15, isAbsolute as isAbsolute6, normalize as normalize2, sep as sep7 } from "path";
|
|
331615
331640
|
var TOOL_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
331616
331641
|
var PARAM_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
331617
331642
|
var FORBIDDEN_ENV = /* @__PURE__ */ new Set([
|
|
@@ -331628,9 +331653,8 @@ var TIMEOUT_DEFAULT = 6e4;
|
|
|
331628
331653
|
var OUTPUT_MAX = 1e6;
|
|
331629
331654
|
var OUTPUT_DEFAULT = 1e5;
|
|
331630
331655
|
function loadProjectConfig(projectRoot) {
|
|
331631
|
-
|
|
331632
|
-
|
|
331633
|
-
if (!existsSync13(file)) {
|
|
331656
|
+
const file = join15(projectRoot, "kontexta.json");
|
|
331657
|
+
if (!existsSync12(file)) {
|
|
331634
331658
|
return { found: false, tools: {}, disabled: [], warnings: [], errors: [] };
|
|
331635
331659
|
}
|
|
331636
331660
|
let raw;
|
|
@@ -331680,7 +331704,7 @@ function validateTool(name50, def, projectRoot) {
|
|
|
331680
331704
|
}
|
|
331681
331705
|
if (!isLiteralArgv0(def.command[0])) return reject("argv[0] must be literal (no {{param}})");
|
|
331682
331706
|
const argv0 = def.command[0];
|
|
331683
|
-
if (!isAbsolute6(argv0) && (argv0.includes("/") || argv0.includes(
|
|
331707
|
+
if (!isAbsolute6(argv0) && (argv0.includes("/") || argv0.includes(sep7))) {
|
|
331684
331708
|
return reject("argv[0] must be absolute or a bare command name (no relative paths)");
|
|
331685
331709
|
}
|
|
331686
331710
|
const paramDefs = def.params ?? {};
|
|
@@ -331713,7 +331737,7 @@ function validateTool(name50, def, projectRoot) {
|
|
|
331713
331737
|
if (typeof def.workingDir !== "string") return reject("workingDir must be string");
|
|
331714
331738
|
if (isAbsolute6(def.workingDir)) return reject("workingDir must be relative");
|
|
331715
331739
|
const norm = normalize2(def.workingDir);
|
|
331716
|
-
if (norm.startsWith("..") || norm.split(
|
|
331740
|
+
if (norm.startsWith("..") || norm.split(sep7).includes("..")) return reject("workingDir must not contain ..");
|
|
331717
331741
|
}
|
|
331718
331742
|
if (def.env !== void 0) {
|
|
331719
331743
|
if (typeof def.env !== "object" || def.env === null) return reject("env must be object");
|
|
@@ -332551,7 +332575,7 @@ function getAgentRulesWarning(projectId) {
|
|
|
332551
332575
|
}
|
|
332552
332576
|
const outdatedProjects = [];
|
|
332553
332577
|
for (const p of projects) {
|
|
332554
|
-
if (!p || !p.path || !
|
|
332578
|
+
if (!p || !p.path || !existsSync13(p.path)) continue;
|
|
332555
332579
|
const contextFiles = detectAgentContextFiles(p.path);
|
|
332556
332580
|
if (contextFiles.length === 0) continue;
|
|
332557
332581
|
const statuses = checkAgentRulesStatus(p.path, contextFiles);
|
|
@@ -332620,8 +332644,8 @@ var pkgVersionFound = false;
|
|
|
332620
332644
|
try {
|
|
332621
332645
|
let currentDir = dirname6(fileURLToPath3(import.meta.url));
|
|
332622
332646
|
while (currentDir !== dirname6(currentDir)) {
|
|
332623
|
-
const p =
|
|
332624
|
-
if (
|
|
332647
|
+
const p = join16(currentDir, "package.json");
|
|
332648
|
+
if (existsSync13(p)) {
|
|
332625
332649
|
const pkg = JSON.parse(readFileSync12(p, "utf8"));
|
|
332626
332650
|
if (pkg.name === "kontexta-mcp" && pkg.version) {
|
|
332627
332651
|
pkgVersion = pkg.version;
|
|
@@ -332642,7 +332666,7 @@ var server = new McpServer({
|
|
|
332642
332666
|
version: pkgVersion
|
|
332643
332667
|
});
|
|
332644
332668
|
var handsRegistry = new HandsRegistry(server);
|
|
332645
|
-
var baseJournalDir =
|
|
332669
|
+
var baseJournalDir = join16(dataDir, "knowledge", "journal");
|
|
332646
332670
|
var projectSlug = process.env.KONTEXTA_DEFAULT_PROJECT_SLUG ?? "default";
|
|
332647
332671
|
var agent = process.env.KONTEXTA_AGENT ?? "unknown";
|
|
332648
332672
|
var sid = `${process.pid}-${Date.now().toString(36)}`;
|
|
@@ -332700,8 +332724,8 @@ server.tool(
|
|
|
332700
332724
|
const journalFolder = "journal";
|
|
332701
332725
|
const title = `journal-${today}`;
|
|
332702
332726
|
const db2 = getDatabase();
|
|
332703
|
-
const knowledgeDir =
|
|
332704
|
-
const expectedPath =
|
|
332727
|
+
const knowledgeDir = join16(dataDir, "knowledge");
|
|
332728
|
+
const expectedPath = join16(knowledgeDir, journalFolder, `${title}.md`);
|
|
332705
332729
|
const existingRow = db2.prepare("SELECT id, path FROM files WHERE path = ? AND project_id IS NULL").get(expectedPath);
|
|
332706
332730
|
if (existingRow) {
|
|
332707
332731
|
const existing = readFile(existingRow.id);
|
|
@@ -332831,7 +332855,7 @@ server.tool(
|
|
|
332831
332855
|
folder = parts.length > 1 ? parts.slice(0, -1).join("/") : null;
|
|
332832
332856
|
}
|
|
332833
332857
|
} else {
|
|
332834
|
-
const knowledgeRoot =
|
|
332858
|
+
const knowledgeRoot = join16(dataDir, "knowledge");
|
|
332835
332859
|
if (file.path?.startsWith(knowledgeRoot)) {
|
|
332836
332860
|
const rel = file.path.slice(knowledgeRoot.length).replace(/^[\/\\]+/, "");
|
|
332837
332861
|
const parts = rel.split(/[\/\\]/);
|
|
@@ -332984,7 +333008,7 @@ server.tool(
|
|
|
332984
333008
|
try {
|
|
332985
333009
|
let re;
|
|
332986
333010
|
try {
|
|
332987
|
-
re = new
|
|
333011
|
+
re = new re2_compat_default(pattern, case_insensitive ? "i" : "");
|
|
332988
333012
|
} catch (e3) {
|
|
332989
333013
|
throw new Error(`invalid regex: ${e3?.message ?? e3}`);
|
|
332990
333014
|
}
|
|
@@ -333039,7 +333063,7 @@ server.tool(
|
|
|
333039
333063
|
try {
|
|
333040
333064
|
let re;
|
|
333041
333065
|
try {
|
|
333042
|
-
re = new
|
|
333066
|
+
re = new re2_compat_default(pattern, case_insensitive ? "i" : "");
|
|
333043
333067
|
} catch (e3) {
|
|
333044
333068
|
throw new Error(`invalid regex: ${e3?.message ?? e3}`);
|
|
333045
333069
|
}
|
|
@@ -333280,7 +333304,7 @@ server.tool(
|
|
|
333280
333304
|
async () => {
|
|
333281
333305
|
const result = listProjects();
|
|
333282
333306
|
const augmented = result.map((p) => {
|
|
333283
|
-
const has_hands = !!(p.path &&
|
|
333307
|
+
const has_hands = !!(p.path && existsSync13(p.path) && existsSync13(`${p.path}/kontexta.json`));
|
|
333284
333308
|
const contextFiles = p.path ? detectAgentContextFiles(p.path) : [];
|
|
333285
333309
|
const statuses = p.path ? checkAgentRulesStatus(p.path, contextFiles) : [];
|
|
333286
333310
|
const outdated = statuses.filter((s3) => !s3.upToDate);
|
|
@@ -333488,6 +333512,130 @@ RETURNS: { written: [{ path, action: created|updated|skipped, version }], skippe
|
|
|
333488
333512
|
}
|
|
333489
333513
|
}
|
|
333490
333514
|
);
|
|
333515
|
+
server.tool(
|
|
333516
|
+
"transfer_agent_context",
|
|
333517
|
+
`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.
|
|
333518
|
+
|
|
333519
|
+
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\`.
|
|
333520
|
+
|
|
333521
|
+
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.
|
|
333522
|
+
|
|
333523
|
+
PARAMETERS:
|
|
333524
|
+
- project_id: number, required. Project ID returned from register_project.
|
|
333525
|
+
- confirm: boolean, required. Must be true.
|
|
333526
|
+
- 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).
|
|
333527
|
+
|
|
333528
|
+
RETURNS: { transferred: [{ source_path, kb_id, kb_path, est_tokens }], skipped: [{ source_path, reason }], next_action }
|
|
333529
|
+
Skip reasons: "missing" | "symlink" | "outside_project" | "already_transferred_same_content" | "read_error" | "write_error".
|
|
333530
|
+
|
|
333531
|
+
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.`,
|
|
333532
|
+
{
|
|
333533
|
+
project_id: z6.number().describe("Project ID returned from register_project"),
|
|
333534
|
+
confirm: z6.boolean().describe("MANDATORY: Set to true only after obtaining explicit user consent."),
|
|
333535
|
+
files: z6.array(z6.string()).optional().describe("Project-relative paths to transfer. Omit to transfer all detected context files.")
|
|
333536
|
+
},
|
|
333537
|
+
async ({ project_id, confirm, files }) => {
|
|
333538
|
+
try {
|
|
333539
|
+
if (confirm !== true) {
|
|
333540
|
+
return {
|
|
333541
|
+
isError: true,
|
|
333542
|
+
content: [{
|
|
333543
|
+
type: "text",
|
|
333544
|
+
text: JSON.stringify({
|
|
333545
|
+
error: "User consent required",
|
|
333546
|
+
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."
|
|
333547
|
+
}, null, 2)
|
|
333548
|
+
}]
|
|
333549
|
+
};
|
|
333550
|
+
}
|
|
333551
|
+
const db2 = getDatabase();
|
|
333552
|
+
const project = db2.prepare("SELECT id, name, slug, path FROM projects WHERE id = ?").get(project_id);
|
|
333553
|
+
if (!project || !project.path) {
|
|
333554
|
+
return {
|
|
333555
|
+
isError: true,
|
|
333556
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Project ${project_id} not found or has no path` }, null, 2) }]
|
|
333557
|
+
};
|
|
333558
|
+
}
|
|
333559
|
+
const projectPath = project.path;
|
|
333560
|
+
const targetRels = files && files.length > 0 ? files : detectAgentContextFiles(projectPath);
|
|
333561
|
+
const existingHashes = new Set(
|
|
333562
|
+
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)
|
|
333563
|
+
);
|
|
333564
|
+
const transferred = [];
|
|
333565
|
+
const skipped = [];
|
|
333566
|
+
for (const rel of targetRels) {
|
|
333567
|
+
let abs2;
|
|
333568
|
+
try {
|
|
333569
|
+
abs2 = assertPathInside(projectPath, rel);
|
|
333570
|
+
} catch {
|
|
333571
|
+
skipped.push({ source_path: rel, reason: "outside_project" });
|
|
333572
|
+
continue;
|
|
333573
|
+
}
|
|
333574
|
+
let stat;
|
|
333575
|
+
try {
|
|
333576
|
+
stat = lstatSync5(abs2);
|
|
333577
|
+
} catch {
|
|
333578
|
+
stat = null;
|
|
333579
|
+
}
|
|
333580
|
+
if (!stat) {
|
|
333581
|
+
skipped.push({ source_path: rel, reason: "missing" });
|
|
333582
|
+
continue;
|
|
333583
|
+
}
|
|
333584
|
+
if (stat.isSymbolicLink()) {
|
|
333585
|
+
skipped.push({ source_path: rel, reason: "symlink" });
|
|
333586
|
+
continue;
|
|
333587
|
+
}
|
|
333588
|
+
if (!stat.isFile()) {
|
|
333589
|
+
skipped.push({ source_path: rel, reason: "missing" });
|
|
333590
|
+
continue;
|
|
333591
|
+
}
|
|
333592
|
+
let content;
|
|
333593
|
+
try {
|
|
333594
|
+
content = readFileSync12(abs2, "utf8");
|
|
333595
|
+
} catch {
|
|
333596
|
+
skipped.push({ source_path: rel, reason: "read_error" });
|
|
333597
|
+
continue;
|
|
333598
|
+
}
|
|
333599
|
+
const hash = createHash3("sha256").update(content, "utf8").digest("hex");
|
|
333600
|
+
if (existingHashes.has(hash)) {
|
|
333601
|
+
skipped.push({ source_path: rel, reason: "already_transferred_same_content" });
|
|
333602
|
+
continue;
|
|
333603
|
+
}
|
|
333604
|
+
const titleSource = rel.replace(/\.[^./]+$/, "");
|
|
333605
|
+
const title = titleSource || rel;
|
|
333606
|
+
try {
|
|
333607
|
+
const created = await createFile({
|
|
333608
|
+
title,
|
|
333609
|
+
content,
|
|
333610
|
+
destination: "kontexta",
|
|
333611
|
+
projectId: project.id,
|
|
333612
|
+
folder: "agent-context",
|
|
333613
|
+
dataDir,
|
|
333614
|
+
sourcePath: abs2
|
|
333615
|
+
});
|
|
333616
|
+
existingHashes.add(hash);
|
|
333617
|
+
transferred.push({
|
|
333618
|
+
source_path: rel,
|
|
333619
|
+
kb_id: created.id,
|
|
333620
|
+
kb_path: created.path,
|
|
333621
|
+
est_tokens: estimateTokensFromBuffer(Buffer.from(content, "utf8"))
|
|
333622
|
+
});
|
|
333623
|
+
} catch (e3) {
|
|
333624
|
+
skipped.push({ source_path: rel, reason: `write_error: ${e3?.message ?? String(e3)}` });
|
|
333625
|
+
}
|
|
333626
|
+
}
|
|
333627
|
+
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.";
|
|
333628
|
+
return {
|
|
333629
|
+
content: [{ type: "text", text: JSON.stringify({ transferred, skipped, next_action }, null, 2) }]
|
|
333630
|
+
};
|
|
333631
|
+
} catch (e3) {
|
|
333632
|
+
return {
|
|
333633
|
+
isError: true,
|
|
333634
|
+
content: [{ type: "text", text: JSON.stringify({ error: e3?.message ?? String(e3) }, null, 2) }]
|
|
333635
|
+
};
|
|
333636
|
+
}
|
|
333637
|
+
}
|
|
333638
|
+
);
|
|
333491
333639
|
server.tool(
|
|
333492
333640
|
"commit_backup",
|
|
333493
333641
|
"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 +333909,7 @@ server.tool(
|
|
|
333761
333909
|
);
|
|
333762
333910
|
function resolveFolderBase(projectId) {
|
|
333763
333911
|
if (projectId === void 0 || projectId === null) {
|
|
333764
|
-
return
|
|
333912
|
+
return join16(dataDir, "knowledge");
|
|
333765
333913
|
}
|
|
333766
333914
|
const project = getDatabase().prepare("SELECT path FROM projects WHERE id = ?").get(projectId);
|
|
333767
333915
|
if (!project?.path) {
|
|
@@ -333886,7 +334034,7 @@ server.tool(
|
|
|
333886
334034
|
if (!project?.path) throw new Error(`Project not found for file ${file_id}`);
|
|
333887
334035
|
base3 = project.path;
|
|
333888
334036
|
} else {
|
|
333889
|
-
base3 =
|
|
334037
|
+
base3 = join16(dataDir, "knowledge");
|
|
333890
334038
|
}
|
|
333891
334039
|
let baseResolved;
|
|
333892
334040
|
try {
|
|
@@ -333902,8 +334050,8 @@ server.tool(
|
|
|
333902
334050
|
} catch {
|
|
333903
334051
|
throw new Error(`Destination parent directory does not exist: ${destParent}`);
|
|
333904
334052
|
}
|
|
333905
|
-
const destResolved =
|
|
333906
|
-
if (destResolved !== baseResolved && !destResolved.startsWith(baseResolved +
|
|
334053
|
+
const destResolved = join16(destParentReal, destAbs.slice(destParent.length + (destParent.endsWith(sep9) ? 0 : 1)));
|
|
334054
|
+
if (destResolved !== baseResolved && !destResolved.startsWith(baseResolved + sep9)) {
|
|
333907
334055
|
throw new Error(`new_path must be inside ${base3}`);
|
|
333908
334056
|
}
|
|
333909
334057
|
let srcResolved;
|
|
@@ -333912,7 +334060,7 @@ server.tool(
|
|
|
333912
334060
|
} catch {
|
|
333913
334061
|
srcResolved = resolve7(file.path);
|
|
333914
334062
|
}
|
|
333915
|
-
if (srcResolved !== baseResolved && !srcResolved.startsWith(baseResolved +
|
|
334063
|
+
if (srcResolved !== baseResolved && !srcResolved.startsWith(baseResolved + sep9)) {
|
|
333916
334064
|
throw new Error(`source path ${file.path} is no longer inside ${base3}; refusing to move`);
|
|
333917
334065
|
}
|
|
333918
334066
|
const updated = moveFile(file_id, new_path);
|
|
@@ -334607,7 +334755,7 @@ server.tool(
|
|
|
334607
334755
|
"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
334756
|
{},
|
|
334609
334757
|
async () => {
|
|
334610
|
-
const projects = listProjects().filter((p) => p.path &&
|
|
334758
|
+
const projects = listProjects().filter((p) => p.path && existsSync13(p.path)).map((p) => ({ name: p.name, root: p.path }));
|
|
334611
334759
|
const r5 = handsRegistry.reloadAll(projects);
|
|
334612
334760
|
return { content: [{ type: "text", text: JSON.stringify(r5, null, 2) }] };
|
|
334613
334761
|
}
|
|
@@ -334638,7 +334786,7 @@ server.tool(
|
|
|
334638
334786
|
async function main() {
|
|
334639
334787
|
const transport = new StdioServerTransport();
|
|
334640
334788
|
{
|
|
334641
|
-
const projects = listProjects().filter((p) => p.path &&
|
|
334789
|
+
const projects = listProjects().filter((p) => p.path && existsSync13(p.path)).map((p) => ({ name: p.name, root: p.path }));
|
|
334642
334790
|
const r5 = handsRegistry.reloadAll(projects);
|
|
334643
334791
|
console.error(
|
|
334644
334792
|
`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": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "The local Brain and Hands for AI coding agents (MCP Server)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"./hands/types": "./dist/hands/types.js"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
41
|
+
"node": ">=22"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"dist",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
64
|
-
"better-sqlite3": "^
|
|
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.
|
|
76
|
+
"rulesVersion": "2.1.0"
|
|
77
77
|
}
|