@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.
- package/CHANGELOG.md +51 -0
- package/dist/cli/index.js +94 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/index.js +1 -1
- package/dist/dashboard/index.js.map +1 -1
- package/dist/server/index.js +93 -6
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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",
|