kontext-engine 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +127 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +125 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1687,6 +1687,57 @@ function pathSearch(db, pattern, limit) {
|
|
|
1687
1687
|
}
|
|
1688
1688
|
return results;
|
|
1689
1689
|
}
|
|
1690
|
+
var SCORE_DIR_EXACT = 1;
|
|
1691
|
+
var SCORE_FILENAME = 0.9;
|
|
1692
|
+
var SCORE_PARTIAL = 0.7;
|
|
1693
|
+
function pathKeywordSearch(db, query, limit) {
|
|
1694
|
+
const queryLower = query.toLowerCase();
|
|
1695
|
+
const allPaths = db.getAllFilePaths();
|
|
1696
|
+
const scoredPaths = [];
|
|
1697
|
+
for (const filePath of allPaths) {
|
|
1698
|
+
const score = scorePathMatch(filePath, queryLower);
|
|
1699
|
+
if (score > 0) {
|
|
1700
|
+
scoredPaths.push({ filePath, score });
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (scoredPaths.length === 0) return [];
|
|
1704
|
+
scoredPaths.sort((a, b) => b.score - a.score);
|
|
1705
|
+
const results = [];
|
|
1706
|
+
for (const { filePath, score } of scoredPaths) {
|
|
1707
|
+
if (results.length >= limit) break;
|
|
1708
|
+
const file = db.getFile(filePath);
|
|
1709
|
+
if (!file) continue;
|
|
1710
|
+
const chunks = db.getChunksByFile(file.id);
|
|
1711
|
+
for (const chunk of chunks) {
|
|
1712
|
+
if (results.length >= limit) break;
|
|
1713
|
+
results.push({
|
|
1714
|
+
chunkId: chunk.id,
|
|
1715
|
+
filePath: file.path,
|
|
1716
|
+
lineStart: chunk.lineStart,
|
|
1717
|
+
lineEnd: chunk.lineEnd,
|
|
1718
|
+
name: chunk.name,
|
|
1719
|
+
type: chunk.type,
|
|
1720
|
+
text: chunk.text,
|
|
1721
|
+
score,
|
|
1722
|
+
language: file.language
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return results;
|
|
1727
|
+
}
|
|
1728
|
+
function scorePathMatch(filePath, queryLower) {
|
|
1729
|
+
const pathLower = filePath.toLowerCase();
|
|
1730
|
+
const segments = pathLower.split("/");
|
|
1731
|
+
const dirSegments = segments.slice(0, -1);
|
|
1732
|
+
for (const seg of dirSegments) {
|
|
1733
|
+
if (seg === queryLower) return SCORE_DIR_EXACT;
|
|
1734
|
+
}
|
|
1735
|
+
const fileName = segments[segments.length - 1];
|
|
1736
|
+
const fileNameNoExt = fileName.replace(/\.[^.]+$/, "");
|
|
1737
|
+
if (fileNameNoExt === queryLower) return SCORE_FILENAME;
|
|
1738
|
+
if (pathLower.includes(queryLower)) return SCORE_PARTIAL;
|
|
1739
|
+
return 0;
|
|
1740
|
+
}
|
|
1690
1741
|
|
|
1691
1742
|
// src/search/fusion.ts
|
|
1692
1743
|
var K = 60;
|
|
@@ -1718,6 +1769,73 @@ function fusionMerge(strategyResults, limit) {
|
|
|
1718
1769
|
}
|
|
1719
1770
|
return results;
|
|
1720
1771
|
}
|
|
1772
|
+
var PATH_BOOST_DIR_EXACT = 1.5;
|
|
1773
|
+
var PATH_BOOST_FILENAME = 1.4;
|
|
1774
|
+
var PATH_BOOST_PARTIAL = 1.2;
|
|
1775
|
+
var IMPORT_PENALTY = 0.5;
|
|
1776
|
+
function fusionMergeWithPathBoost(strategyResults, limit, pathBoostTerms) {
|
|
1777
|
+
const fused = fusionMerge(strategyResults, limit * 3);
|
|
1778
|
+
if (fused.length === 0) return [];
|
|
1779
|
+
const boosted = applyPathBoost(fused, pathBoostTerms);
|
|
1780
|
+
const adjusted = applyImportDeprioritization(boosted);
|
|
1781
|
+
adjusted.sort((a, b) => b.score - a.score);
|
|
1782
|
+
const sliced = adjusted.slice(0, limit);
|
|
1783
|
+
return renormalize(sliced);
|
|
1784
|
+
}
|
|
1785
|
+
function applyPathBoost(results, terms) {
|
|
1786
|
+
if (terms.length === 0) return results;
|
|
1787
|
+
return results.map((r) => {
|
|
1788
|
+
const boost = getPathBoostFactor(r.filePath, terms);
|
|
1789
|
+
return { ...r, score: r.score * boost };
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
function getPathBoostFactor(filePath, terms) {
|
|
1793
|
+
let maxBoost = 1;
|
|
1794
|
+
const pathLower = filePath.toLowerCase();
|
|
1795
|
+
const segments = pathLower.split("/");
|
|
1796
|
+
const dirSegments = segments.slice(0, -1);
|
|
1797
|
+
const fileName = segments[segments.length - 1];
|
|
1798
|
+
const fileNameNoExt = fileName.replace(/\.[^.]+$/, "");
|
|
1799
|
+
for (const term of terms) {
|
|
1800
|
+
const termLower = term.toLowerCase();
|
|
1801
|
+
for (const seg of dirSegments) {
|
|
1802
|
+
if (seg === termLower) {
|
|
1803
|
+
maxBoost = Math.max(maxBoost, PATH_BOOST_DIR_EXACT);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
if (fileNameNoExt === termLower) {
|
|
1807
|
+
maxBoost = Math.max(maxBoost, PATH_BOOST_FILENAME);
|
|
1808
|
+
}
|
|
1809
|
+
if (maxBoost < PATH_BOOST_PARTIAL && pathLower.includes(termLower)) {
|
|
1810
|
+
maxBoost = Math.max(maxBoost, PATH_BOOST_PARTIAL);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return maxBoost;
|
|
1814
|
+
}
|
|
1815
|
+
function applyImportDeprioritization(results) {
|
|
1816
|
+
const hasNonImport = results.some((r) => r.type !== "import");
|
|
1817
|
+
if (!hasNonImport) return results;
|
|
1818
|
+
const maxNonImportScore = Math.max(
|
|
1819
|
+
...results.filter((r) => r.type !== "import").map((r) => r.score),
|
|
1820
|
+
0
|
|
1821
|
+
);
|
|
1822
|
+
if (maxNonImportScore === 0) return results;
|
|
1823
|
+
return results.map((r) => {
|
|
1824
|
+
if (r.type === "import") {
|
|
1825
|
+
return { ...r, score: r.score * IMPORT_PENALTY };
|
|
1826
|
+
}
|
|
1827
|
+
return r;
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
function renormalize(results) {
|
|
1831
|
+
if (results.length === 0) return results;
|
|
1832
|
+
const maxScore = Math.max(...results.map((r) => r.score));
|
|
1833
|
+
if (maxScore === 0) return results;
|
|
1834
|
+
return results.map((r) => ({
|
|
1835
|
+
...r,
|
|
1836
|
+
score: r.score / maxScore
|
|
1837
|
+
}));
|
|
1838
|
+
}
|
|
1721
1839
|
|
|
1722
1840
|
// src/cli/commands/query.ts
|
|
1723
1841
|
var CTX_DIR2 = ".ctx";
|
|
@@ -1769,6 +1887,9 @@ function extractSymbolNames(query) {
|
|
|
1769
1887
|
function isPathLike(query) {
|
|
1770
1888
|
return query.includes("/") || query.includes("*") || query.includes(".");
|
|
1771
1889
|
}
|
|
1890
|
+
function extractPathBoostTerms(query) {
|
|
1891
|
+
return query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
|
|
1892
|
+
}
|
|
1772
1893
|
async function runQuery(projectPath, query, options) {
|
|
1773
1894
|
const absoluteRoot = path5.resolve(projectPath);
|
|
1774
1895
|
const dbPath = path5.join(absoluteRoot, CTX_DIR2, DB_FILENAME2);
|
|
@@ -1782,7 +1903,8 @@ async function runQuery(projectPath, query, options) {
|
|
|
1782
1903
|
const db = createDatabase(dbPath);
|
|
1783
1904
|
try {
|
|
1784
1905
|
const strategyResults = await runStrategies(db, query, options);
|
|
1785
|
-
const
|
|
1906
|
+
const pathBoostTerms = extractPathBoostTerms(query);
|
|
1907
|
+
const fused = fusionMergeWithPathBoost(strategyResults, options.limit, pathBoostTerms);
|
|
1786
1908
|
const outputResults = fused.map(toOutputResult);
|
|
1787
1909
|
const searchTimeMs = Math.round(performance.now() - start);
|
|
1788
1910
|
const text = options.format === "text" ? formatTextOutput(query, outputResults) : void 0;
|
|
@@ -1847,8 +1969,8 @@ async function executeStrategy(db, strategy, query, limit, filters) {
|
|
|
1847
1969
|
});
|
|
1848
1970
|
}
|
|
1849
1971
|
case "path": {
|
|
1850
|
-
if (
|
|
1851
|
-
return
|
|
1972
|
+
if (isPathLike(query)) return pathSearch(db, query, limit);
|
|
1973
|
+
return pathKeywordSearch(db, query, limit);
|
|
1852
1974
|
}
|
|
1853
1975
|
case "dependency":
|
|
1854
1976
|
return [];
|
|
@@ -1864,12 +1986,12 @@ function registerQueryCommand(program2) {
|
|
|
1864
1986
|
program2.command("query <query>").description("Multi-strategy code search").option("-l, --limit <n>", "Max results", "10").option(
|
|
1865
1987
|
"-s, --strategy <list>",
|
|
1866
1988
|
"Comma-separated strategies: vector,fts,ast,path",
|
|
1867
|
-
"fts,ast"
|
|
1989
|
+
"fts,ast,path"
|
|
1868
1990
|
).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) => {
|
|
1869
1991
|
const projectPath = process.cwd();
|
|
1870
1992
|
const verbose = program2.opts()["verbose"] === true;
|
|
1871
1993
|
const logger = createLogger({ level: verbose ? LogLevel.DEBUG : LogLevel.INFO });
|
|
1872
|
-
const strategies = (opts["strategy"] ?? "fts,ast").split(",").map((s) => s.trim());
|
|
1994
|
+
const strategies = (opts["strategy"] ?? "fts,ast,path").split(",").map((s) => s.trim());
|
|
1873
1995
|
try {
|
|
1874
1996
|
const output = await runQuery(projectPath, query, {
|
|
1875
1997
|
limit: parseInt(opts["limit"] ?? "10", 10),
|