@jefuriiij/synthra 0.1.19 → 0.1.21

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.
@@ -485,7 +485,7 @@ function extractKeywords(content, _ext) {
485
485
  }
486
486
 
487
487
  // src/scanner/extract.ts
488
- var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".svelte", ".vue", ".dart"];
488
+ var RESOLVE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".svelte", ".vue", ".dart", ".html", ".hubl"];
489
489
  var INDEX_FILES = ["index.ts", "index.tsx", "index.js", "index.jsx", "__init__.py"];
490
490
  function fileId(relPath) {
491
491
  return `file:${relPath}`;
@@ -889,6 +889,57 @@ async function parseGo(f, source) {
889
889
  );
890
890
  }
891
891
 
892
+ // src/scanner/parsers/hubl.ts
893
+ var MACRO_RE = /\{%-?\s*macro\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/g;
894
+ var ENDMACRO_RE = /\{%-?\s*endmacro\b/g;
895
+ var BLOCK_RE = /\{%-?\s*block\s+([A-Za-z_]\w*)/g;
896
+ var ENDBLOCK_RE = /\{%-?\s*endblock\b/g;
897
+ var IMPORT_RE = /\{%-?\s*(?:include|extends|import|from)\s+["']([^"']+)["']/g;
898
+ function lineAt(source, index) {
899
+ return source.slice(0, index).split(/\r?\n/).length;
900
+ }
901
+ function endLineAfter(source, fromIndex, endRe, startLine) {
902
+ endRe.lastIndex = fromIndex;
903
+ const m = endRe.exec(source);
904
+ return m ? lineAt(source, m.index) : startLine;
905
+ }
906
+ function parseHubL(f, source) {
907
+ const symbols = [];
908
+ const imports = [];
909
+ for (const m of source.matchAll(MACRO_RE)) {
910
+ const name = m[1];
911
+ if (!name) continue;
912
+ const args = (m[2] ?? "").trim();
913
+ const start = m.index ?? 0;
914
+ const startLine = lineAt(source, start);
915
+ symbols.push({
916
+ name,
917
+ kind: "function",
918
+ startLine,
919
+ endLine: endLineAfter(source, start + m[0].length, ENDMACRO_RE, startLine),
920
+ signature: `macro ${name}(${args})`
921
+ });
922
+ }
923
+ for (const m of source.matchAll(BLOCK_RE)) {
924
+ const name = m[1];
925
+ if (!name) continue;
926
+ const start = m.index ?? 0;
927
+ const startLine = lineAt(source, start);
928
+ symbols.push({
929
+ name,
930
+ kind: "component",
931
+ startLine,
932
+ endLine: endLineAfter(source, start + m[0].length, ENDBLOCK_RE, startLine),
933
+ signature: `block ${name}`
934
+ });
935
+ }
936
+ for (const m of source.matchAll(IMPORT_RE)) {
937
+ const spec = m[1];
938
+ if (spec) imports.push(spec);
939
+ }
940
+ return { file: f, source, symbols, imports: Array.from(new Set(imports)), calls: [] };
941
+ }
942
+
892
943
  // src/scanner/parsers/java.ts
893
944
  var QUERY6 = `
894
945
  (class_declaration name: (identifier) @class.name) @class
@@ -1327,6 +1378,9 @@ async function parseFile(f) {
1327
1378
  return parseSvelte(f, source);
1328
1379
  case ".vue":
1329
1380
  return parseVue(f, source);
1381
+ case ".html":
1382
+ case ".hubl":
1383
+ return parseHubL(f, source);
1330
1384
  case ".go":
1331
1385
  return parseGo(f, source);
1332
1386
  case ".rs":
@@ -1965,10 +2019,12 @@ function scoreFiles(inputs) {
1965
2019
  }
1966
2020
  const symbols = symbolsByFile.get(file.path) ?? [];
1967
2021
  let symHits = 0;
2022
+ let exactSym = 0;
1968
2023
  for (const sym of symbols) {
1969
2024
  const name = sym.name.toLowerCase();
1970
2025
  if (qTokens.has(name)) {
1971
2026
  symHits += 3;
2027
+ exactSym += 1;
1972
2028
  } else {
1973
2029
  for (const t of qTokens) {
1974
2030
  if (name.includes(t) || t.includes(name)) {
@@ -1993,7 +2049,7 @@ function scoreFiles(inputs) {
1993
2049
  score2 += 5;
1994
2050
  reasons.push("seed");
1995
2051
  }
1996
- scored.push({ file, score: score2, reasons });
2052
+ scored.push({ file, score: score2, reasons, symHits, exactSym });
1997
2053
  }
1998
2054
  const positivePaths = new Set(scored.filter((s) => s.score > 0).map((s) => s.file.path));
1999
2055
  if (positivePaths.size > 0) {
@@ -2028,7 +2084,8 @@ async function retrieve(graph, query, options = {}) {
2028
2084
  return {
2029
2085
  files: [],
2030
2086
  confidence: "low",
2031
- reason: qTokens.length === 0 ? "empty query" : "empty graph"
2087
+ reason: qTokens.length === 0 ? "empty query" : "empty graph",
2088
+ symbolMatched: false
2032
2089
  };
2033
2090
  }
2034
2091
  const rankInputs = {
@@ -2044,10 +2101,13 @@ async function retrieve(graph, query, options = {}) {
2044
2101
  return {
2045
2102
  files: [],
2046
2103
  confidence: "low",
2047
- reason: `no matches for ${JSON.stringify(qTokens)}`
2104
+ reason: `no matches for ${JSON.stringify(qTokens)}`,
2105
+ symbolMatched: false
2048
2106
  };
2049
2107
  }
2050
- const top = positive.slice(0, topK).map((s) => s.file);
2108
+ const topScored = positive.slice(0, topK);
2109
+ const top = topScored.map((s) => s.file);
2110
+ const symbolMatched = topScored.some((s) => s.exactSym > 0);
2051
2111
  const topScore = positive[0]?.score ?? 0;
2052
2112
  const secondScore = positive[1]?.score ?? 0;
2053
2113
  let confidence;
@@ -2059,7 +2119,8 @@ async function retrieve(graph, query, options = {}) {
2059
2119
  return {
2060
2120
  files: top,
2061
2121
  confidence,
2062
- reason: `top: ${reasons}`
2122
+ reason: `top: ${reasons}`,
2123
+ symbolMatched
2063
2124
  };
2064
2125
  }
2065
2126
 
@@ -2913,6 +2974,16 @@ function extractQuery(toolName, input) {
2913
2974
  }
2914
2975
  return null;
2915
2976
  }
2977
+ function looksLikeNonSymbolQuery(pattern) {
2978
+ if (/<\/?[a-zA-Z]/.test(pattern)) return true;
2979
+ if (/[a-zA-Z][\w-]*-[\w-]*\s*=/.test(pattern)) return true;
2980
+ if (/\{/.test(pattern)) return true;
2981
+ if (/\\\.[a-zA-Z]/.test(pattern)) return true;
2982
+ if (/:\s*\d/.test(pattern) || /\d(?:px|rem|em|vh|vw)\b/.test(pattern) || /\d%/.test(pattern)) {
2983
+ return true;
2984
+ }
2985
+ return false;
2986
+ }
2916
2987
  function recentlyTouchedMatchesQuery(recentPaths, queryTokens) {
2917
2988
  const matches = [];
2918
2989
  for (const path of recentPaths) {
@@ -2954,6 +3025,14 @@ async function handleGate(req, ctx) {
2954
3025
  await logDecision(ctx, req.tool_name, null, res2.decision, res2.reason);
2955
3026
  return res2;
2956
3027
  }
3028
+ if (req.tool_name === "Grep" && looksLikeNonSymbolQuery(query)) {
3029
+ const res2 = {
3030
+ decision: "allow",
3031
+ reason: `"${query}" targets markup/CSS/attributes, not code symbols \u2014 letting Grep through (the graph indexes symbols).`
3032
+ };
3033
+ await logDecision(ctx, req.tool_name, query, res2.decision, res2.reason);
3034
+ return res2;
3035
+ }
2957
3036
  const retrieval = await retrieve(ctx.graph, query);
2958
3037
  if (retrieval.confidence === "low") {
2959
3038
  const res2 = {
@@ -2974,6 +3053,14 @@ async function handleGate(req, ctx) {
2974
3053
  await logDecision(ctx, req.tool_name, query, res2.decision, res2.reason);
2975
3054
  return res2;
2976
3055
  }
3056
+ if (!retrieval.symbolMatched) {
3057
+ const res2 = {
3058
+ decision: "allow",
3059
+ reason: `confidence=${retrieval.confidence} but only keyword/path matched (no symbol the query names) \u2014 graph_read can't slice it, letting ${req.tool_name} through.`
3060
+ };
3061
+ await logDecision(ctx, req.tool_name, query, res2.decision, res2.reason);
3062
+ return res2;
3063
+ }
2977
3064
  const top = retrieval.files.slice(0, 3).map((f) => f.path).join(", ");
2978
3065
  const res = {
2979
3066
  decision: "block",