@jefuriiij/synthra 0.1.20 → 0.1.22

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":
@@ -2726,15 +2780,33 @@ Reason: ${retrieval.reason}
2726
2780
  return textContent(`${header}
2727
2781
  ${packed.text}`);
2728
2782
  }
2783
+ function resolveFileTarget(graph, filePath) {
2784
+ const files = graph.nodes.filter((n) => n.kind === "file");
2785
+ const exact = files.find((n) => n.path === filePath);
2786
+ if (exact) return { node: exact };
2787
+ const suffix = "/" + filePath;
2788
+ const matches = files.filter((n) => n.path.endsWith(suffix));
2789
+ if (matches.length === 1) return { node: matches[0] };
2790
+ if (matches.length > 1) return { ambiguous: matches.map((n) => n.path) };
2791
+ return { none: true };
2792
+ }
2729
2793
  function graphRead(args, ctx) {
2730
2794
  const target = typeof args?.target === "string" ? args.target : "";
2731
2795
  if (!target) return errorContent("graph_read: 'target' (string) is required");
2732
2796
  const [rawFile, symbolName] = target.includes("::") ? target.split("::", 2) : [target, void 0];
2733
2797
  const filePath = (rawFile ?? "").trim();
2734
- const fileNode = ctx.graph.nodes.find(
2735
- (n) => n.kind === "file" && n.path === filePath
2736
- );
2737
- if (!fileNode) return errorContent(`graph_read: file not found in graph: ${filePath}`);
2798
+ const resolved = resolveFileTarget(ctx.graph, filePath);
2799
+ if ("ambiguous" in resolved) {
2800
+ const shown = resolved.ambiguous.slice(0, 5).join(", ");
2801
+ const more = resolved.ambiguous.length > 5 ? ", \u2026" : "";
2802
+ return errorContent(
2803
+ `graph_read: '${filePath}' matches multiple files (${shown}${more}). Pass a longer path.`
2804
+ );
2805
+ }
2806
+ if ("none" in resolved) {
2807
+ return errorContent(`graph_read: file not found in graph: ${filePath}`);
2808
+ }
2809
+ const fileNode = resolved.node;
2738
2810
  if (!symbolName) {
2739
2811
  return textContent(`# ${fileNode.path}
2740
2812
 
@@ -2742,10 +2814,10 @@ ${fileNode.content}`);
2742
2814
  }
2743
2815
  const cleanSym = symbolName.trim();
2744
2816
  const symbol = ctx.graph.nodes.find(
2745
- (n) => n.kind === "symbol" && n.file === filePath && n.name === cleanSym
2817
+ (n) => n.kind === "symbol" && n.file === fileNode.path && n.name === cleanSym
2746
2818
  );
2747
2819
  if (!symbol) {
2748
- return errorContent(`graph_read: symbol '${cleanSym}' not found in ${filePath}`);
2820
+ return errorContent(`graph_read: symbol '${cleanSym}' not found in ${fileNode.path}`);
2749
2821
  }
2750
2822
  const lines = fileNode.content.split(/\r?\n/);
2751
2823
  const body = lines.slice(symbol.start_line - 1, symbol.end_line).join("\n");
@@ -2930,16 +3002,32 @@ function looksLikeNonSymbolQuery(pattern) {
2930
3002
  }
2931
3003
  return false;
2932
3004
  }
2933
- function recentlyTouchedMatchesQuery(recentPaths, queryTokens) {
3005
+ function recentlyTouchedMatchesQuery(recentPaths, queryTokens, graph) {
3006
+ if (recentPaths.length === 0) return [];
3007
+ const recent = new Set(recentPaths);
3008
+ const keywordsByPath = /* @__PURE__ */ new Map();
3009
+ for (const n of graph.nodes) {
3010
+ if (n.kind === "file" && recent.has(n.path)) keywordsByPath.set(n.path, n.keywords);
3011
+ }
2934
3012
  const matches = [];
2935
3013
  for (const path of recentPaths) {
2936
3014
  const lower = path.toLowerCase();
3015
+ let matched = false;
2937
3016
  for (const t of queryTokens) {
2938
3017
  if (lower.includes(t)) {
2939
- matches.push(path);
3018
+ matched = true;
2940
3019
  break;
2941
3020
  }
2942
3021
  }
3022
+ if (!matched) {
3023
+ for (const kw of keywordsByPath.get(path) ?? []) {
3024
+ if (queryTokens.has(kw)) {
3025
+ matched = true;
3026
+ break;
3027
+ }
3028
+ }
3029
+ }
3030
+ if (matched) matches.push(path);
2943
3031
  }
2944
3032
  return matches;
2945
3033
  }
@@ -2990,7 +3078,7 @@ async function handleGate(req, ctx) {
2990
3078
  }
2991
3079
  const qTokens = new Set(tokenizeQuery(query));
2992
3080
  const recentPaths = ctx.activity.recentFilePaths(RECENT_ACTIVITY_WINDOW_MS);
2993
- const overlap = recentlyTouchedMatchesQuery(recentPaths, qTokens);
3081
+ const overlap = recentlyTouchedMatchesQuery(recentPaths, qTokens, ctx.graph);
2994
3082
  if (overlap.length > 0) {
2995
3083
  const res2 = {
2996
3084
  decision: "allow",