@jefuriiij/synthra 0.1.19 → 0.1.20

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.
@@ -1965,10 +1965,12 @@ function scoreFiles(inputs) {
1965
1965
  }
1966
1966
  const symbols = symbolsByFile.get(file.path) ?? [];
1967
1967
  let symHits = 0;
1968
+ let exactSym = 0;
1968
1969
  for (const sym of symbols) {
1969
1970
  const name = sym.name.toLowerCase();
1970
1971
  if (qTokens.has(name)) {
1971
1972
  symHits += 3;
1973
+ exactSym += 1;
1972
1974
  } else {
1973
1975
  for (const t of qTokens) {
1974
1976
  if (name.includes(t) || t.includes(name)) {
@@ -1993,7 +1995,7 @@ function scoreFiles(inputs) {
1993
1995
  score2 += 5;
1994
1996
  reasons.push("seed");
1995
1997
  }
1996
- scored.push({ file, score: score2, reasons });
1998
+ scored.push({ file, score: score2, reasons, symHits, exactSym });
1997
1999
  }
1998
2000
  const positivePaths = new Set(scored.filter((s) => s.score > 0).map((s) => s.file.path));
1999
2001
  if (positivePaths.size > 0) {
@@ -2028,7 +2030,8 @@ async function retrieve(graph, query, options = {}) {
2028
2030
  return {
2029
2031
  files: [],
2030
2032
  confidence: "low",
2031
- reason: qTokens.length === 0 ? "empty query" : "empty graph"
2033
+ reason: qTokens.length === 0 ? "empty query" : "empty graph",
2034
+ symbolMatched: false
2032
2035
  };
2033
2036
  }
2034
2037
  const rankInputs = {
@@ -2044,10 +2047,13 @@ async function retrieve(graph, query, options = {}) {
2044
2047
  return {
2045
2048
  files: [],
2046
2049
  confidence: "low",
2047
- reason: `no matches for ${JSON.stringify(qTokens)}`
2050
+ reason: `no matches for ${JSON.stringify(qTokens)}`,
2051
+ symbolMatched: false
2048
2052
  };
2049
2053
  }
2050
- const top = positive.slice(0, topK).map((s) => s.file);
2054
+ const topScored = positive.slice(0, topK);
2055
+ const top = topScored.map((s) => s.file);
2056
+ const symbolMatched = topScored.some((s) => s.exactSym > 0);
2051
2057
  const topScore = positive[0]?.score ?? 0;
2052
2058
  const secondScore = positive[1]?.score ?? 0;
2053
2059
  let confidence;
@@ -2059,7 +2065,8 @@ async function retrieve(graph, query, options = {}) {
2059
2065
  return {
2060
2066
  files: top,
2061
2067
  confidence,
2062
- reason: `top: ${reasons}`
2068
+ reason: `top: ${reasons}`,
2069
+ symbolMatched
2063
2070
  };
2064
2071
  }
2065
2072
 
@@ -2913,6 +2920,16 @@ function extractQuery(toolName, input) {
2913
2920
  }
2914
2921
  return null;
2915
2922
  }
2923
+ function looksLikeNonSymbolQuery(pattern) {
2924
+ if (/<\/?[a-zA-Z]/.test(pattern)) return true;
2925
+ if (/[a-zA-Z][\w-]*-[\w-]*\s*=/.test(pattern)) return true;
2926
+ if (/\{/.test(pattern)) return true;
2927
+ if (/\\\.[a-zA-Z]/.test(pattern)) return true;
2928
+ if (/:\s*\d/.test(pattern) || /\d(?:px|rem|em|vh|vw)\b/.test(pattern) || /\d%/.test(pattern)) {
2929
+ return true;
2930
+ }
2931
+ return false;
2932
+ }
2916
2933
  function recentlyTouchedMatchesQuery(recentPaths, queryTokens) {
2917
2934
  const matches = [];
2918
2935
  for (const path of recentPaths) {
@@ -2954,6 +2971,14 @@ async function handleGate(req, ctx) {
2954
2971
  await logDecision(ctx, req.tool_name, null, res2.decision, res2.reason);
2955
2972
  return res2;
2956
2973
  }
2974
+ if (req.tool_name === "Grep" && looksLikeNonSymbolQuery(query)) {
2975
+ const res2 = {
2976
+ decision: "allow",
2977
+ reason: `"${query}" targets markup/CSS/attributes, not code symbols \u2014 letting Grep through (the graph indexes symbols).`
2978
+ };
2979
+ await logDecision(ctx, req.tool_name, query, res2.decision, res2.reason);
2980
+ return res2;
2981
+ }
2957
2982
  const retrieval = await retrieve(ctx.graph, query);
2958
2983
  if (retrieval.confidence === "low") {
2959
2984
  const res2 = {
@@ -2974,6 +2999,14 @@ async function handleGate(req, ctx) {
2974
2999
  await logDecision(ctx, req.tool_name, query, res2.decision, res2.reason);
2975
3000
  return res2;
2976
3001
  }
3002
+ if (!retrieval.symbolMatched) {
3003
+ const res2 = {
3004
+ decision: "allow",
3005
+ 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.`
3006
+ };
3007
+ await logDecision(ctx, req.tool_name, query, res2.decision, res2.reason);
3008
+ return res2;
3009
+ }
2977
3010
  const top = retrieval.files.slice(0, 3).map((f) => f.path).join(", ");
2978
3011
  const res = {
2979
3012
  decision: "block",