@jefuriiij/synthra 0.1.21 → 0.1.23

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.
@@ -273,6 +273,9 @@ import { resolve } from "path";
273
273
  // src/scanner/extract.ts
274
274
  import { dirname as dirname2, join as join3, posix } from "path";
275
275
 
276
+ // src/graph/types.ts
277
+ var SCHEMA_VERSION = 1;
278
+
276
279
  // src/scanner/hash.ts
277
280
  import { createHash } from "crypto";
278
281
  function fileHash(content) {
@@ -609,7 +612,7 @@ async function buildGraph(root, parsed) {
609
612
  nodes,
610
613
  edges,
611
614
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
612
- schema_version: 1
615
+ schema_version: SCHEMA_VERSION
613
616
  };
614
617
  }
615
618
  function buildSymbolIndex(graph) {
@@ -1143,6 +1146,7 @@ var TS_QUERY = `
1143
1146
  (enum_declaration name: (identifier) @enum.name) @enum
1144
1147
  (method_definition name: (property_identifier) @method.name) @method
1145
1148
  (lexical_declaration (variable_declarator name: (identifier) @const-fn.name value: [(arrow_function) (function_expression)])) @const-fn
1149
+ (assignment_expression left: (member_expression property: (property_identifier) @member-fn.name) right: [(arrow_function) (function_expression)]) @member-fn
1146
1150
  (import_statement source: (string) @import)
1147
1151
  `;
1148
1152
  var JS_QUERY = `
@@ -1150,6 +1154,7 @@ var JS_QUERY = `
1150
1154
  (class_declaration name: (identifier) @class.name) @class
1151
1155
  (method_definition name: (property_identifier) @method.name) @method
1152
1156
  (lexical_declaration (variable_declarator name: (identifier) @const-fn.name value: [(arrow_function) (function_expression)])) @const-fn
1157
+ (assignment_expression left: (member_expression property: (property_identifier) @member-fn.name) right: [(arrow_function) (function_expression)]) @member-fn
1153
1158
  (import_statement source: (string) @import)
1154
1159
  (call_expression function: (identifier) @_require_fn arguments: (arguments . (string) @require_source))
1155
1160
  `;
@@ -1174,7 +1179,7 @@ function shapeFromCaptures(captures) {
1174
1179
  const name = captures.get(`${k}.name`);
1175
1180
  return decl && name ? { decl, name, kind: sk } : null;
1176
1181
  };
1177
- return findDecl("function", "function") ?? findDecl("class", "class") ?? findDecl("interface", "interface") ?? findDecl("type", "type") ?? findDecl("enum", "enum") ?? findDecl("method", "method") ?? findDecl("const-fn", "function");
1182
+ return findDecl("function", "function") ?? findDecl("class", "class") ?? findDecl("interface", "interface") ?? findDecl("type", "type") ?? findDecl("enum", "enum") ?? findDecl("method", "method") ?? findDecl("const-fn", "function") ?? findDecl("member-fn", "function");
1178
1183
  }
1179
1184
  async function parseTypeScript(f, source) {
1180
1185
  const grammar = grammarFor(f.ext);
@@ -1612,7 +1617,7 @@ import { basename as basename2 } from "path";
1612
1617
  // src/hooks/claude-md.ts
1613
1618
  import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
1614
1619
  import { basename, dirname as dirname4 } from "path";
1615
- var POLICY_VERSION = 4;
1620
+ var POLICY_VERSION = 5;
1616
1621
  var POLICY_BEGIN = `<!-- synthra-policy v${POLICY_VERSION} BEGIN -->`;
1617
1622
  var POLICY_END = `<!-- synthra-policy v${POLICY_VERSION} END -->`;
1618
1623
  var ANY_BLOCK_RE = /<!--\s*synthra-policy\s+v\d+\s+BEGIN\s*-->[\s\S]*?<!--\s*synthra-policy\s+v\d+\s+END\s*-->\s*/g;
@@ -1680,6 +1685,10 @@ function policyBlock() {
1680
1685
  " reads should be rare \u2014 only when you genuinely need the full file.",
1681
1686
  "- If `graph_continue`'s `Files` list contains a `::` entry, pass it",
1682
1687
  " verbatim to `graph_read`.",
1688
+ "- **Large file?** Don't read it in successive line-range chunks \u2014 call",
1689
+ ' `graph_continue` or `graph_read("file::symbol")` to pull the one symbol',
1690
+ " you need. Chunked whole-file Reads are exactly the cost `graph_read`",
1691
+ " exists to avoid.",
1683
1692
  "",
1684
1693
  "### Editing a file",
1685
1694
  "",
@@ -2234,7 +2243,7 @@ async function writeContextMd(path, ctx) {
2234
2243
  // src/memory/context-store.ts
2235
2244
  import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
2236
2245
  import { dirname as dirname6 } from "path";
2237
- var SCHEMA_VERSION = 1;
2246
+ var SCHEMA_VERSION2 = 1;
2238
2247
  async function readEntries(path) {
2239
2248
  try {
2240
2249
  const raw = await readFile10(path, "utf8");
@@ -2246,7 +2255,7 @@ async function readEntries(path) {
2246
2255
  }
2247
2256
  async function writeEntries(path, entries) {
2248
2257
  await mkdir5(dirname6(path), { recursive: true });
2249
- const store = { schema_version: SCHEMA_VERSION, entries };
2258
+ const store = { schema_version: SCHEMA_VERSION2, entries };
2250
2259
  await writeFile5(path, JSON.stringify(store, null, 2) + "\n", "utf8");
2251
2260
  }
2252
2261
  async function appendEntry(path, entry) {
@@ -2780,15 +2789,33 @@ Reason: ${retrieval.reason}
2780
2789
  return textContent(`${header}
2781
2790
  ${packed.text}`);
2782
2791
  }
2792
+ function resolveFileTarget(graph, filePath) {
2793
+ const files = graph.nodes.filter((n) => n.kind === "file");
2794
+ const exact = files.find((n) => n.path === filePath);
2795
+ if (exact) return { node: exact };
2796
+ const suffix = "/" + filePath;
2797
+ const matches = files.filter((n) => n.path.endsWith(suffix));
2798
+ if (matches.length === 1) return { node: matches[0] };
2799
+ if (matches.length > 1) return { ambiguous: matches.map((n) => n.path) };
2800
+ return { none: true };
2801
+ }
2783
2802
  function graphRead(args, ctx) {
2784
2803
  const target = typeof args?.target === "string" ? args.target : "";
2785
2804
  if (!target) return errorContent("graph_read: 'target' (string) is required");
2786
2805
  const [rawFile, symbolName] = target.includes("::") ? target.split("::", 2) : [target, void 0];
2787
2806
  const filePath = (rawFile ?? "").trim();
2788
- const fileNode = ctx.graph.nodes.find(
2789
- (n) => n.kind === "file" && n.path === filePath
2790
- );
2791
- if (!fileNode) return errorContent(`graph_read: file not found in graph: ${filePath}`);
2807
+ const resolved = resolveFileTarget(ctx.graph, filePath);
2808
+ if ("ambiguous" in resolved) {
2809
+ const shown = resolved.ambiguous.slice(0, 5).join(", ");
2810
+ const more = resolved.ambiguous.length > 5 ? ", \u2026" : "";
2811
+ return errorContent(
2812
+ `graph_read: '${filePath}' matches multiple files (${shown}${more}). Pass a longer path.`
2813
+ );
2814
+ }
2815
+ if ("none" in resolved) {
2816
+ return errorContent(`graph_read: file not found in graph: ${filePath}`);
2817
+ }
2818
+ const fileNode = resolved.node;
2792
2819
  if (!symbolName) {
2793
2820
  return textContent(`# ${fileNode.path}
2794
2821
 
@@ -2796,10 +2823,10 @@ ${fileNode.content}`);
2796
2823
  }
2797
2824
  const cleanSym = symbolName.trim();
2798
2825
  const symbol = ctx.graph.nodes.find(
2799
- (n) => n.kind === "symbol" && n.file === filePath && n.name === cleanSym
2826
+ (n) => n.kind === "symbol" && n.file === fileNode.path && n.name === cleanSym
2800
2827
  );
2801
2828
  if (!symbol) {
2802
- return errorContent(`graph_read: symbol '${cleanSym}' not found in ${filePath}`);
2829
+ return errorContent(`graph_read: symbol '${cleanSym}' not found in ${fileNode.path}`);
2803
2830
  }
2804
2831
  const lines = fileNode.content.split(/\r?\n/);
2805
2832
  const body = lines.slice(symbol.start_line - 1, symbol.end_line).join("\n");
@@ -2984,16 +3011,32 @@ function looksLikeNonSymbolQuery(pattern) {
2984
3011
  }
2985
3012
  return false;
2986
3013
  }
2987
- function recentlyTouchedMatchesQuery(recentPaths, queryTokens) {
3014
+ function recentlyTouchedMatchesQuery(recentPaths, queryTokens, graph) {
3015
+ if (recentPaths.length === 0) return [];
3016
+ const recent = new Set(recentPaths);
3017
+ const keywordsByPath = /* @__PURE__ */ new Map();
3018
+ for (const n of graph.nodes) {
3019
+ if (n.kind === "file" && recent.has(n.path)) keywordsByPath.set(n.path, n.keywords);
3020
+ }
2988
3021
  const matches = [];
2989
3022
  for (const path of recentPaths) {
2990
3023
  const lower = path.toLowerCase();
3024
+ let matched = false;
2991
3025
  for (const t of queryTokens) {
2992
3026
  if (lower.includes(t)) {
2993
- matches.push(path);
3027
+ matched = true;
2994
3028
  break;
2995
3029
  }
2996
3030
  }
3031
+ if (!matched) {
3032
+ for (const kw of keywordsByPath.get(path) ?? []) {
3033
+ if (queryTokens.has(kw)) {
3034
+ matched = true;
3035
+ break;
3036
+ }
3037
+ }
3038
+ }
3039
+ if (matched) matches.push(path);
2997
3040
  }
2998
3041
  return matches;
2999
3042
  }
@@ -3044,7 +3087,7 @@ async function handleGate(req, ctx) {
3044
3087
  }
3045
3088
  const qTokens = new Set(tokenizeQuery(query));
3046
3089
  const recentPaths = ctx.activity.recentFilePaths(RECENT_ACTIVITY_WINDOW_MS);
3047
- const overlap = recentlyTouchedMatchesQuery(recentPaths, qTokens);
3090
+ const overlap = recentlyTouchedMatchesQuery(recentPaths, qTokens, ctx.graph);
3048
3091
  if (overlap.length > 0) {
3049
3092
  const res2 = {
3050
3093
  decision: "allow",
@@ -3131,10 +3174,20 @@ ${fileCount} files indexed, ${symbolCount} symbols. Prefer the graph_* MCP tools
3131
3174
  // src/server/http.ts
3132
3175
  async function loadContext(paths) {
3133
3176
  try {
3134
- const [graph, symbolIndex] = await Promise.all([
3177
+ let [graph, symbolIndex] = await Promise.all([
3135
3178
  readGraph(paths.infoGraph),
3136
3179
  readSymbolIndex(paths.symbolIndex)
3137
3180
  ]);
3181
+ if (graph.schema_version !== SCHEMA_VERSION) {
3182
+ log.info(
3183
+ `graph schema v${graph.schema_version} \u2260 current v${SCHEMA_VERSION} \u2014 rescanning\u2026`
3184
+ );
3185
+ await scanProject(paths.projectRoot, { silent: true });
3186
+ [graph, symbolIndex] = await Promise.all([
3187
+ readGraph(paths.infoGraph),
3188
+ readSymbolIndex(paths.symbolIndex)
3189
+ ]);
3190
+ }
3138
3191
  const activity = new ActivityStore(paths.activityLog);
3139
3192
  return { paths, graph, symbolIndex, activity };
3140
3193
  } catch (err2) {