@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.
- package/CHANGELOG.md +76 -0
- package/dist/cli/index.js +312 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/index.js +61 -8
- package/dist/dashboard/index.js.map +1 -1
- package/dist/server/index.js +57 -15
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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:
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
2980
|
-
import { dirname as
|
|
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
|
|
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
|
|
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
|
|
3109
|
-
import { dirname as
|
|
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
|
|
3117
|
-
await
|
|
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
|
-
|
|
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) {
|