@jefuriiij/synthra 0.1.22 → 0.1.24

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);
@@ -1591,6 +1596,7 @@ function resolvePaths(projectRoot) {
1591
1596
  activityLog: join5(graphDir, "activity.jsonl"),
1592
1597
  tokenLog: join5(graphDir, "token_log.jsonl"),
1593
1598
  gateLog: join5(graphDir, "gate_log.jsonl"),
1599
+ toolLog: join5(graphDir, "tool_log.jsonl"),
1594
1600
  mcpPort: join5(graphDir, "mcp_port"),
1595
1601
  mcpServerLog: join5(graphDir, "mcp_server.log"),
1596
1602
  mcpServerErrLog: join5(graphDir, "mcp_server.err.log"),
@@ -1612,7 +1618,7 @@ import { basename as basename2 } from "path";
1612
1618
  // src/hooks/claude-md.ts
1613
1619
  import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
1614
1620
  import { basename, dirname as dirname4 } from "path";
1615
- var POLICY_VERSION = 4;
1621
+ var POLICY_VERSION = 5;
1616
1622
  var POLICY_BEGIN = `<!-- synthra-policy v${POLICY_VERSION} BEGIN -->`;
1617
1623
  var POLICY_END = `<!-- synthra-policy v${POLICY_VERSION} END -->`;
1618
1624
  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 +1686,10 @@ function policyBlock() {
1680
1686
  " reads should be rare \u2014 only when you genuinely need the full file.",
1681
1687
  "- If `graph_continue`'s `Files` list contains a `::` entry, pass it",
1682
1688
  " verbatim to `graph_read`.",
1689
+ "- **Large file?** Don't read it in successive line-range chunks \u2014 call",
1690
+ ' `graph_continue` or `graph_read("file::symbol")` to pull the one symbol',
1691
+ " you need. Chunked whole-file Reads are exactly the cost `graph_read`",
1692
+ " exists to avoid.",
1683
1693
  "",
1684
1694
  "### Editing a file",
1685
1695
  "",
@@ -1922,6 +1932,10 @@ async function scanProject(projectRootRaw, opts = {}) {
1922
1932
  };
1923
1933
  }
1924
1934
 
1935
+ // src/server/mcp.ts
1936
+ import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
1937
+ import { dirname as dirname7 } from "path";
1938
+
1925
1939
  // src/graph/rank.ts
1926
1940
  var STOPWORDS2 = /* @__PURE__ */ new Set([
1927
1941
  "a",
@@ -2234,7 +2248,7 @@ async function writeContextMd(path, ctx) {
2234
2248
  // src/memory/context-store.ts
2235
2249
  import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
2236
2250
  import { dirname as dirname6 } from "path";
2237
- var SCHEMA_VERSION = 1;
2251
+ var SCHEMA_VERSION2 = 1;
2238
2252
  async function readEntries(path) {
2239
2253
  try {
2240
2254
  const raw = await readFile10(path, "utf8");
@@ -2246,7 +2260,7 @@ async function readEntries(path) {
2246
2260
  }
2247
2261
  async function writeEntries(path, entries) {
2248
2262
  await mkdir5(dirname6(path), { recursive: true });
2249
- const store = { schema_version: SCHEMA_VERSION, entries };
2263
+ const store = { schema_version: SCHEMA_VERSION2, entries };
2250
2264
  await writeFile5(path, JSON.stringify(store, null, 2) + "\n", "utf8");
2251
2265
  }
2252
2266
  async function appendEntry(path, entry) {
@@ -2771,7 +2785,10 @@ _(no file is unreferenced \u2014 every file is either imported by another, has a
2771
2785
  async function graphContinue(args, ctx) {
2772
2786
  const query = typeof args?.query === "string" ? args.query : "";
2773
2787
  if (!query) return errorContent("graph_continue: 'query' (string) is required");
2774
- const retrieval = await retrieve(ctx.graph, query);
2788
+ const retrieval = await retrieve(ctx.graph, query, {
2789
+ recentlyEditedPaths: ctx.activity.recentFilePaths(15 * 60 * 1e3),
2790
+ sessionKnownPaths: getRegisteredEdits()
2791
+ });
2775
2792
  const packed = await pack(retrieval.files, { query, graph: ctx.graph });
2776
2793
  const header = `Confidence: ${retrieval.confidence}
2777
2794
  Files: ${retrieval.files.map((f) => f.path).join(", ") || "(none)"}
@@ -2833,6 +2850,9 @@ function graphRegisterEdit(args, _ctx) {
2833
2850
  for (const f of files) editedFiles.add(f);
2834
2851
  return textContent(`Registered ${files.length} edited file(s). Total tracked this session: ${editedFiles.size}.`);
2835
2852
  }
2853
+ function getRegisteredEdits() {
2854
+ return Array.from(editedFiles);
2855
+ }
2836
2856
  var VALID_KINDS = /* @__PURE__ */ new Set(["decision", "task", "next", "fact", "blocker"]);
2837
2857
  async function contextRemember(args, ctx) {
2838
2858
  const text = typeof args?.text === "string" ? args.text.trim() : "";
@@ -2896,6 +2916,17 @@ async function contextRecall(args, ctx) {
2896
2916
  }
2897
2917
  return textContent(lines.join("\n"));
2898
2918
  }
2919
+ async function logToolCall(ctx, tool) {
2920
+ try {
2921
+ await mkdir6(dirname7(ctx.paths.toolLog), { recursive: true });
2922
+ await appendFile2(
2923
+ ctx.paths.toolLog,
2924
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), tool }) + "\n",
2925
+ "utf8"
2926
+ );
2927
+ } catch {
2928
+ }
2929
+ }
2899
2930
  async function handleMcpRequest(body, ctx) {
2900
2931
  if (!body || typeof body !== "object") {
2901
2932
  return err(null, ERR.invalidRequest, "Request body must be a JSON-RPC 2.0 object.");
@@ -2922,6 +2953,7 @@ async function handleMcpRequest(body, ctx) {
2922
2953
  const toolName = typeof params.name === "string" ? params.name : "";
2923
2954
  if (!toolName) return err(id, ERR.invalidParams, "'name' is required for tools/call.");
2924
2955
  const args = params.arguments && typeof params.arguments === "object" ? params.arguments : {};
2956
+ void logToolCall(ctx, toolName);
2925
2957
  const result = await callTool(toolName, args, ctx);
2926
2958
  return ok(id, result);
2927
2959
  }
@@ -2976,8 +3008,8 @@ async function handleContextUpdate(req, ctx) {
2976
3008
  }
2977
3009
 
2978
3010
  // src/server/routes/gate.ts
2979
- import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
2980
- import { dirname as dirname7 } from "path";
3011
+ import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
3012
+ import { dirname as dirname8 } from "path";
2981
3013
  var BLOCKABLE_TOOLS = /* @__PURE__ */ new Set(["Grep", "Glob"]);
2982
3014
  var RECENT_ACTIVITY_WINDOW_MS = 5 * 60 * 1e3;
2983
3015
  function extractQuery(toolName, input) {
@@ -3033,7 +3065,7 @@ function recentlyTouchedMatchesQuery(recentPaths, queryTokens, graph) {
3033
3065
  }
3034
3066
  async function logDecision(ctx, toolName, query, decision, reason) {
3035
3067
  try {
3036
- await mkdir6(dirname7(ctx.paths.gateLog), { recursive: true });
3068
+ await mkdir7(dirname8(ctx.paths.gateLog), { recursive: true });
3037
3069
  const entry = {
3038
3070
  ts: (/* @__PURE__ */ new Date()).toISOString(),
3039
3071
  tool: toolName,
@@ -3041,7 +3073,7 @@ async function logDecision(ctx, toolName, query, decision, reason) {
3041
3073
  query,
3042
3074
  reason
3043
3075
  };
3044
- await appendFile2(ctx.paths.gateLog, JSON.stringify(entry) + "\n", "utf8");
3076
+ await appendFile3(ctx.paths.gateLog, JSON.stringify(entry) + "\n", "utf8");
3045
3077
  } catch {
3046
3078
  }
3047
3079
  }
@@ -3105,16 +3137,16 @@ async function handleGate(req, ctx) {
3105
3137
  }
3106
3138
 
3107
3139
  // src/server/routes/log.ts
3108
- import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
3109
- import { dirname as dirname8 } from "path";
3140
+ import { appendFile as appendFile4, mkdir as mkdir8 } from "fs/promises";
3141
+ import { dirname as dirname9 } from "path";
3110
3142
  async function handleLog(entry, ctx) {
3111
3143
  if (!entry || typeof entry.input_tokens !== "number" || typeof entry.output_tokens !== "number") {
3112
3144
  throw new Error("log: input_tokens and output_tokens (number) are required");
3113
3145
  }
3114
3146
  const written_at = (/* @__PURE__ */ new Date()).toISOString();
3115
3147
  const record = { ...entry, written_at };
3116
- await mkdir7(dirname8(ctx.paths.tokenLog), { recursive: true });
3117
- await appendFile3(ctx.paths.tokenLog, JSON.stringify(record) + "\n", "utf8");
3148
+ await mkdir8(dirname9(ctx.paths.tokenLog), { recursive: true });
3149
+ await appendFile4(ctx.paths.tokenLog, JSON.stringify(record) + "\n", "utf8");
3118
3150
  return { ok: true, written_at };
3119
3151
  }
3120
3152
 
@@ -3165,10 +3197,20 @@ ${fileCount} files indexed, ${symbolCount} symbols. Prefer the graph_* MCP tools
3165
3197
  // src/server/http.ts
3166
3198
  async function loadContext(paths) {
3167
3199
  try {
3168
- const [graph, symbolIndex] = await Promise.all([
3200
+ let [graph, symbolIndex] = await Promise.all([
3169
3201
  readGraph(paths.infoGraph),
3170
3202
  readSymbolIndex(paths.symbolIndex)
3171
3203
  ]);
3204
+ if (graph.schema_version !== SCHEMA_VERSION) {
3205
+ log.info(
3206
+ `graph schema v${graph.schema_version} \u2260 current v${SCHEMA_VERSION} \u2014 rescanning\u2026`
3207
+ );
3208
+ await scanProject(paths.projectRoot, { silent: true });
3209
+ [graph, symbolIndex] = await Promise.all([
3210
+ readGraph(paths.infoGraph),
3211
+ readSymbolIndex(paths.symbolIndex)
3212
+ ]);
3213
+ }
3172
3214
  const activity = new ActivityStore(paths.activityLog);
3173
3215
  return { paths, graph, symbolIndex, activity };
3174
3216
  } catch (err2) {