@inerrata-corporation/errata 2.0.21-dev.3707 → 2.0.21-dev.3716

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/errata.mjs CHANGED
@@ -34899,7 +34899,7 @@ function isQuotaError(err2) {
34899
34899
  return status === 403 && /quota/i.test(msg);
34900
34900
  }
34901
34901
  async function dualAugment(opts) {
34902
- const { store, cloud, localHits, seedNode, queryText, limit } = opts;
34902
+ const { store, cloud, localHits, seedNode, queryText, limit, generalizeSoft } = opts;
34903
34903
  const cloudHits = [];
34904
34904
  let seedUsageCount;
34905
34905
  let seedProvenance = "local";
@@ -34910,7 +34910,7 @@ async function dualAugment(opts) {
34910
34910
  return !!n && isHardBridge(n);
34911
34911
  });
34912
34912
  const softText = queryText ?? (seedNode && BRIDGE_LABELS.has(seedNode.label) ? seedNode.description : void 0);
34913
- const softQ = softText ? generalizeSymbols(softText, lexiconsFor(store).symbols, 1) : void 0;
34913
+ const softQ = softText ? await generalizeSoft?.(softText) ?? generalizeSymbols(softText, lexiconsFor(store).symbols, 1) : void 0;
34914
34914
  if (seedNode?.label === "Package") {
34915
34915
  calls.push(
34916
34916
  cloud.packages(seedNode.id).then((pn) => {
@@ -35022,6 +35022,50 @@ var init_dual_augment = __esm({
35022
35022
  }
35023
35023
  });
35024
35024
 
35025
+ // src/daemon-generalize-client.ts
35026
+ function daemonLoopbackUrl(lockPath = globalDaemonLock()) {
35027
+ const url2 = readDaemonLock(lockPath)?.webUiUrl;
35028
+ if (!url2) return null;
35029
+ try {
35030
+ const u = new URL(url2);
35031
+ const isLoopback = u.hostname === "127.0.0.1" || u.hostname === "localhost" || u.hostname === "[::1]";
35032
+ return u.protocol === "http:" && isLoopback ? u.origin : null;
35033
+ } catch {
35034
+ return null;
35035
+ }
35036
+ }
35037
+ function daemonGeneralizer(workspaceRoot, opts = {}) {
35038
+ const baseUrl = opts.baseUrl ?? (() => daemonLoopbackUrl());
35039
+ const fetchFn = opts.fetchFn ?? fetch;
35040
+ const timeoutMs = opts.timeoutMs ?? DAEMON_GENERALIZE_TIMEOUT_MS;
35041
+ return async (text) => {
35042
+ try {
35043
+ const base = baseUrl();
35044
+ if (!base) return void 0;
35045
+ const res = await fetchFn(`${base}/api/generalize`, {
35046
+ method: "POST",
35047
+ headers: { "content-type": "application/json" },
35048
+ body: JSON.stringify({ workspaceRoot, text }),
35049
+ signal: AbortSignal.timeout(timeoutMs)
35050
+ });
35051
+ if (!res.ok) return void 0;
35052
+ const body2 = await res.json();
35053
+ return typeof body2.generalized === "string" ? body2.generalized : void 0;
35054
+ } catch {
35055
+ return void 0;
35056
+ }
35057
+ };
35058
+ }
35059
+ var DAEMON_GENERALIZE_TIMEOUT_MS;
35060
+ var init_daemon_generalize_client = __esm({
35061
+ "src/daemon-generalize-client.ts"() {
35062
+ "use strict";
35063
+ init_lock();
35064
+ init_paths();
35065
+ DAEMON_GENERALIZE_TIMEOUT_MS = 3e3;
35066
+ }
35067
+ });
35068
+
35025
35069
  // ../../packages/indexer/src/parse-cache.ts
35026
35070
  import { createHash as createHash6 } from "node:crypto";
35027
35071
  import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync14, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync11 } from "node:fs";
@@ -44382,6 +44426,7 @@ async function runMcpServer(workspaceRoot, options = {}) {
44382
44426
  const store = openGraphStore({ path: paths.castalia });
44383
44427
  const handle2 = createMcpHandler(store, {
44384
44428
  ...buildToolContext(workspaceRoot),
44429
+ generalizeSoft: daemonGeneralizer(workspaceRoot),
44385
44430
  sharedDbPath: sharedStorePath()
44386
44431
  }, options);
44387
44432
  process.stdin.setEncoding("utf8");
@@ -44427,6 +44472,7 @@ var init_mcp = __esm({
44427
44472
  init_src8();
44428
44473
  init_dual_burst();
44429
44474
  init_paths();
44475
+ init_daemon_generalize_client();
44430
44476
  init_webui();
44431
44477
  init_reconcile();
44432
44478
  init_agent_signals();
@@ -44502,6 +44548,7 @@ var init_mcp = __esm({
44502
44548
  const dual = await dualAugment({
44503
44549
  store,
44504
44550
  cloud: ctx.cloud,
44551
+ ...ctx.generalizeSoft ? { generalizeSoft: ctx.generalizeSoft } : {},
44505
44552
  localHits,
44506
44553
  queryText: query,
44507
44554
  limit
@@ -44681,6 +44728,7 @@ var init_mcp = __esm({
44681
44728
  const dual = await dualAugment({
44682
44729
  store,
44683
44730
  cloud: ctx.cloud,
44731
+ ...ctx.generalizeSoft ? { generalizeSoft: ctx.generalizeSoft } : {},
44684
44732
  localHits,
44685
44733
  seedNode,
44686
44734
  limit: args2["limit"] != null ? Number(args2["limit"]) : 20
@@ -45252,6 +45300,7 @@ var init_mcp = __esm({
45252
45300
  const dual = await dualAugment({
45253
45301
  store,
45254
45302
  cloud: ctx.cloud,
45303
+ ...ctx.generalizeSoft ? { generalizeSoft: ctx.generalizeSoft } : {},
45255
45304
  localHits,
45256
45305
  seedNode: node2,
45257
45306
  limit
@@ -82804,7 +82853,7 @@ function createWatchBreaker(deps) {
82804
82853
  }
82805
82854
 
82806
82855
  // src/engine.ts
82807
- var DAEMON_VERSION = true ? "2.0.21-dev.3707" : "2.0.0-alpha.0";
82856
+ var DAEMON_VERSION = true ? "2.0.21-dev.3716" : "2.0.0-alpha.0";
82808
82857
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
82809
82858
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
82810
82859
  var GIT_OP_MUTE_MS = 4e3;
@@ -86970,6 +87019,98 @@ var ConsolidateWorker = class {
86970
87019
  }
86971
87020
  };
86972
87021
 
87022
+ // src/generalize-worker-client.ts
87023
+ import { Worker as Worker3 } from "node:worker_threads";
87024
+ var GENERALIZE_WORKER_IDLE_MS = 30 * 6e4;
87025
+ var GeneralizeWorker = class {
87026
+ worker = null;
87027
+ ready = null;
87028
+ pending = /* @__PURE__ */ new Map();
87029
+ nextId = 1;
87030
+ stopped = false;
87031
+ idleTimer = null;
87032
+ async generalize(castaliaPath, text) {
87033
+ if (this.stopped) throw new Error("generalize worker stopped");
87034
+ this.ensureWorker();
87035
+ this.touch();
87036
+ await this.ready;
87037
+ const id = this.nextId++;
87038
+ return new Promise((resolve10, reject) => {
87039
+ this.pending.set(id, { resolve: resolve10, reject });
87040
+ const req = { id, castaliaPath, text };
87041
+ this.worker.postMessage(req);
87042
+ });
87043
+ }
87044
+ touch() {
87045
+ if (this.idleTimer) clearTimeout(this.idleTimer);
87046
+ this.idleTimer = setTimeout(() => {
87047
+ if (this.pending.size > 0) return this.touch();
87048
+ const w = this.worker;
87049
+ this.worker = null;
87050
+ this.ready = null;
87051
+ void w?.terminate().catch(() => {
87052
+ });
87053
+ }, GENERALIZE_WORKER_IDLE_MS);
87054
+ this.idleTimer.unref?.();
87055
+ }
87056
+ ensureWorker() {
87057
+ if (this.worker) return;
87058
+ const worker = new Worker3(new URL("./generalize-worker.mjs", import.meta.url), {
87059
+ workerData: {},
87060
+ execArgv: process.execArgv,
87061
+ resourceLimits: workerResourceLimits()
87062
+ });
87063
+ noteIsolateSpawned();
87064
+ this.worker = worker;
87065
+ this.ready = new Promise((resolve10, reject) => {
87066
+ const onReady = (m) => {
87067
+ if (m?.ready) {
87068
+ worker.off("message", onReady);
87069
+ worker.off("error", onErr);
87070
+ resolve10();
87071
+ }
87072
+ };
87073
+ const onErr = (err2) => {
87074
+ worker.off("message", onReady);
87075
+ reject(err2);
87076
+ };
87077
+ worker.once("error", onErr);
87078
+ worker.on("message", onReady);
87079
+ });
87080
+ worker.on("message", (m) => {
87081
+ if (typeof m?.id !== "number") return;
87082
+ const p = this.pending.get(m.id);
87083
+ if (!p) return;
87084
+ this.pending.delete(m.id);
87085
+ if (m.ok && typeof m.result === "string") p.resolve(m.result);
87086
+ else p.reject(new Error(m.error ?? "generalization failed"));
87087
+ });
87088
+ const onGone = (err2) => {
87089
+ const e = err2 instanceof Error ? err2 : new Error("generalize worker exited");
87090
+ for (const [, p] of this.pending) p.reject(e);
87091
+ this.pending.clear();
87092
+ if (this.worker === worker) {
87093
+ this.worker = null;
87094
+ this.ready = null;
87095
+ }
87096
+ };
87097
+ worker.on("error", onGone);
87098
+ worker.on("exit", () => {
87099
+ noteIsolateExited();
87100
+ onGone();
87101
+ });
87102
+ }
87103
+ async stop() {
87104
+ this.stopped = true;
87105
+ if (this.idleTimer) clearTimeout(this.idleTimer);
87106
+ if (this.worker) {
87107
+ await this.worker.terminate().catch(() => {
87108
+ });
87109
+ this.worker = null;
87110
+ }
87111
+ }
87112
+ };
87113
+
86973
87114
  // src/prepare-store.ts
86974
87115
  init_src5();
86975
87116
  async function prepareStore(path2, root, workspaceId2) {
@@ -89925,6 +90066,7 @@ async function startMultiDaemon(opts = {}) {
89925
90066
  const sharedStore = openGraphStore({ path: sharedDbPath });
89926
90067
  const bundledRun = import.meta.url.endsWith(".mjs");
89927
90068
  const consolidateWorker = bundledRun && sharedDbPath !== ":memory:" ? new ConsolidateWorker() : null;
90069
+ const generalizeWorker = bundledRun ? new GeneralizeWorker() : null;
89928
90070
  startLoopLagMonitor();
89929
90071
  let baseUrl = "";
89930
90072
  const records = [];
@@ -91612,6 +91754,7 @@ async function startMultiDaemon(opts = {}) {
91612
91754
  await Promise.allSettled(attaching.values());
91613
91755
  for (const r of records) await r.engine.stop();
91614
91756
  if (consolidateWorker) await consolidateWorker.stop();
91757
+ if (generalizeWorker) await generalizeWorker.stop();
91615
91758
  try {
91616
91759
  sharedStore.close();
91617
91760
  } catch {
@@ -91640,6 +91783,33 @@ async function startMultiDaemon(opts = {}) {
91640
91783
  }
91641
91784
  });
91642
91785
  app.get("/api/sync", (c) => c.json(flushSnapshot()));
91786
+ const GENERALIZE_MAX_CHARS = 8e3;
91787
+ app.post("/api/generalize", async (c) => {
91788
+ const host = (c.req.header("host") ?? "").toLowerCase();
91789
+ if (!/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/.test(host)) return c.json({ error: "forbidden" }, 403);
91790
+ if (!(c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json")) {
91791
+ return c.json({ error: "json required" }, 415);
91792
+ }
91793
+ let body2;
91794
+ try {
91795
+ body2 = await c.req.json();
91796
+ } catch {
91797
+ return c.json({ error: "bad json" }, 400);
91798
+ }
91799
+ if (typeof body2.workspaceRoot !== "string" || typeof body2.text !== "string") {
91800
+ return c.json({ error: "workspaceRoot and text required" }, 400);
91801
+ }
91802
+ if (body2.text.length > GENERALIZE_MAX_CHARS) return c.json({ error: "text too long" }, 413);
91803
+ const entry = workspaceEntryForPath(pathResolve(body2.workspaceRoot));
91804
+ if (!entry) return c.json({ error: "unregistered workspace" }, 404);
91805
+ if (!generalizeWorker) return c.json({ error: "no worker" }, 503);
91806
+ try {
91807
+ const generalized = await generalizeWorker.generalize(workspacePaths(entry.path).castalia, body2.text);
91808
+ return c.json({ generalized });
91809
+ } catch (err2) {
91810
+ return c.json({ error: err2 instanceof Error ? err2.message : String(err2) }, 503);
91811
+ }
91812
+ });
91643
91813
  app.post("/api/tick", async (c) => {
91644
91814
  try {
91645
91815
  return c.json({ reports: Object.fromEntries(await daemon.tickAll()) });