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 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 fused = fusionMerge(strategyResults, options.limit);
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 (!isPathLike(query)) return [];
1851
- return pathSearch(db, query, limit);
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),