kontext-engine 0.1.3 → 0.1.4
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/README.md +123 -83
- package/dist/cli/index.js +594 -344
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +374 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
6
|
|
|
6
7
|
// src/cli/commands/init.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
8
|
+
import fs6 from "fs";
|
|
9
|
+
import path5 from "path";
|
|
9
10
|
|
|
10
11
|
// src/indexer/discovery.ts
|
|
11
12
|
import fs from "fs/promises";
|
|
@@ -1336,33 +1337,230 @@ function getMetaVersion(db) {
|
|
|
1336
1337
|
}
|
|
1337
1338
|
}
|
|
1338
1339
|
|
|
1339
|
-
// src/cli/commands/
|
|
1340
|
+
// src/cli/commands/config.ts
|
|
1341
|
+
import fs5 from "fs";
|
|
1342
|
+
import path4 from "path";
|
|
1340
1343
|
var CTX_DIR = ".ctx";
|
|
1341
|
-
var DB_FILENAME = "index.db";
|
|
1342
1344
|
var CONFIG_FILENAME = "config.json";
|
|
1345
|
+
var DEFAULT_CONFIG = {
|
|
1346
|
+
embedder: {
|
|
1347
|
+
provider: "local",
|
|
1348
|
+
model: "Xenova/all-MiniLM-L6-v2",
|
|
1349
|
+
dimensions: 384
|
|
1350
|
+
},
|
|
1351
|
+
search: {
|
|
1352
|
+
defaultLimit: 10,
|
|
1353
|
+
strategies: ["vector", "fts", "ast", "path"],
|
|
1354
|
+
weights: { vector: 1, fts: 0.8, ast: 0.9, path: 0.7, dependency: 0.6 }
|
|
1355
|
+
},
|
|
1356
|
+
watch: {
|
|
1357
|
+
debounceMs: 500,
|
|
1358
|
+
ignored: []
|
|
1359
|
+
},
|
|
1360
|
+
llm: {
|
|
1361
|
+
provider: null,
|
|
1362
|
+
model: null
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
var VALID_EMBEDDER_PROVIDERS = /* @__PURE__ */ new Set(["local", "voyage", "openai"]);
|
|
1366
|
+
var VALID_LLM_PROVIDERS = /* @__PURE__ */ new Set(["gemini", "openai", "anthropic"]);
|
|
1367
|
+
var VALIDATION_RULES = {
|
|
1368
|
+
"embedder.provider": {
|
|
1369
|
+
validate: (v) => typeof v === "string" && VALID_EMBEDDER_PROVIDERS.has(v),
|
|
1370
|
+
message: `Must be one of: ${[...VALID_EMBEDDER_PROVIDERS].join(", ")}`
|
|
1371
|
+
},
|
|
1372
|
+
"embedder.dimensions": {
|
|
1373
|
+
validate: (v) => typeof v === "number" && v > 0 && Number.isInteger(v),
|
|
1374
|
+
message: "Must be a positive integer"
|
|
1375
|
+
},
|
|
1376
|
+
"search.defaultLimit": {
|
|
1377
|
+
validate: (v) => typeof v === "number" && v > 0 && Number.isInteger(v),
|
|
1378
|
+
message: "Must be a positive integer"
|
|
1379
|
+
},
|
|
1380
|
+
"watch.debounceMs": {
|
|
1381
|
+
validate: (v) => typeof v === "number" && v >= 0 && Number.isInteger(v),
|
|
1382
|
+
message: "Must be a non-negative integer"
|
|
1383
|
+
},
|
|
1384
|
+
"llm.provider": {
|
|
1385
|
+
validate: (v) => v === null || typeof v === "string" && VALID_LLM_PROVIDERS.has(v),
|
|
1386
|
+
message: `Must be null or one of: ${[...VALID_LLM_PROVIDERS].join(", ")}`
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
function resolveCtxDir(projectPath) {
|
|
1390
|
+
const absoluteRoot = path4.resolve(projectPath);
|
|
1391
|
+
const ctxDir = path4.join(absoluteRoot, CTX_DIR);
|
|
1392
|
+
if (!fs5.existsSync(ctxDir)) {
|
|
1393
|
+
throw new ConfigError(
|
|
1394
|
+
`Project not initialized. Run "ctx init" first. (${CTX_DIR}/ not found)`,
|
|
1395
|
+
ErrorCode.NOT_INITIALIZED
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
return ctxDir;
|
|
1399
|
+
}
|
|
1400
|
+
function configPath(ctxDir) {
|
|
1401
|
+
return path4.join(ctxDir, CONFIG_FILENAME);
|
|
1402
|
+
}
|
|
1403
|
+
function readConfig(ctxDir) {
|
|
1404
|
+
const filePath = configPath(ctxDir);
|
|
1405
|
+
if (!fs5.existsSync(filePath)) {
|
|
1406
|
+
writeConfig(ctxDir, DEFAULT_CONFIG);
|
|
1407
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
1408
|
+
}
|
|
1409
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
1410
|
+
const parsed = JSON.parse(raw);
|
|
1411
|
+
return mergeWithDefaults(parsed);
|
|
1412
|
+
}
|
|
1413
|
+
function writeConfig(ctxDir, config) {
|
|
1414
|
+
fs5.writeFileSync(
|
|
1415
|
+
configPath(ctxDir),
|
|
1416
|
+
JSON.stringify(config, null, 2) + "\n"
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
function mergeWithDefaults(partial) {
|
|
1420
|
+
return {
|
|
1421
|
+
embedder: { ...DEFAULT_CONFIG.embedder, ...partial.embedder },
|
|
1422
|
+
search: {
|
|
1423
|
+
...DEFAULT_CONFIG.search,
|
|
1424
|
+
...partial.search,
|
|
1425
|
+
weights: { ...DEFAULT_CONFIG.search.weights, ...partial.search?.weights }
|
|
1426
|
+
},
|
|
1427
|
+
watch: { ...DEFAULT_CONFIG.watch, ...partial.watch },
|
|
1428
|
+
llm: { ...DEFAULT_CONFIG.llm, ...partial.llm }
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function getNestedValue(obj, key) {
|
|
1432
|
+
const parts = key.split(".");
|
|
1433
|
+
let current = obj;
|
|
1434
|
+
for (const part of parts) {
|
|
1435
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
1436
|
+
return void 0;
|
|
1437
|
+
}
|
|
1438
|
+
current = current[part];
|
|
1439
|
+
}
|
|
1440
|
+
return current;
|
|
1441
|
+
}
|
|
1442
|
+
function setNestedValue(obj, key, value) {
|
|
1443
|
+
const parts = key.split(".");
|
|
1444
|
+
let current = obj;
|
|
1445
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1446
|
+
const part = parts[i];
|
|
1447
|
+
if (typeof current[part] !== "object" || current[part] === null) {
|
|
1448
|
+
current[part] = {};
|
|
1449
|
+
}
|
|
1450
|
+
current = current[part];
|
|
1451
|
+
}
|
|
1452
|
+
current[parts[parts.length - 1]] = value;
|
|
1453
|
+
}
|
|
1454
|
+
function parseValue(rawValue) {
|
|
1455
|
+
if (rawValue === "null") return null;
|
|
1456
|
+
if (rawValue === "true") return true;
|
|
1457
|
+
if (rawValue === "false") return false;
|
|
1458
|
+
const num = Number(rawValue);
|
|
1459
|
+
if (!Number.isNaN(num) && rawValue.trim() !== "") return num;
|
|
1460
|
+
if (rawValue.startsWith("[") || rawValue.startsWith("{")) {
|
|
1461
|
+
try {
|
|
1462
|
+
return JSON.parse(rawValue);
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return rawValue;
|
|
1467
|
+
}
|
|
1468
|
+
function runConfigShow(projectPath) {
|
|
1469
|
+
const ctxDir = resolveCtxDir(projectPath);
|
|
1470
|
+
const config = readConfig(ctxDir);
|
|
1471
|
+
return {
|
|
1472
|
+
config,
|
|
1473
|
+
text: JSON.stringify(config, null, 2)
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function runConfigGet(projectPath, key) {
|
|
1477
|
+
const ctxDir = resolveCtxDir(projectPath);
|
|
1478
|
+
const config = readConfig(ctxDir);
|
|
1479
|
+
return getNestedValue(config, key);
|
|
1480
|
+
}
|
|
1481
|
+
function runConfigSet(projectPath, key, rawValue) {
|
|
1482
|
+
const ctxDir = resolveCtxDir(projectPath);
|
|
1483
|
+
const config = readConfig(ctxDir);
|
|
1484
|
+
const value = parseValue(rawValue);
|
|
1485
|
+
const rule = VALIDATION_RULES[key];
|
|
1486
|
+
if (rule && !rule.validate(value)) {
|
|
1487
|
+
throw new ConfigError(`Invalid value for "${key}": ${rule.message}`, ErrorCode.CONFIG_INVALID);
|
|
1488
|
+
}
|
|
1489
|
+
setNestedValue(config, key, value);
|
|
1490
|
+
writeConfig(ctxDir, config);
|
|
1491
|
+
}
|
|
1492
|
+
function runConfigReset(projectPath) {
|
|
1493
|
+
const ctxDir = resolveCtxDir(projectPath);
|
|
1494
|
+
writeConfig(ctxDir, structuredClone(DEFAULT_CONFIG));
|
|
1495
|
+
}
|
|
1496
|
+
function registerConfigCommand(program2) {
|
|
1497
|
+
const cmd = program2.command("config").description("Show or modify configuration");
|
|
1498
|
+
function configErrorHandler(err) {
|
|
1499
|
+
const verbose = program2.opts()["verbose"] === true;
|
|
1500
|
+
const logger = createLogger({ level: verbose ? LogLevel.DEBUG : LogLevel.INFO });
|
|
1501
|
+
process.exitCode = handleCommandError(err, logger, verbose);
|
|
1502
|
+
}
|
|
1503
|
+
cmd.command("show").description("Show current configuration").action(() => {
|
|
1504
|
+
try {
|
|
1505
|
+
const output = runConfigShow(process.cwd());
|
|
1506
|
+
console.log(output.text);
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
configErrorHandler(err);
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
cmd.command("get <key>").description("Get a configuration value (dot notation)").action((key) => {
|
|
1512
|
+
try {
|
|
1513
|
+
const value = runConfigGet(process.cwd(), key);
|
|
1514
|
+
console.log(
|
|
1515
|
+
typeof value === "object" ? JSON.stringify(value, null, 2) : String(value)
|
|
1516
|
+
);
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
configErrorHandler(err);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
cmd.command("set <key> <value>").description("Set a configuration value (dot notation)").action((key, value) => {
|
|
1522
|
+
try {
|
|
1523
|
+
runConfigSet(process.cwd(), key, value);
|
|
1524
|
+
console.log(`Set ${key} = ${value}`);
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
configErrorHandler(err);
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
cmd.command("reset").description("Reset configuration to defaults").action(() => {
|
|
1530
|
+
try {
|
|
1531
|
+
runConfigReset(process.cwd());
|
|
1532
|
+
console.log("Configuration reset to defaults.");
|
|
1533
|
+
} catch (err) {
|
|
1534
|
+
configErrorHandler(err);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/cli/commands/init.ts
|
|
1540
|
+
var CTX_DIR2 = ".ctx";
|
|
1541
|
+
var DB_FILENAME = "index.db";
|
|
1542
|
+
var CONFIG_FILENAME2 = "config.json";
|
|
1343
1543
|
var GITIGNORE_ENTRY = ".ctx/";
|
|
1344
1544
|
function ensureGitignore(projectRoot) {
|
|
1345
|
-
const gitignorePath =
|
|
1346
|
-
if (
|
|
1347
|
-
const content =
|
|
1545
|
+
const gitignorePath = path5.join(projectRoot, ".gitignore");
|
|
1546
|
+
if (fs6.existsSync(gitignorePath)) {
|
|
1547
|
+
const content = fs6.readFileSync(gitignorePath, "utf-8");
|
|
1348
1548
|
if (content.includes(GITIGNORE_ENTRY)) return;
|
|
1349
1549
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
1350
|
-
|
|
1550
|
+
fs6.writeFileSync(gitignorePath, `${content}${suffix}${GITIGNORE_ENTRY}
|
|
1351
1551
|
`);
|
|
1352
1552
|
} else {
|
|
1353
|
-
|
|
1553
|
+
fs6.writeFileSync(gitignorePath, `${GITIGNORE_ENTRY}
|
|
1354
1554
|
`);
|
|
1355
1555
|
}
|
|
1356
1556
|
}
|
|
1357
1557
|
function ensureConfig(ctxDir) {
|
|
1358
|
-
const configPath2 =
|
|
1359
|
-
if (
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
};
|
|
1365
|
-
fs5.writeFileSync(configPath2, JSON.stringify(config, null, 2) + "\n");
|
|
1558
|
+
const configPath2 = path5.join(ctxDir, CONFIG_FILENAME2);
|
|
1559
|
+
if (fs6.existsSync(configPath2)) return;
|
|
1560
|
+
fs6.writeFileSync(
|
|
1561
|
+
configPath2,
|
|
1562
|
+
JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n"
|
|
1563
|
+
);
|
|
1366
1564
|
}
|
|
1367
1565
|
function formatDuration(ms) {
|
|
1368
1566
|
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
@@ -1379,14 +1577,14 @@ function formatLanguageSummary(counts) {
|
|
|
1379
1577
|
}
|
|
1380
1578
|
async function runInit(projectPath, options = {}) {
|
|
1381
1579
|
const log = options.log ?? console.log;
|
|
1382
|
-
const absoluteRoot =
|
|
1580
|
+
const absoluteRoot = path5.resolve(projectPath);
|
|
1383
1581
|
const start = performance.now();
|
|
1384
1582
|
log(`Indexing ${absoluteRoot}...`);
|
|
1385
|
-
const ctxDir =
|
|
1386
|
-
if (!
|
|
1583
|
+
const ctxDir = path5.join(absoluteRoot, CTX_DIR2);
|
|
1584
|
+
if (!fs6.existsSync(ctxDir)) fs6.mkdirSync(ctxDir, { recursive: true });
|
|
1387
1585
|
ensureGitignore(absoluteRoot);
|
|
1388
1586
|
ensureConfig(ctxDir);
|
|
1389
|
-
const dbPath =
|
|
1587
|
+
const dbPath = path5.join(ctxDir, DB_FILENAME);
|
|
1390
1588
|
const db = createDatabase(dbPath);
|
|
1391
1589
|
try {
|
|
1392
1590
|
const discovered = await discoverFiles({
|
|
@@ -1491,13 +1689,13 @@ async function runInit(projectPath, options = {}) {
|
|
|
1491
1689
|
vectorsCreated = vectors.length;
|
|
1492
1690
|
}
|
|
1493
1691
|
const durationMs = performance.now() - start;
|
|
1494
|
-
const dbSize =
|
|
1692
|
+
const dbSize = fs6.existsSync(dbPath) ? fs6.statSync(dbPath).size : 0;
|
|
1495
1693
|
log("");
|
|
1496
1694
|
log(`\u2713 Indexed in ${formatDuration(durationMs)}`);
|
|
1497
1695
|
log(
|
|
1498
1696
|
` ${discovered.length} files \u2192 ${allChunksWithMeta.length} chunks` + (vectorsCreated > 0 ? ` \u2192 ${vectorsCreated} vectors` : "")
|
|
1499
1697
|
);
|
|
1500
|
-
log(` Database: ${
|
|
1698
|
+
log(` Database: ${CTX_DIR2}/${DB_FILENAME} (${formatBytes(dbSize)})`);
|
|
1501
1699
|
return {
|
|
1502
1700
|
filesDiscovered: discovered.length,
|
|
1503
1701
|
filesAdded: changes.added.length,
|
|
@@ -1535,8 +1733,8 @@ function registerInitCommand(program2) {
|
|
|
1535
1733
|
}
|
|
1536
1734
|
|
|
1537
1735
|
// src/cli/commands/query.ts
|
|
1538
|
-
import
|
|
1539
|
-
import
|
|
1736
|
+
import fs7 from "fs";
|
|
1737
|
+
import path6 from "path";
|
|
1540
1738
|
|
|
1541
1739
|
// src/search/vector.ts
|
|
1542
1740
|
function distanceToScore(distance) {
|
|
@@ -1577,7 +1775,15 @@ async function vectorSearch(db, embedder, query, limit, filters) {
|
|
|
1577
1775
|
|
|
1578
1776
|
// src/search/fts.ts
|
|
1579
1777
|
function sanitizeFtsQuery(query) {
|
|
1580
|
-
|
|
1778
|
+
const tokenized = query.replace(/[^A-Za-z0-9_*]+/g, " ").trim();
|
|
1779
|
+
if (tokenized.length === 0) return "";
|
|
1780
|
+
const sanitizedTerms = tokenized.split(/\s+/).map((term) => {
|
|
1781
|
+
const hasTrailingWildcard = /\*+$/.test(term);
|
|
1782
|
+
const base = term.replace(/\*/g, "");
|
|
1783
|
+
if (base.length === 0) return "";
|
|
1784
|
+
return hasTrailingWildcard ? `${base}*` : base;
|
|
1785
|
+
}).filter((term) => term.length > 0);
|
|
1786
|
+
return sanitizedTerms.join(" ");
|
|
1581
1787
|
}
|
|
1582
1788
|
function bm25ToScore(rank) {
|
|
1583
1789
|
return 1 / (1 + Math.abs(rank));
|
|
@@ -1722,27 +1928,56 @@ function pathKeywordSearch(db, query, limit) {
|
|
|
1722
1928
|
}
|
|
1723
1929
|
if (scoredPaths.length === 0) return [];
|
|
1724
1930
|
scoredPaths.sort((a, b) => b.score - a.score);
|
|
1725
|
-
const
|
|
1931
|
+
const matchedFiles = [];
|
|
1726
1932
|
for (const { filePath, score } of scoredPaths) {
|
|
1727
|
-
if (results.length >= limit) break;
|
|
1728
1933
|
const file = db.getFile(filePath);
|
|
1729
1934
|
if (!file) continue;
|
|
1730
1935
|
const chunks = db.getChunksByFile(file.id);
|
|
1731
|
-
|
|
1936
|
+
if (chunks.length === 0) continue;
|
|
1937
|
+
matchedFiles.push({
|
|
1938
|
+
filePath: file.path,
|
|
1939
|
+
language: file.language,
|
|
1940
|
+
score,
|
|
1941
|
+
chunks
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
if (matchedFiles.length === 0) return [];
|
|
1945
|
+
const results = [];
|
|
1946
|
+
const pushChunk = (filePath, language, score, chunk) => {
|
|
1947
|
+
results.push({
|
|
1948
|
+
chunkId: chunk.id,
|
|
1949
|
+
filePath,
|
|
1950
|
+
lineStart: chunk.lineStart,
|
|
1951
|
+
lineEnd: chunk.lineEnd,
|
|
1952
|
+
name: chunk.name,
|
|
1953
|
+
type: chunk.type,
|
|
1954
|
+
exported: chunk.exports,
|
|
1955
|
+
text: chunk.text,
|
|
1956
|
+
score,
|
|
1957
|
+
language
|
|
1958
|
+
});
|
|
1959
|
+
};
|
|
1960
|
+
for (const matched of matchedFiles) {
|
|
1961
|
+
if (results.length >= limit) break;
|
|
1962
|
+
pushChunk(
|
|
1963
|
+
matched.filePath,
|
|
1964
|
+
matched.language,
|
|
1965
|
+
matched.score,
|
|
1966
|
+
matched.chunks[0]
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
let offset = 1;
|
|
1970
|
+
while (results.length < limit) {
|
|
1971
|
+
let addedInRound = false;
|
|
1972
|
+
for (const matched of matchedFiles) {
|
|
1732
1973
|
if (results.length >= limit) break;
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
lineEnd: chunk.lineEnd,
|
|
1738
|
-
name: chunk.name,
|
|
1739
|
-
type: chunk.type,
|
|
1740
|
-
exported: chunk.exports,
|
|
1741
|
-
text: chunk.text,
|
|
1742
|
-
score,
|
|
1743
|
-
language: file.language
|
|
1744
|
-
});
|
|
1974
|
+
const chunk = matched.chunks[offset];
|
|
1975
|
+
if (!chunk) continue;
|
|
1976
|
+
pushChunk(matched.filePath, matched.language, matched.score, chunk);
|
|
1977
|
+
addedInRound = true;
|
|
1745
1978
|
}
|
|
1979
|
+
if (!addedInRound) break;
|
|
1980
|
+
offset++;
|
|
1746
1981
|
}
|
|
1747
1982
|
return results;
|
|
1748
1983
|
}
|
|
@@ -1941,25 +2176,128 @@ function renormalize(results) {
|
|
|
1941
2176
|
}));
|
|
1942
2177
|
}
|
|
1943
2178
|
|
|
1944
|
-
// src/
|
|
1945
|
-
var
|
|
1946
|
-
var
|
|
1947
|
-
var
|
|
1948
|
-
var
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
2179
|
+
// src/steering/classify.ts
|
|
2180
|
+
var SYMBOL_CAMEL_RE = /^[a-z][a-zA-Z0-9]*$/;
|
|
2181
|
+
var SYMBOL_PASCAL_RE = /^[A-Z][a-zA-Z0-9]*$/;
|
|
2182
|
+
var SYMBOL_SNAKE_RE = /^[a-z]+(?:_[a-z]+)+$/;
|
|
2183
|
+
var SYMBOL_UPPER_RE = /^[A-Z]+(?:_[A-Z]+)*$/;
|
|
2184
|
+
var PATH_EXTENSION_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|swift|rb|php|cs|cpp|c|h|hpp|json|yaml|yml|toml|md|sql|sh|bash)$/i;
|
|
2185
|
+
var QUESTION_WORDS = /* @__PURE__ */ new Set([
|
|
2186
|
+
"how",
|
|
2187
|
+
"what",
|
|
2188
|
+
"where",
|
|
2189
|
+
"why",
|
|
2190
|
+
"when",
|
|
2191
|
+
"which",
|
|
2192
|
+
"show",
|
|
2193
|
+
"explain",
|
|
2194
|
+
"find",
|
|
2195
|
+
"list"
|
|
2196
|
+
]);
|
|
2197
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
2198
|
+
"the",
|
|
2199
|
+
"a",
|
|
2200
|
+
"an",
|
|
2201
|
+
"is",
|
|
2202
|
+
"are",
|
|
2203
|
+
"was",
|
|
2204
|
+
"were",
|
|
2205
|
+
"do",
|
|
2206
|
+
"does",
|
|
2207
|
+
"did",
|
|
2208
|
+
"to",
|
|
2209
|
+
"for",
|
|
2210
|
+
"of",
|
|
2211
|
+
"in",
|
|
2212
|
+
"on",
|
|
2213
|
+
"with",
|
|
2214
|
+
"by",
|
|
2215
|
+
"and",
|
|
2216
|
+
"or"
|
|
2217
|
+
]);
|
|
2218
|
+
function defaultMultipliers() {
|
|
2219
|
+
return {
|
|
2220
|
+
vector: 1,
|
|
2221
|
+
fts: 1,
|
|
2222
|
+
ast: 1,
|
|
2223
|
+
path: 1,
|
|
2224
|
+
dependency: 1
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function isSymbolQuery(query) {
|
|
2228
|
+
return SYMBOL_CAMEL_RE.test(query) || SYMBOL_PASCAL_RE.test(query) || SYMBOL_SNAKE_RE.test(query) || SYMBOL_UPPER_RE.test(query);
|
|
2229
|
+
}
|
|
2230
|
+
function isPathQuery(query) {
|
|
2231
|
+
return query.includes("/") || PATH_EXTENSION_RE.test(query);
|
|
2232
|
+
}
|
|
2233
|
+
function isNaturalLanguageQuery(query) {
|
|
2234
|
+
const lower = query.toLowerCase();
|
|
2235
|
+
const words = lower.split(/\s+/).filter((w) => w.length > 0);
|
|
2236
|
+
const hasQuestionWord = words.some((w) => QUESTION_WORDS.has(w));
|
|
2237
|
+
const hasStopWord = words.some((w) => STOP_WORDS.has(w));
|
|
2238
|
+
return hasQuestionWord || words.length >= 4 && hasStopWord;
|
|
2239
|
+
}
|
|
2240
|
+
function classifyQuery(query) {
|
|
2241
|
+
const trimmed = query.trim();
|
|
2242
|
+
const multipliers = defaultMultipliers();
|
|
2243
|
+
if (isPathQuery(trimmed)) {
|
|
2244
|
+
multipliers.path = 2;
|
|
2245
|
+
multipliers.ast = 0.5;
|
|
2246
|
+
return { kind: "path", multipliers };
|
|
2247
|
+
}
|
|
2248
|
+
if (isSymbolQuery(trimmed)) {
|
|
2249
|
+
multipliers.ast = 1.5;
|
|
2250
|
+
multipliers.vector = 0.5;
|
|
2251
|
+
return { kind: "symbol", multipliers };
|
|
2252
|
+
}
|
|
2253
|
+
if (isNaturalLanguageQuery(trimmed)) {
|
|
2254
|
+
multipliers.vector = 1.5;
|
|
2255
|
+
multipliers.path = 1.2;
|
|
2256
|
+
multipliers.ast = 0.7;
|
|
2257
|
+
return { kind: "natural_language", multipliers };
|
|
2258
|
+
}
|
|
2259
|
+
return { kind: "keyword", multipliers };
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// src/cli/commands/query.ts
|
|
2263
|
+
var CTX_DIR3 = ".ctx";
|
|
2264
|
+
var DB_FILENAME2 = "index.db";
|
|
2265
|
+
var SNIPPET_MAX_LENGTH = 200;
|
|
2266
|
+
var STRATEGY_WEIGHTS = {
|
|
2267
|
+
vector: 1,
|
|
2268
|
+
fts: 0.8,
|
|
2269
|
+
ast: 0.9,
|
|
2270
|
+
path: 0.7,
|
|
2271
|
+
dependency: 0.6
|
|
2272
|
+
};
|
|
2273
|
+
function getEffectiveStrategyWeights(query) {
|
|
2274
|
+
const { multipliers } = classifyQuery(query);
|
|
2275
|
+
return {
|
|
2276
|
+
vector: STRATEGY_WEIGHTS.vector * multipliers.vector,
|
|
2277
|
+
fts: STRATEGY_WEIGHTS.fts * multipliers.fts,
|
|
2278
|
+
ast: STRATEGY_WEIGHTS.ast * multipliers.ast,
|
|
2279
|
+
path: STRATEGY_WEIGHTS.path * multipliers.path,
|
|
2280
|
+
dependency: STRATEGY_WEIGHTS.dependency * multipliers.dependency
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
function normalizeLimit(limit) {
|
|
2284
|
+
if (!Number.isFinite(limit)) return 0;
|
|
2285
|
+
return Math.max(0, Math.trunc(limit));
|
|
2286
|
+
}
|
|
2287
|
+
function resolveQueryStrategies(query, strategies, source) {
|
|
2288
|
+
if (source !== "default") return strategies;
|
|
2289
|
+
if (classifyQuery(query).kind !== "natural_language") return strategies;
|
|
2290
|
+
if (strategies.includes("vector")) return strategies;
|
|
2291
|
+
return [...strategies, "vector"];
|
|
2292
|
+
}
|
|
2293
|
+
function truncateSnippet(text) {
|
|
2294
|
+
const oneLine = text.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
|
|
2295
|
+
if (oneLine.length <= SNIPPET_MAX_LENGTH) return oneLine;
|
|
2296
|
+
return oneLine.slice(0, SNIPPET_MAX_LENGTH) + "...";
|
|
2297
|
+
}
|
|
2298
|
+
function toOutputResult(r) {
|
|
2299
|
+
return {
|
|
2300
|
+
file: r.filePath,
|
|
1963
2301
|
lines: [r.lineStart, r.lineEnd],
|
|
1964
2302
|
name: r.name,
|
|
1965
2303
|
type: r.type,
|
|
@@ -1992,20 +2330,21 @@ function isPathLike(query) {
|
|
|
1992
2330
|
return query.includes("/") || query.includes("*") || query.includes(".");
|
|
1993
2331
|
}
|
|
1994
2332
|
async function runQuery(projectPath, query, options) {
|
|
1995
|
-
const
|
|
1996
|
-
const
|
|
1997
|
-
|
|
2333
|
+
const limit = normalizeLimit(options.limit);
|
|
2334
|
+
const absoluteRoot = path6.resolve(projectPath);
|
|
2335
|
+
const dbPath = path6.join(absoluteRoot, CTX_DIR3, DB_FILENAME2);
|
|
2336
|
+
if (!fs7.existsSync(dbPath)) {
|
|
1998
2337
|
throw new KontextError(
|
|
1999
|
-
`Project not initialized. Run "ctx init" first. (${
|
|
2338
|
+
`Project not initialized. Run "ctx init" first. (${CTX_DIR3}/${DB_FILENAME2} not found)`,
|
|
2000
2339
|
ErrorCode.NOT_INITIALIZED
|
|
2001
2340
|
);
|
|
2002
2341
|
}
|
|
2003
2342
|
const start = performance.now();
|
|
2004
2343
|
const db = createDatabase(dbPath);
|
|
2005
2344
|
try {
|
|
2006
|
-
const strategyResults = await runStrategies(db, query, options);
|
|
2345
|
+
const strategyResults = await runStrategies(db, query, { ...options, limit });
|
|
2007
2346
|
const pathBoostTerms = extractPathBoostTerms(query);
|
|
2008
|
-
const fused = fusionMergeWithPathBoost(strategyResults,
|
|
2347
|
+
const fused = fusionMergeWithPathBoost(strategyResults, limit, pathBoostTerms);
|
|
2009
2348
|
const outputResults = fused.map(toOutputResult);
|
|
2010
2349
|
const searchTimeMs = Math.round(performance.now() - start);
|
|
2011
2350
|
const text = options.format === "text" ? formatTextOutput(query, outputResults) : void 0;
|
|
@@ -2027,8 +2366,9 @@ async function runStrategies(db, query, options) {
|
|
|
2027
2366
|
const results = [];
|
|
2028
2367
|
const filters = options.language ? { language: options.language } : void 0;
|
|
2029
2368
|
const limit = options.limit * 3;
|
|
2369
|
+
const effectiveWeights = getEffectiveStrategyWeights(query);
|
|
2030
2370
|
for (const strategy of options.strategies) {
|
|
2031
|
-
const weight =
|
|
2371
|
+
const weight = effectiveWeights[strategy];
|
|
2032
2372
|
const searchResults = await executeStrategy(
|
|
2033
2373
|
db,
|
|
2034
2374
|
strategy,
|
|
@@ -2084,18 +2424,25 @@ async function loadEmbedder() {
|
|
|
2084
2424
|
return embedderInstance;
|
|
2085
2425
|
}
|
|
2086
2426
|
function registerQueryCommand(program2) {
|
|
2087
|
-
program2.command("query <query>").description("Multi-strategy code search").option("-l, --limit <n>", "Max results", "10").option(
|
|
2427
|
+
program2.command("query <query>").alias("find").description("Multi-strategy code search").option("-l, --limit <n>", "Max results", "10").option(
|
|
2088
2428
|
"-s, --strategy <list>",
|
|
2089
2429
|
"Comma-separated strategies: vector,fts,ast,path",
|
|
2090
2430
|
"fts,ast,path"
|
|
2091
|
-
).option("--language <lang>", "Filter by language").option("-f, --format <fmt>", "Output format: json|text", "json").option("--no-vectors", "Skip vector search").action(async (query, opts) => {
|
|
2431
|
+
).option("--language <lang>", "Filter by language").option("-f, --format <fmt>", "Output format: json|text", "json").option("--no-vectors", "Skip vector search").action(async (query, opts, command) => {
|
|
2092
2432
|
const projectPath = process.cwd();
|
|
2093
2433
|
const verbose = program2.opts()["verbose"] === true;
|
|
2094
2434
|
const logger = createLogger({ level: verbose ? LogLevel.DEBUG : LogLevel.INFO });
|
|
2095
|
-
const
|
|
2435
|
+
const strategySource = command.getOptionValueSource("strategy");
|
|
2436
|
+
const parsedStrategies = (opts["strategy"] ?? "fts,ast,path").split(",").map((s) => s.trim());
|
|
2437
|
+
const strategies = resolveQueryStrategies(
|
|
2438
|
+
query,
|
|
2439
|
+
parsedStrategies,
|
|
2440
|
+
strategySource === "default" ? "default" : strategySource ? "cli" : "unknown"
|
|
2441
|
+
);
|
|
2442
|
+
const limit = normalizeLimit(parseInt(opts["limit"] ?? "10", 10));
|
|
2096
2443
|
try {
|
|
2097
2444
|
const output = await runQuery(projectPath, query, {
|
|
2098
|
-
limit
|
|
2445
|
+
limit,
|
|
2099
2446
|
strategies,
|
|
2100
2447
|
language: opts["language"],
|
|
2101
2448
|
format: opts["format"] ?? "json"
|
|
@@ -2117,8 +2464,8 @@ function registerQueryCommand(program2) {
|
|
|
2117
2464
|
}
|
|
2118
2465
|
|
|
2119
2466
|
// src/cli/commands/ask.ts
|
|
2120
|
-
import
|
|
2121
|
-
import
|
|
2467
|
+
import fs8 from "fs";
|
|
2468
|
+
import path7 from "path";
|
|
2122
2469
|
|
|
2123
2470
|
// src/steering/prompts.ts
|
|
2124
2471
|
var PLAN_SYSTEM_PROMPT = `You are a code-search strategy planner for a TypeScript/JavaScript codebase.
|
|
@@ -2348,7 +2695,7 @@ function createAnthropicProvider(apiKey) {
|
|
|
2348
2695
|
}
|
|
2349
2696
|
};
|
|
2350
2697
|
}
|
|
2351
|
-
var
|
|
2698
|
+
var STOP_WORDS2 = /* @__PURE__ */ new Set([
|
|
2352
2699
|
// Interrogatives & conjunctions
|
|
2353
2700
|
"how",
|
|
2354
2701
|
"does",
|
|
@@ -2459,6 +2806,80 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
2459
2806
|
]);
|
|
2460
2807
|
var CODE_IDENT_RE = /^(?:[a-z]+(?:[A-Z][a-z]*)+|[A-Z][a-zA-Z]+|[a-z]+(?:_[a-z]+)+|[A-Z]+(?:_[A-Z]+)+)$/;
|
|
2461
2808
|
var DOTTED_IDENT_RE = /[a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)+/g;
|
|
2809
|
+
var COMMON_STEMS = {
|
|
2810
|
+
authentication: "auth",
|
|
2811
|
+
authorization: "auth",
|
|
2812
|
+
configuration: "config",
|
|
2813
|
+
initialization: "init",
|
|
2814
|
+
initialize: "init",
|
|
2815
|
+
initializing: "init",
|
|
2816
|
+
implementation: "impl",
|
|
2817
|
+
implements: "impl",
|
|
2818
|
+
implementing: "impl",
|
|
2819
|
+
dependency: "dep",
|
|
2820
|
+
dependencies: "dep",
|
|
2821
|
+
middleware: "middleware",
|
|
2822
|
+
validation: "valid",
|
|
2823
|
+
validator: "valid",
|
|
2824
|
+
serialize: "serial",
|
|
2825
|
+
serialization: "serial",
|
|
2826
|
+
deserialize: "deserial",
|
|
2827
|
+
database: "db",
|
|
2828
|
+
logging: "log",
|
|
2829
|
+
logger: "log",
|
|
2830
|
+
testing: "test",
|
|
2831
|
+
handler: "handle",
|
|
2832
|
+
handling: "handle",
|
|
2833
|
+
callback: "callback",
|
|
2834
|
+
subscriber: "subscribe",
|
|
2835
|
+
subscription: "subscribe",
|
|
2836
|
+
rendering: "render",
|
|
2837
|
+
renderer: "render",
|
|
2838
|
+
transformer: "transform",
|
|
2839
|
+
transformation: "transform",
|
|
2840
|
+
connection: "connect",
|
|
2841
|
+
connector: "connect",
|
|
2842
|
+
migration: "migrate",
|
|
2843
|
+
scheduling: "schedule",
|
|
2844
|
+
scheduler: "schedule",
|
|
2845
|
+
parsing: "parse",
|
|
2846
|
+
parser: "parse",
|
|
2847
|
+
routing: "route",
|
|
2848
|
+
router: "route",
|
|
2849
|
+
indexing: "index",
|
|
2850
|
+
indexer: "index"
|
|
2851
|
+
};
|
|
2852
|
+
var STEM_SUFFIXES = [
|
|
2853
|
+
"tion",
|
|
2854
|
+
"sion",
|
|
2855
|
+
"ment",
|
|
2856
|
+
"ness",
|
|
2857
|
+
"ing",
|
|
2858
|
+
"er",
|
|
2859
|
+
"or",
|
|
2860
|
+
"able",
|
|
2861
|
+
"ible",
|
|
2862
|
+
"ity",
|
|
2863
|
+
"ous",
|
|
2864
|
+
"ive",
|
|
2865
|
+
"ful",
|
|
2866
|
+
"less",
|
|
2867
|
+
"ly"
|
|
2868
|
+
];
|
|
2869
|
+
function getStemVariant(term) {
|
|
2870
|
+
const lower = term.toLowerCase();
|
|
2871
|
+
const mapped = COMMON_STEMS[lower];
|
|
2872
|
+
if (mapped && mapped !== lower) return mapped;
|
|
2873
|
+
if (!/^[a-z][a-z0-9_]*$/.test(lower)) return null;
|
|
2874
|
+
for (const suffix of STEM_SUFFIXES) {
|
|
2875
|
+
if (!lower.endsWith(suffix)) continue;
|
|
2876
|
+
const stem = lower.slice(0, -suffix.length);
|
|
2877
|
+
if (stem.length >= 4 && stem !== lower) {
|
|
2878
|
+
return stem;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2462
2883
|
function extractSearchTerms(query) {
|
|
2463
2884
|
const terms = [];
|
|
2464
2885
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2469,16 +2890,23 @@ function extractSearchTerms(query) {
|
|
|
2469
2890
|
terms.push(term);
|
|
2470
2891
|
}
|
|
2471
2892
|
};
|
|
2893
|
+
const addTermAndVariants = (term) => {
|
|
2894
|
+
addUnique(term);
|
|
2895
|
+
const variant = getStemVariant(term);
|
|
2896
|
+
if (variant && variant !== term.toLowerCase()) {
|
|
2897
|
+
addUnique(variant);
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2472
2900
|
const dottedMatches = query.match(DOTTED_IDENT_RE) ?? [];
|
|
2473
|
-
for (const m of dottedMatches)
|
|
2901
|
+
for (const m of dottedMatches) addTermAndVariants(m);
|
|
2474
2902
|
const pathTokens = query.split(/\s+/).filter((t) => t.includes("/"));
|
|
2475
|
-
for (const p of pathTokens)
|
|
2903
|
+
for (const p of pathTokens) addTermAndVariants(p.replace(/[?!,;]+$/g, ""));
|
|
2476
2904
|
const words = query.replace(/[^a-zA-Z0-9_.\s/-]/g, " ").split(/\s+/).filter((w) => w.length >= 2);
|
|
2477
2905
|
for (const w of words) {
|
|
2478
2906
|
const lower = w.toLowerCase();
|
|
2479
2907
|
if (seen.has(lower)) continue;
|
|
2480
|
-
if (
|
|
2481
|
-
|
|
2908
|
+
if (STOP_WORDS2.has(lower) && !CODE_IDENT_RE.test(w)) continue;
|
|
2909
|
+
addTermAndVariants(w);
|
|
2482
2910
|
}
|
|
2483
2911
|
if (terms.length === 0) {
|
|
2484
2912
|
const allWords = query.replace(/[^a-zA-Z0-9_\s]/g, " ").split(/\s+/).filter((w) => w.length >= 2);
|
|
@@ -2495,17 +2923,42 @@ var VALID_STRATEGIES = /* @__PURE__ */ new Set([
|
|
|
2495
2923
|
"dependency"
|
|
2496
2924
|
]);
|
|
2497
2925
|
function buildFallbackPlan(query) {
|
|
2498
|
-
const
|
|
2499
|
-
const strategies = [
|
|
2500
|
-
{ strategy: "fts", query: keywords, weight: 0.8, reason: "Full-text keyword search" },
|
|
2501
|
-
{ strategy: "ast", query: keywords, weight: 0.9, reason: "Structural symbol search" },
|
|
2502
|
-
{ strategy: "path", query: keywords, weight: 0.7, reason: "Path keyword search" }
|
|
2503
|
-
];
|
|
2926
|
+
const strategies = buildFallbackStrategies(query);
|
|
2504
2927
|
return {
|
|
2505
2928
|
interpretation: `Searching for: ${query}`,
|
|
2506
2929
|
strategies
|
|
2507
2930
|
};
|
|
2508
2931
|
}
|
|
2932
|
+
function buildFallbackStrategies(query) {
|
|
2933
|
+
const keywords = extractSearchTerms(query);
|
|
2934
|
+
const { multipliers } = classifyQuery(query);
|
|
2935
|
+
return [
|
|
2936
|
+
{
|
|
2937
|
+
strategy: "vector",
|
|
2938
|
+
query,
|
|
2939
|
+
weight: 1 * multipliers.vector,
|
|
2940
|
+
reason: "Semantic search over natural language intent"
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
strategy: "fts",
|
|
2944
|
+
query: keywords,
|
|
2945
|
+
weight: 0.8 * multipliers.fts,
|
|
2946
|
+
reason: "Full-text keyword search"
|
|
2947
|
+
},
|
|
2948
|
+
{
|
|
2949
|
+
strategy: "ast",
|
|
2950
|
+
query: keywords,
|
|
2951
|
+
weight: 0.9 * multipliers.ast,
|
|
2952
|
+
reason: "Structural symbol search"
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
strategy: "path",
|
|
2956
|
+
query: keywords,
|
|
2957
|
+
weight: 0.7 * multipliers.path,
|
|
2958
|
+
reason: "Path keyword search"
|
|
2959
|
+
}
|
|
2960
|
+
];
|
|
2961
|
+
}
|
|
2509
2962
|
function parseSearchPlan(raw, query) {
|
|
2510
2963
|
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
2511
2964
|
if (!jsonMatch) return buildFallbackPlan(query);
|
|
@@ -2585,10 +3038,14 @@ async function steer(provider, query, limit, searchExecutor) {
|
|
|
2585
3038
|
}
|
|
2586
3039
|
|
|
2587
3040
|
// src/cli/commands/ask.ts
|
|
2588
|
-
var
|
|
3041
|
+
var CTX_DIR4 = ".ctx";
|
|
2589
3042
|
var DB_FILENAME3 = "index.db";
|
|
2590
3043
|
var SNIPPET_MAX_LENGTH2 = 200;
|
|
2591
3044
|
var FALLBACK_NOTICE = "No LLM provider configured. Set CTX_GEMINI_KEY, CTX_OPENAI_KEY, or CTX_ANTHROPIC_KEY. Running basic search instead.";
|
|
3045
|
+
function normalizeLimit2(limit) {
|
|
3046
|
+
if (!Number.isFinite(limit)) return 0;
|
|
3047
|
+
return Math.max(0, Math.trunc(limit));
|
|
3048
|
+
}
|
|
2592
3049
|
var PROVIDER_ENV_MAP = {
|
|
2593
3050
|
gemini: "CTX_GEMINI_KEY",
|
|
2594
3051
|
openai: "CTX_OPENAI_KEY",
|
|
@@ -2734,12 +3191,7 @@ async function loadEmbedder2() {
|
|
|
2734
3191
|
}
|
|
2735
3192
|
async function fallbackSearch(db, query, limit) {
|
|
2736
3193
|
const executor = createSearchExecutor(db, query);
|
|
2737
|
-
const
|
|
2738
|
-
const fallbackStrategies = [
|
|
2739
|
-
{ strategy: "fts", query: keywords, weight: 0.8, reason: "fallback keyword search" },
|
|
2740
|
-
{ strategy: "ast", query: keywords, weight: 0.9, reason: "fallback structural search" },
|
|
2741
|
-
{ strategy: "path", query: keywords, weight: 0.7, reason: "fallback path search" }
|
|
2742
|
-
];
|
|
3194
|
+
const fallbackStrategies = buildFallbackStrategies(query);
|
|
2743
3195
|
const results = await executor(fallbackStrategies, limit);
|
|
2744
3196
|
return {
|
|
2745
3197
|
query,
|
|
@@ -2756,11 +3208,12 @@ async function fallbackSearch(db, query, limit) {
|
|
|
2756
3208
|
};
|
|
2757
3209
|
}
|
|
2758
3210
|
async function runAsk(projectPath, query, options) {
|
|
2759
|
-
const
|
|
2760
|
-
const
|
|
2761
|
-
|
|
3211
|
+
const limit = normalizeLimit2(options.limit);
|
|
3212
|
+
const absoluteRoot = path7.resolve(projectPath);
|
|
3213
|
+
const dbPath = path7.join(absoluteRoot, CTX_DIR4, DB_FILENAME3);
|
|
3214
|
+
if (!fs8.existsSync(dbPath)) {
|
|
2762
3215
|
throw new KontextError(
|
|
2763
|
-
`Project not initialized. Run "ctx init" first. (${
|
|
3216
|
+
`Project not initialized. Run "ctx init" first. (${CTX_DIR4}/${DB_FILENAME3} not found)`,
|
|
2764
3217
|
ErrorCode.NOT_INITIALIZED
|
|
2765
3218
|
);
|
|
2766
3219
|
}
|
|
@@ -2768,7 +3221,7 @@ async function runAsk(projectPath, query, options) {
|
|
|
2768
3221
|
try {
|
|
2769
3222
|
const provider = options.provider ?? null;
|
|
2770
3223
|
if (!provider) {
|
|
2771
|
-
const output = await fallbackSearch(db, query,
|
|
3224
|
+
const output = await fallbackSearch(db, query, limit);
|
|
2772
3225
|
output.warning = FALLBACK_NOTICE;
|
|
2773
3226
|
if (options.format === "text") {
|
|
2774
3227
|
output.text = formatTextOutput2(output);
|
|
@@ -2777,16 +3230,16 @@ async function runAsk(projectPath, query, options) {
|
|
|
2777
3230
|
}
|
|
2778
3231
|
const executor = createSearchExecutor(db, query);
|
|
2779
3232
|
if (options.noExplain) {
|
|
2780
|
-
return await runNoExplain(provider, query, options, executor);
|
|
3233
|
+
return await runNoExplain(provider, query, limit, options, executor);
|
|
2781
3234
|
}
|
|
2782
|
-
return await runWithSteering(provider, query, options, executor);
|
|
3235
|
+
return await runWithSteering(provider, query, limit, options, executor);
|
|
2783
3236
|
} finally {
|
|
2784
3237
|
db.close();
|
|
2785
3238
|
}
|
|
2786
3239
|
}
|
|
2787
|
-
async function runNoExplain(provider, query, options, executor) {
|
|
3240
|
+
async function runNoExplain(provider, query, limit, options, executor) {
|
|
2788
3241
|
const plan = await planSearch(provider, query);
|
|
2789
|
-
const results = await executor(plan.strategies,
|
|
3242
|
+
const results = await executor(plan.strategies, limit);
|
|
2790
3243
|
const output = {
|
|
2791
3244
|
query,
|
|
2792
3245
|
interpretation: plan.interpretation,
|
|
@@ -2804,8 +3257,8 @@ async function runNoExplain(provider, query, options, executor) {
|
|
|
2804
3257
|
}
|
|
2805
3258
|
return output;
|
|
2806
3259
|
}
|
|
2807
|
-
async function runWithSteering(provider, query, options, executor) {
|
|
2808
|
-
const result = await steer(provider, query,
|
|
3260
|
+
async function runWithSteering(provider, query, limit, options, executor) {
|
|
3261
|
+
const result = await steer(provider, query, limit, executor);
|
|
2809
3262
|
const output = {
|
|
2810
3263
|
query,
|
|
2811
3264
|
interpretation: result.interpretation,
|
|
@@ -2830,9 +3283,10 @@ function registerAskCommand(program2) {
|
|
|
2830
3283
|
const logger = createLogger({ level: verbose ? LogLevel.DEBUG : LogLevel.INFO });
|
|
2831
3284
|
const providerName = opts["provider"];
|
|
2832
3285
|
const provider = detectProvider(providerName);
|
|
3286
|
+
const limit = normalizeLimit2(parseInt(String(opts["limit"] ?? "10"), 10));
|
|
2833
3287
|
try {
|
|
2834
3288
|
const output = await runAsk(projectPath, query, {
|
|
2835
|
-
limit
|
|
3289
|
+
limit,
|
|
2836
3290
|
format: opts["format"] ?? "text",
|
|
2837
3291
|
provider: provider ?? void 0,
|
|
2838
3292
|
noExplain: opts["explain"] === false
|
|
@@ -2856,13 +3310,6 @@ function registerAskCommand(program2) {
|
|
|
2856
3310
|
});
|
|
2857
3311
|
}
|
|
2858
3312
|
|
|
2859
|
-
// src/cli/commands/find.ts
|
|
2860
|
-
function registerFindCommand(program2) {
|
|
2861
|
-
program2.command("find <query>").description("Natural language code search").option("--full", "Include source code in output").option("--json", "Machine-readable JSON output").option("--no-llm", "Skip steering LLM, raw vector search only").option("-l, --limit <n>", "Max results", "5").option("--language <lang>", "Filter by language").action((_query, _options) => {
|
|
2862
|
-
console.log("ctx find \u2014 not yet implemented");
|
|
2863
|
-
});
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
3313
|
// src/cli/commands/update.ts
|
|
2867
3314
|
function registerUpdateCommand(program2) {
|
|
2868
3315
|
program2.command("update").description("Incremental re-index of changed files").action(() => {
|
|
@@ -2871,12 +3318,12 @@ function registerUpdateCommand(program2) {
|
|
|
2871
3318
|
}
|
|
2872
3319
|
|
|
2873
3320
|
// src/cli/commands/watch.ts
|
|
2874
|
-
import
|
|
2875
|
-
import
|
|
3321
|
+
import fs9 from "fs";
|
|
3322
|
+
import path9 from "path";
|
|
2876
3323
|
|
|
2877
3324
|
// src/watcher/watcher.ts
|
|
2878
3325
|
import { watch } from "chokidar";
|
|
2879
|
-
import
|
|
3326
|
+
import path8 from "path";
|
|
2880
3327
|
var DEFAULT_DEBOUNCE_MS = 500;
|
|
2881
3328
|
var ALWAYS_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2882
3329
|
"node_modules",
|
|
@@ -2888,15 +3335,15 @@ var ALWAYS_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2888
3335
|
]);
|
|
2889
3336
|
var WATCHED_EXTENSIONS = new Set(Object.keys(LANGUAGE_MAP));
|
|
2890
3337
|
function isWatchedFile(filePath) {
|
|
2891
|
-
const ext =
|
|
3338
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2892
3339
|
return WATCHED_EXTENSIONS.has(ext);
|
|
2893
3340
|
}
|
|
2894
3341
|
function createWatcher(options, events) {
|
|
2895
3342
|
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2896
|
-
const projectPath =
|
|
3343
|
+
const projectPath = path8.resolve(options.projectPath);
|
|
2897
3344
|
const extraIgnored = new Set(options.ignored ?? []);
|
|
2898
3345
|
function isIgnored(filePath) {
|
|
2899
|
-
const segments = filePath.split(
|
|
3346
|
+
const segments = filePath.split(path8.sep);
|
|
2900
3347
|
for (const seg of segments) {
|
|
2901
3348
|
if (ALWAYS_IGNORED_DIRS.has(seg)) return true;
|
|
2902
3349
|
if (extraIgnored.has(seg)) return true;
|
|
@@ -2954,13 +3401,13 @@ function createWatcher(options, events) {
|
|
|
2954
3401
|
}
|
|
2955
3402
|
|
|
2956
3403
|
// src/cli/commands/watch.ts
|
|
2957
|
-
var
|
|
3404
|
+
var CTX_DIR5 = ".ctx";
|
|
2958
3405
|
var DB_FILENAME4 = "index.db";
|
|
2959
3406
|
function timestamp() {
|
|
2960
3407
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
|
|
2961
3408
|
}
|
|
2962
3409
|
function detectLanguage(filePath) {
|
|
2963
|
-
const ext =
|
|
3410
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2964
3411
|
return LANGUAGE_MAP[ext] ?? null;
|
|
2965
3412
|
}
|
|
2966
3413
|
function formatDuration2(ms) {
|
|
@@ -2969,7 +3416,7 @@ function formatDuration2(ms) {
|
|
|
2969
3416
|
}
|
|
2970
3417
|
async function hashFile(absolutePath) {
|
|
2971
3418
|
const { createHash: createHash3 } = await import("crypto");
|
|
2972
|
-
const content =
|
|
3419
|
+
const content = fs9.readFileSync(absolutePath);
|
|
2973
3420
|
return createHash3("sha256").update(content).digest("hex");
|
|
2974
3421
|
}
|
|
2975
3422
|
async function reindexChanges(db, changes, projectPath, options) {
|
|
@@ -2979,7 +3426,7 @@ async function reindexChanges(db, changes, projectPath, options) {
|
|
|
2979
3426
|
let chunksUpdated = 0;
|
|
2980
3427
|
const allChunksWithMeta = [];
|
|
2981
3428
|
for (const change of changes) {
|
|
2982
|
-
const absolutePath =
|
|
3429
|
+
const absolutePath = path9.join(projectPath, change.path);
|
|
2983
3430
|
const language = detectLanguage(change.path);
|
|
2984
3431
|
if (change.type === "unlink") {
|
|
2985
3432
|
log(`[${timestamp()}] Deleted: ${change.path}`);
|
|
@@ -2991,7 +3438,7 @@ async function reindexChanges(db, changes, projectPath, options) {
|
|
|
2991
3438
|
continue;
|
|
2992
3439
|
}
|
|
2993
3440
|
if (!language) continue;
|
|
2994
|
-
if (!
|
|
3441
|
+
if (!fs9.existsSync(absolutePath)) continue;
|
|
2995
3442
|
const label = change.type === "add" ? "Added" : "Changed";
|
|
2996
3443
|
log(`[${timestamp()}] ${label}: ${change.path}`);
|
|
2997
3444
|
const existingFile = db.getFile(change.path);
|
|
@@ -3007,7 +3454,7 @@ async function reindexChanges(db, changes, projectPath, options) {
|
|
|
3007
3454
|
}
|
|
3008
3455
|
const chunks = chunkFile(nodes, change.path);
|
|
3009
3456
|
const hash = await hashFile(absolutePath);
|
|
3010
|
-
const size =
|
|
3457
|
+
const size = fs9.statSync(absolutePath).size;
|
|
3011
3458
|
const fileId = db.upsertFile({
|
|
3012
3459
|
path: change.path,
|
|
3013
3460
|
language,
|
|
@@ -3060,15 +3507,15 @@ async function loadEmbedder3() {
|
|
|
3060
3507
|
return embedderInstance3;
|
|
3061
3508
|
}
|
|
3062
3509
|
async function runWatch(projectPath, options = {}) {
|
|
3063
|
-
const absoluteRoot =
|
|
3064
|
-
const dbPath =
|
|
3510
|
+
const absoluteRoot = path9.resolve(projectPath);
|
|
3511
|
+
const dbPath = path9.join(absoluteRoot, CTX_DIR5, DB_FILENAME4);
|
|
3065
3512
|
const log = options.log ?? console.log;
|
|
3066
3513
|
if (options.init) {
|
|
3067
3514
|
await runInit(absoluteRoot, { log, skipEmbedding: options.skipEmbedding });
|
|
3068
3515
|
}
|
|
3069
|
-
if (!
|
|
3516
|
+
if (!fs9.existsSync(dbPath)) {
|
|
3070
3517
|
throw new KontextError(
|
|
3071
|
-
`Project not initialized. Run "ctx init" first or use --init flag. (${
|
|
3518
|
+
`Project not initialized. Run "ctx init" first or use --init flag. (${CTX_DIR5}/${DB_FILENAME4} not found)`,
|
|
3072
3519
|
ErrorCode.NOT_INITIALIZED
|
|
3073
3520
|
);
|
|
3074
3521
|
}
|
|
@@ -3149,11 +3596,11 @@ function registerWatchCommand(program2) {
|
|
|
3149
3596
|
}
|
|
3150
3597
|
|
|
3151
3598
|
// src/cli/commands/status.ts
|
|
3152
|
-
import
|
|
3153
|
-
import
|
|
3154
|
-
var
|
|
3599
|
+
import fs10 from "fs";
|
|
3600
|
+
import path10 from "path";
|
|
3601
|
+
var CTX_DIR6 = ".ctx";
|
|
3155
3602
|
var DB_FILENAME5 = "index.db";
|
|
3156
|
-
var
|
|
3603
|
+
var CONFIG_FILENAME3 = "config.json";
|
|
3157
3604
|
function formatBytes2(bytes) {
|
|
3158
3605
|
if (bytes < 1024) return `${bytes} B`;
|
|
3159
3606
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -3168,15 +3615,16 @@ function formatTimestamp(raw) {
|
|
|
3168
3615
|
function capitalize(s) {
|
|
3169
3616
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3170
3617
|
}
|
|
3171
|
-
function
|
|
3172
|
-
const configPath2 =
|
|
3173
|
-
if (!
|
|
3618
|
+
function readConfig2(ctxDir) {
|
|
3619
|
+
const configPath2 = path10.join(ctxDir, CONFIG_FILENAME3);
|
|
3620
|
+
if (!fs10.existsSync(configPath2)) return null;
|
|
3174
3621
|
try {
|
|
3175
|
-
const raw =
|
|
3622
|
+
const raw = fs10.readFileSync(configPath2, "utf-8");
|
|
3176
3623
|
const parsed = JSON.parse(raw);
|
|
3624
|
+
const embedder = parsed.embedder;
|
|
3177
3625
|
return {
|
|
3178
|
-
model: parsed.model ?? "unknown",
|
|
3179
|
-
dimensions: parsed.dimensions ?? 0
|
|
3626
|
+
model: embedder?.model ?? parsed.model ?? "unknown",
|
|
3627
|
+
dimensions: embedder?.dimensions ?? parsed.dimensions ?? 0
|
|
3180
3628
|
};
|
|
3181
3629
|
} catch {
|
|
3182
3630
|
return null;
|
|
@@ -3195,7 +3643,7 @@ function formatStatus(projectPath, output) {
|
|
|
3195
3643
|
`Kontext Status \u2014 ${projectPath}`,
|
|
3196
3644
|
"",
|
|
3197
3645
|
` Initialized: Yes`,
|
|
3198
|
-
` Database: ${
|
|
3646
|
+
` Database: ${CTX_DIR6}/${DB_FILENAME5} (${formatBytes2(output.dbSizeBytes)})`
|
|
3199
3647
|
];
|
|
3200
3648
|
if (output.lastIndexed) {
|
|
3201
3649
|
lines.push(` Last indexed: ${formatTimestamp(output.lastIndexed)}`);
|
|
@@ -3225,10 +3673,10 @@ function formatStatus(projectPath, output) {
|
|
|
3225
3673
|
return lines.join("\n");
|
|
3226
3674
|
}
|
|
3227
3675
|
async function runStatus(projectPath) {
|
|
3228
|
-
const absoluteRoot =
|
|
3229
|
-
const ctxDir =
|
|
3230
|
-
const dbPath =
|
|
3231
|
-
if (!
|
|
3676
|
+
const absoluteRoot = path10.resolve(projectPath);
|
|
3677
|
+
const ctxDir = path10.join(absoluteRoot, CTX_DIR6);
|
|
3678
|
+
const dbPath = path10.join(ctxDir, DB_FILENAME5);
|
|
3679
|
+
if (!fs10.existsSync(dbPath)) {
|
|
3232
3680
|
const output = {
|
|
3233
3681
|
initialized: false,
|
|
3234
3682
|
fileCount: 0,
|
|
@@ -3249,8 +3697,8 @@ async function runStatus(projectPath) {
|
|
|
3249
3697
|
const vectorCount = db.getVectorCount();
|
|
3250
3698
|
const languages = db.getLanguageBreakdown();
|
|
3251
3699
|
const lastIndexed = db.getLastIndexed();
|
|
3252
|
-
const config =
|
|
3253
|
-
const dbSizeBytes =
|
|
3700
|
+
const config = readConfig2(ctxDir);
|
|
3701
|
+
const dbSizeBytes = fs10.statSync(dbPath).size;
|
|
3254
3702
|
const output = {
|
|
3255
3703
|
initialized: true,
|
|
3256
3704
|
fileCount,
|
|
@@ -3303,205 +3751,6 @@ function registerChunkCommand(program2) {
|
|
|
3303
3751
|
});
|
|
3304
3752
|
}
|
|
3305
3753
|
|
|
3306
|
-
// src/cli/commands/config.ts
|
|
3307
|
-
import fs10 from "fs";
|
|
3308
|
-
import path10 from "path";
|
|
3309
|
-
var CTX_DIR6 = ".ctx";
|
|
3310
|
-
var CONFIG_FILENAME3 = "config.json";
|
|
3311
|
-
var DEFAULT_CONFIG = {
|
|
3312
|
-
embedder: {
|
|
3313
|
-
provider: "local",
|
|
3314
|
-
model: "Xenova/all-MiniLM-L6-v2",
|
|
3315
|
-
dimensions: 384
|
|
3316
|
-
},
|
|
3317
|
-
search: {
|
|
3318
|
-
defaultLimit: 10,
|
|
3319
|
-
strategies: ["vector", "fts", "ast", "path"],
|
|
3320
|
-
weights: { vector: 1, fts: 0.8, ast: 0.9, path: 0.7, dependency: 0.6 }
|
|
3321
|
-
},
|
|
3322
|
-
watch: {
|
|
3323
|
-
debounceMs: 500,
|
|
3324
|
-
ignored: []
|
|
3325
|
-
},
|
|
3326
|
-
llm: {
|
|
3327
|
-
provider: null,
|
|
3328
|
-
model: null
|
|
3329
|
-
}
|
|
3330
|
-
};
|
|
3331
|
-
var VALID_EMBEDDER_PROVIDERS = /* @__PURE__ */ new Set(["local", "voyage", "openai"]);
|
|
3332
|
-
var VALID_LLM_PROVIDERS = /* @__PURE__ */ new Set(["gemini", "openai", "anthropic"]);
|
|
3333
|
-
var VALIDATION_RULES = {
|
|
3334
|
-
"embedder.provider": {
|
|
3335
|
-
validate: (v) => typeof v === "string" && VALID_EMBEDDER_PROVIDERS.has(v),
|
|
3336
|
-
message: `Must be one of: ${[...VALID_EMBEDDER_PROVIDERS].join(", ")}`
|
|
3337
|
-
},
|
|
3338
|
-
"embedder.dimensions": {
|
|
3339
|
-
validate: (v) => typeof v === "number" && v > 0 && Number.isInteger(v),
|
|
3340
|
-
message: "Must be a positive integer"
|
|
3341
|
-
},
|
|
3342
|
-
"search.defaultLimit": {
|
|
3343
|
-
validate: (v) => typeof v === "number" && v > 0 && Number.isInteger(v),
|
|
3344
|
-
message: "Must be a positive integer"
|
|
3345
|
-
},
|
|
3346
|
-
"watch.debounceMs": {
|
|
3347
|
-
validate: (v) => typeof v === "number" && v >= 0 && Number.isInteger(v),
|
|
3348
|
-
message: "Must be a non-negative integer"
|
|
3349
|
-
},
|
|
3350
|
-
"llm.provider": {
|
|
3351
|
-
validate: (v) => v === null || typeof v === "string" && VALID_LLM_PROVIDERS.has(v),
|
|
3352
|
-
message: `Must be null or one of: ${[...VALID_LLM_PROVIDERS].join(", ")}`
|
|
3353
|
-
}
|
|
3354
|
-
};
|
|
3355
|
-
function resolveCtxDir(projectPath) {
|
|
3356
|
-
const absoluteRoot = path10.resolve(projectPath);
|
|
3357
|
-
const ctxDir = path10.join(absoluteRoot, CTX_DIR6);
|
|
3358
|
-
if (!fs10.existsSync(ctxDir)) {
|
|
3359
|
-
throw new ConfigError(
|
|
3360
|
-
`Project not initialized. Run "ctx init" first. (${CTX_DIR6}/ not found)`,
|
|
3361
|
-
ErrorCode.NOT_INITIALIZED
|
|
3362
|
-
);
|
|
3363
|
-
}
|
|
3364
|
-
return ctxDir;
|
|
3365
|
-
}
|
|
3366
|
-
function configPath(ctxDir) {
|
|
3367
|
-
return path10.join(ctxDir, CONFIG_FILENAME3);
|
|
3368
|
-
}
|
|
3369
|
-
function readConfig2(ctxDir) {
|
|
3370
|
-
const filePath = configPath(ctxDir);
|
|
3371
|
-
if (!fs10.existsSync(filePath)) {
|
|
3372
|
-
writeConfig(ctxDir, DEFAULT_CONFIG);
|
|
3373
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
3374
|
-
}
|
|
3375
|
-
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
3376
|
-
const parsed = JSON.parse(raw);
|
|
3377
|
-
return mergeWithDefaults(parsed);
|
|
3378
|
-
}
|
|
3379
|
-
function writeConfig(ctxDir, config) {
|
|
3380
|
-
fs10.writeFileSync(
|
|
3381
|
-
configPath(ctxDir),
|
|
3382
|
-
JSON.stringify(config, null, 2) + "\n"
|
|
3383
|
-
);
|
|
3384
|
-
}
|
|
3385
|
-
function mergeWithDefaults(partial) {
|
|
3386
|
-
return {
|
|
3387
|
-
embedder: { ...DEFAULT_CONFIG.embedder, ...partial.embedder },
|
|
3388
|
-
search: {
|
|
3389
|
-
...DEFAULT_CONFIG.search,
|
|
3390
|
-
...partial.search,
|
|
3391
|
-
weights: { ...DEFAULT_CONFIG.search.weights, ...partial.search?.weights }
|
|
3392
|
-
},
|
|
3393
|
-
watch: { ...DEFAULT_CONFIG.watch, ...partial.watch },
|
|
3394
|
-
llm: { ...DEFAULT_CONFIG.llm, ...partial.llm }
|
|
3395
|
-
};
|
|
3396
|
-
}
|
|
3397
|
-
function getNestedValue(obj, key) {
|
|
3398
|
-
const parts = key.split(".");
|
|
3399
|
-
let current = obj;
|
|
3400
|
-
for (const part of parts) {
|
|
3401
|
-
if (current === null || current === void 0 || typeof current !== "object") {
|
|
3402
|
-
return void 0;
|
|
3403
|
-
}
|
|
3404
|
-
current = current[part];
|
|
3405
|
-
}
|
|
3406
|
-
return current;
|
|
3407
|
-
}
|
|
3408
|
-
function setNestedValue(obj, key, value) {
|
|
3409
|
-
const parts = key.split(".");
|
|
3410
|
-
let current = obj;
|
|
3411
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
3412
|
-
const part = parts[i];
|
|
3413
|
-
if (typeof current[part] !== "object" || current[part] === null) {
|
|
3414
|
-
current[part] = {};
|
|
3415
|
-
}
|
|
3416
|
-
current = current[part];
|
|
3417
|
-
}
|
|
3418
|
-
current[parts[parts.length - 1]] = value;
|
|
3419
|
-
}
|
|
3420
|
-
function parseValue(rawValue) {
|
|
3421
|
-
if (rawValue === "null") return null;
|
|
3422
|
-
if (rawValue === "true") return true;
|
|
3423
|
-
if (rawValue === "false") return false;
|
|
3424
|
-
const num = Number(rawValue);
|
|
3425
|
-
if (!Number.isNaN(num) && rawValue.trim() !== "") return num;
|
|
3426
|
-
if (rawValue.startsWith("[") || rawValue.startsWith("{")) {
|
|
3427
|
-
try {
|
|
3428
|
-
return JSON.parse(rawValue);
|
|
3429
|
-
} catch {
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3432
|
-
return rawValue;
|
|
3433
|
-
}
|
|
3434
|
-
function runConfigShow(projectPath) {
|
|
3435
|
-
const ctxDir = resolveCtxDir(projectPath);
|
|
3436
|
-
const config = readConfig2(ctxDir);
|
|
3437
|
-
return {
|
|
3438
|
-
config,
|
|
3439
|
-
text: JSON.stringify(config, null, 2)
|
|
3440
|
-
};
|
|
3441
|
-
}
|
|
3442
|
-
function runConfigGet(projectPath, key) {
|
|
3443
|
-
const ctxDir = resolveCtxDir(projectPath);
|
|
3444
|
-
const config = readConfig2(ctxDir);
|
|
3445
|
-
return getNestedValue(config, key);
|
|
3446
|
-
}
|
|
3447
|
-
function runConfigSet(projectPath, key, rawValue) {
|
|
3448
|
-
const ctxDir = resolveCtxDir(projectPath);
|
|
3449
|
-
const config = readConfig2(ctxDir);
|
|
3450
|
-
const value = parseValue(rawValue);
|
|
3451
|
-
const rule = VALIDATION_RULES[key];
|
|
3452
|
-
if (rule && !rule.validate(value)) {
|
|
3453
|
-
throw new ConfigError(`Invalid value for "${key}": ${rule.message}`, ErrorCode.CONFIG_INVALID);
|
|
3454
|
-
}
|
|
3455
|
-
setNestedValue(config, key, value);
|
|
3456
|
-
writeConfig(ctxDir, config);
|
|
3457
|
-
}
|
|
3458
|
-
function runConfigReset(projectPath) {
|
|
3459
|
-
const ctxDir = resolveCtxDir(projectPath);
|
|
3460
|
-
writeConfig(ctxDir, structuredClone(DEFAULT_CONFIG));
|
|
3461
|
-
}
|
|
3462
|
-
function registerConfigCommand(program2) {
|
|
3463
|
-
const cmd = program2.command("config").description("Show or modify configuration");
|
|
3464
|
-
function configErrorHandler(err) {
|
|
3465
|
-
const verbose = program2.opts()["verbose"] === true;
|
|
3466
|
-
const logger = createLogger({ level: verbose ? LogLevel.DEBUG : LogLevel.INFO });
|
|
3467
|
-
process.exitCode = handleCommandError(err, logger, verbose);
|
|
3468
|
-
}
|
|
3469
|
-
cmd.command("show").description("Show current configuration").action(() => {
|
|
3470
|
-
try {
|
|
3471
|
-
const output = runConfigShow(process.cwd());
|
|
3472
|
-
console.log(output.text);
|
|
3473
|
-
} catch (err) {
|
|
3474
|
-
configErrorHandler(err);
|
|
3475
|
-
}
|
|
3476
|
-
});
|
|
3477
|
-
cmd.command("get <key>").description("Get a configuration value (dot notation)").action((key) => {
|
|
3478
|
-
try {
|
|
3479
|
-
const value = runConfigGet(process.cwd(), key);
|
|
3480
|
-
console.log(
|
|
3481
|
-
typeof value === "object" ? JSON.stringify(value, null, 2) : String(value)
|
|
3482
|
-
);
|
|
3483
|
-
} catch (err) {
|
|
3484
|
-
configErrorHandler(err);
|
|
3485
|
-
}
|
|
3486
|
-
});
|
|
3487
|
-
cmd.command("set <key> <value>").description("Set a configuration value (dot notation)").action((key, value) => {
|
|
3488
|
-
try {
|
|
3489
|
-
runConfigSet(process.cwd(), key, value);
|
|
3490
|
-
console.log(`Set ${key} = ${value}`);
|
|
3491
|
-
} catch (err) {
|
|
3492
|
-
configErrorHandler(err);
|
|
3493
|
-
}
|
|
3494
|
-
});
|
|
3495
|
-
cmd.command("reset").description("Reset configuration to defaults").action(() => {
|
|
3496
|
-
try {
|
|
3497
|
-
runConfigReset(process.cwd());
|
|
3498
|
-
console.log("Configuration reset to defaults.");
|
|
3499
|
-
} catch (err) {
|
|
3500
|
-
configErrorHandler(err);
|
|
3501
|
-
}
|
|
3502
|
-
});
|
|
3503
|
-
}
|
|
3504
|
-
|
|
3505
3754
|
// src/cli/commands/auth.ts
|
|
3506
3755
|
function registerAuthCommand(program2) {
|
|
3507
3756
|
program2.command("auth").description("Set API keys for LLM and embedding providers").action(() => {
|
|
@@ -3510,12 +3759,13 @@ function registerAuthCommand(program2) {
|
|
|
3510
3759
|
}
|
|
3511
3760
|
|
|
3512
3761
|
// src/cli/index.ts
|
|
3762
|
+
var require3 = createRequire2(import.meta.url);
|
|
3763
|
+
var packageJson = require3("../../package.json");
|
|
3513
3764
|
var program = new Command();
|
|
3514
|
-
program.name("ctx").description("Kontext \u2014 Context engine for AI coding agents").version("0.
|
|
3765
|
+
program.name("ctx").description("Kontext \u2014 Context engine for AI coding agents").version(packageJson.version ?? "0.0.0").option("--verbose", "Enable verbose/debug output");
|
|
3515
3766
|
registerInitCommand(program);
|
|
3516
3767
|
registerQueryCommand(program);
|
|
3517
3768
|
registerAskCommand(program);
|
|
3518
|
-
registerFindCommand(program);
|
|
3519
3769
|
registerUpdateCommand(program);
|
|
3520
3770
|
registerWatchCommand(program);
|
|
3521
3771
|
registerStatusCommand(program);
|