@jefuriiij/synthra 0.8.0 → 0.9.0
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 +41 -0
- package/dist/cli/index.js +105 -47
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/index.js +2 -2
- package/dist/dashboard/index.js.map +1 -1
- package/dist/server/index.js +103 -45
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -1987,9 +1987,13 @@ async function patchClaudeMd(path, projectName) {
|
|
|
1987
1987
|
return { created: true, updated: false, skipped: false };
|
|
1988
1988
|
}
|
|
1989
1989
|
const stripped = existing.replace(ANY_BLOCK_RE, "");
|
|
1990
|
-
const
|
|
1991
|
-
const desired =
|
|
1992
|
-
|
|
1990
|
+
const base = stripped.replace(/\s+$/, "");
|
|
1991
|
+
const desired = base.length ? `${base}
|
|
1992
|
+
|
|
1993
|
+
${block}
|
|
1994
|
+
` : `${block}
|
|
1995
|
+
`;
|
|
1996
|
+
if (desired === existing) {
|
|
1993
1997
|
return { created: false, updated: false, skipped: true };
|
|
1994
1998
|
}
|
|
1995
1999
|
await writeFile3(path, desired, "utf8");
|
|
@@ -2088,8 +2092,8 @@ async function scanProject(projectRootRaw, opts = {}) {
|
|
|
2088
2092
|
const start = Date.now();
|
|
2089
2093
|
const verbose = !opts.silent;
|
|
2090
2094
|
if (verbose) log.info(`scanning ${projectRoot}`);
|
|
2091
|
-
const boot = await bootstrap(paths);
|
|
2092
|
-
if (verbose) {
|
|
2095
|
+
const boot = opts.skipBootstrap ? null : await bootstrap(paths);
|
|
2096
|
+
if (verbose && boot) {
|
|
2093
2097
|
if (boot.graphCreated) log.info(" created .synthra-graph/");
|
|
2094
2098
|
if (boot.contextCreated) log.info(" created .synthra/");
|
|
2095
2099
|
if (boot.gitignoreUpdated) log.info(" updated .gitignore");
|
|
@@ -2314,6 +2318,36 @@ var LearnRuntime = class _LearnRuntime {
|
|
|
2314
2318
|
}
|
|
2315
2319
|
};
|
|
2316
2320
|
|
|
2321
|
+
// src/shared/config.ts
|
|
2322
|
+
function num(name, fallback) {
|
|
2323
|
+
const v = process.env[name];
|
|
2324
|
+
if (!v) return fallback;
|
|
2325
|
+
const n = Number(v);
|
|
2326
|
+
return Number.isFinite(n) ? n : fallback;
|
|
2327
|
+
}
|
|
2328
|
+
function str(name, fallback) {
|
|
2329
|
+
return process.env[name] ?? fallback;
|
|
2330
|
+
}
|
|
2331
|
+
function loadConfig() {
|
|
2332
|
+
return {
|
|
2333
|
+
hardMaxReadChars: num("SYN_HARD_MAX_READ_CHARS", 4e3),
|
|
2334
|
+
gateHintMaxChars: num("SYN_GATE_HINT_CHARS", 1200),
|
|
2335
|
+
readDepsMaxChars: num("SYN_READ_DEPS_CHARS", 900),
|
|
2336
|
+
turnReadBudgetChars: num("SYN_TURN_READ_BUDGET_CHARS", 18e3),
|
|
2337
|
+
fallbackMaxCallsPerTurn: num("SYN_FALLBACK_MAX_CALLS_PER_TURN", 1),
|
|
2338
|
+
retrieveCacheTtlSec: num("SYN_RETRIEVE_CACHE_TTL_SEC", 900),
|
|
2339
|
+
// Auto-reindex: re-run the incremental scan + swap the in-memory graph this
|
|
2340
|
+
// many ms after the last source-file change, so graph reads never go stale
|
|
2341
|
+
// mid-session. Set SYN_NO_AUTOREINDEX to disable entirely.
|
|
2342
|
+
reindexDebounceMs: num("SYN_REINDEX_DEBOUNCE_MS", 1e3),
|
|
2343
|
+
autoReindex: !process.env.SYN_NO_AUTOREINDEX,
|
|
2344
|
+
mcpPort: process.env.SYN_MCP_PORT ? num("SYN_MCP_PORT", 0) : null,
|
|
2345
|
+
dashboardPort: num("SYN_DASHBOARD_PORT", 8901),
|
|
2346
|
+
logLevel: str("SYN_LOG_LEVEL", "info"),
|
|
2347
|
+
claudeBin: str("SYN_CLAUDE_BIN", "claude")
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2317
2351
|
// src/server/mcp.ts
|
|
2318
2352
|
import { appendFile as appendFile3, mkdir as mkdir8 } from "fs/promises";
|
|
2319
2353
|
import { dirname as dirname9 } from "path";
|
|
@@ -2935,31 +2969,6 @@ async function pack(files, opts) {
|
|
|
2935
2969
|
};
|
|
2936
2970
|
}
|
|
2937
2971
|
|
|
2938
|
-
// src/shared/config.ts
|
|
2939
|
-
function num(name, fallback) {
|
|
2940
|
-
const v = process.env[name];
|
|
2941
|
-
if (!v) return fallback;
|
|
2942
|
-
const n = Number(v);
|
|
2943
|
-
return Number.isFinite(n) ? n : fallback;
|
|
2944
|
-
}
|
|
2945
|
-
function str(name, fallback) {
|
|
2946
|
-
return process.env[name] ?? fallback;
|
|
2947
|
-
}
|
|
2948
|
-
function loadConfig() {
|
|
2949
|
-
return {
|
|
2950
|
-
hardMaxReadChars: num("SYN_HARD_MAX_READ_CHARS", 4e3),
|
|
2951
|
-
gateHintMaxChars: num("SYN_GATE_HINT_CHARS", 1200),
|
|
2952
|
-
readDepsMaxChars: num("SYN_READ_DEPS_CHARS", 900),
|
|
2953
|
-
turnReadBudgetChars: num("SYN_TURN_READ_BUDGET_CHARS", 18e3),
|
|
2954
|
-
fallbackMaxCallsPerTurn: num("SYN_FALLBACK_MAX_CALLS_PER_TURN", 1),
|
|
2955
|
-
retrieveCacheTtlSec: num("SYN_RETRIEVE_CACHE_TTL_SEC", 900),
|
|
2956
|
-
mcpPort: process.env.SYN_MCP_PORT ? num("SYN_MCP_PORT", 0) : null,
|
|
2957
|
-
dashboardPort: num("SYN_DASHBOARD_PORT", 8901),
|
|
2958
|
-
logLevel: str("SYN_LOG_LEVEL", "info"),
|
|
2959
|
-
claudeBin: str("SYN_CLAUDE_BIN", "claude")
|
|
2960
|
-
};
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
2972
|
// src/server/mcp.ts
|
|
2964
2973
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
2965
2974
|
var SERVER_INFO = { name: "synthra", version: "0.0.1" };
|
|
@@ -3552,6 +3561,61 @@ function isFree(port) {
|
|
|
3552
3561
|
});
|
|
3553
3562
|
}
|
|
3554
3563
|
|
|
3564
|
+
// src/server/reindex.ts
|
|
3565
|
+
async function rescanAndSwap(ctx, paths, label) {
|
|
3566
|
+
try {
|
|
3567
|
+
await scanProject(paths.projectRoot, { silent: true, skipBootstrap: true });
|
|
3568
|
+
const [graph, symbolIndex] = await Promise.all([
|
|
3569
|
+
readGraph(paths.infoGraph),
|
|
3570
|
+
readSymbolIndex(paths.symbolIndex)
|
|
3571
|
+
]);
|
|
3572
|
+
ctx.graph = graph;
|
|
3573
|
+
ctx.symbolIndex = symbolIndex;
|
|
3574
|
+
log.info(`reindexed (${label}) \u2014 ${graph.symbol_count} symbols, ${graph.edge_count} edges.`);
|
|
3575
|
+
} catch (err2) {
|
|
3576
|
+
log.warn(`reindex failed (${label}): ${err2.message}`);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
function createReindexer(ctx, paths, opts = {}) {
|
|
3580
|
+
const debounceMs = opts.debounceMs ?? 1e3;
|
|
3581
|
+
const rescan = opts.rescan ?? rescanAndSwap;
|
|
3582
|
+
let timer = null;
|
|
3583
|
+
let running = false;
|
|
3584
|
+
let pending = false;
|
|
3585
|
+
async function run() {
|
|
3586
|
+
if (running) {
|
|
3587
|
+
pending = true;
|
|
3588
|
+
return;
|
|
3589
|
+
}
|
|
3590
|
+
running = true;
|
|
3591
|
+
try {
|
|
3592
|
+
await rescan(ctx, paths, "edit");
|
|
3593
|
+
} finally {
|
|
3594
|
+
running = false;
|
|
3595
|
+
if (pending) {
|
|
3596
|
+
pending = false;
|
|
3597
|
+
void run();
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
return {
|
|
3602
|
+
schedule() {
|
|
3603
|
+
if (timer) clearTimeout(timer);
|
|
3604
|
+
timer = setTimeout(() => {
|
|
3605
|
+
timer = null;
|
|
3606
|
+
void run();
|
|
3607
|
+
}, debounceMs);
|
|
3608
|
+
timer.unref?.();
|
|
3609
|
+
},
|
|
3610
|
+
stop() {
|
|
3611
|
+
if (timer) {
|
|
3612
|
+
clearTimeout(timer);
|
|
3613
|
+
timer = null;
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3555
3619
|
// src/server/routes/activity.ts
|
|
3556
3620
|
async function handleActivity(sinceMs, ctx) {
|
|
3557
3621
|
const events = ctx.activity.getEvents(sinceMs);
|
|
@@ -4037,24 +4101,17 @@ async function startServer(paths, options = {}) {
|
|
|
4037
4101
|
const app = buildApp(ctx, port);
|
|
4038
4102
|
const nodeServer = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" });
|
|
4039
4103
|
await writeFile9(paths.mcpPort, String(port), "utf8");
|
|
4040
|
-
const
|
|
4104
|
+
const cfg = loadConfig();
|
|
4105
|
+
const reindexer = cfg.autoReindex ? createReindexer(ctx, paths, { debounceMs: cfg.reindexDebounceMs }) : null;
|
|
4106
|
+
const fileWatcher = createFileWatcher(paths.projectRoot, (e) => {
|
|
4107
|
+
void ctx.activity.add(e);
|
|
4108
|
+
reindexer?.schedule();
|
|
4109
|
+
});
|
|
4041
4110
|
const gitWatcher = createGitWatcher(paths.projectRoot, async (e) => {
|
|
4042
4111
|
await ctx.activity.add(e);
|
|
4043
4112
|
if (e.kind === "branch-switch") {
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
log.info(`branch switched to '${to}' \u2014 rebuilding graph\u2026`);
|
|
4047
|
-
await scanProject(paths.projectRoot, { silent: true });
|
|
4048
|
-
const [g, idx] = await Promise.all([
|
|
4049
|
-
readGraph(paths.infoGraph),
|
|
4050
|
-
readSymbolIndex(paths.symbolIndex)
|
|
4051
|
-
]);
|
|
4052
|
-
ctx.graph = g;
|
|
4053
|
-
ctx.symbolIndex = idx;
|
|
4054
|
-
log.info(`graph rebuilt for '${to}' (${g.symbol_count} symbols).`);
|
|
4055
|
-
} catch (err2) {
|
|
4056
|
-
log.warn(`branch rescan failed: ${err2.message}`);
|
|
4057
|
-
}
|
|
4113
|
+
const to = e.details?.to ?? "unknown";
|
|
4114
|
+
await rescanAndSwap(ctx, paths, `branch ${to}`);
|
|
4058
4115
|
}
|
|
4059
4116
|
});
|
|
4060
4117
|
try {
|
|
@@ -4072,6 +4129,7 @@ async function startServer(paths, options = {}) {
|
|
|
4072
4129
|
port,
|
|
4073
4130
|
url,
|
|
4074
4131
|
async stop() {
|
|
4132
|
+
reindexer?.stop();
|
|
4075
4133
|
await fileWatcher.stop().catch(() => void 0);
|
|
4076
4134
|
await gitWatcher.stop().catch(() => void 0);
|
|
4077
4135
|
await ctx.learn?.flush().catch(() => void 0);
|