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

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.
@@ -774,7 +774,7 @@ function claimStructure(text, scope) {
774
774
  const predicate = normalizePredicate(text);
775
775
  const stance = detectStance(text);
776
776
  const base = { entities, predicate, stance, scope: scope ?? {} };
777
- if (predicate === "") base.raw = conceptBag(text);
777
+ if (predicate === "" || entities.length === 0) base.raw = conceptBag(text);
778
778
  return base;
779
779
  }
780
780
  function canonicalClaimId(text, scope) {
package/errata.mjs CHANGED
@@ -1056,7 +1056,7 @@ function claimStructure(text, scope) {
1056
1056
  const predicate = normalizePredicate(text);
1057
1057
  const stance = detectStance(text);
1058
1058
  const base = { entities, predicate, stance, scope: scope ?? {} };
1059
- if (predicate === "") base.raw = conceptBag(text);
1059
+ if (predicate === "" || entities.length === 0) base.raw = conceptBag(text);
1060
1060
  return base;
1061
1061
  }
1062
1062
  function canonicalClaimId(text, scope) {
@@ -32475,7 +32475,7 @@ function triageBullet(ref) {
32475
32475
  " \u2022 you diagnosed a cause \u2014 fold the chain into the flag with <- (caused-by), deepest",
32476
32476
  " sure mechanism rightmost: [!symptom <- cause <- root cause]. Symptom and cause far",
32477
32477
  " apart? open a thread [!#slug symptom] \u2026 later bind (cause:#slug mechanism);",
32478
- ` explaining a problem we SHOWED you \u2192 (cause:[${ref}] \u2026)`
32478
+ ` explaining a SHOWN problem \u2192 ${TAG_EXAMPLE.triageExplain(ref)}`
32479
32479
  ].join("\n");
32480
32480
  }
32481
32481
  function linkBullet(ref) {
@@ -32536,6 +32536,8 @@ var init_agent_signals = __esm({
32536
32536
  // links to a cited RootCause/Solution node; `(cause: free text)` mints a RootCause
32537
32537
  // from the text. Harvested in prior-tags.ts → engine.ts (see the triage note below).
32538
32538
  triage: (ref) => `(cause:[${ref}])`,
32539
+ // Unlike a bare cause citation, this handle names the Problem being explained.
32540
+ triageExplain: (ref) => `(cause:[${ref}] the mechanism)`,
32539
32541
  // The MINT form — free-text cause that mints a RootCause. Prime-time surfacing
32540
32542
  // (recallForFile) pastes this next to an open Problem that has no cause yet, so the
32541
32543
  // diagnostic moment is cued when the agent is looking at the problem. Kept here so
@@ -34899,7 +34901,7 @@ function isQuotaError(err2) {
34899
34901
  return status === 403 && /quota/i.test(msg);
34900
34902
  }
34901
34903
  async function dualAugment(opts) {
34902
- const { store, cloud, localHits, seedNode, queryText, limit } = opts;
34904
+ const { store, cloud, localHits, seedNode, queryText, limit, generalizeSoft } = opts;
34903
34905
  const cloudHits = [];
34904
34906
  let seedUsageCount;
34905
34907
  let seedProvenance = "local";
@@ -34910,7 +34912,7 @@ async function dualAugment(opts) {
34910
34912
  return !!n && isHardBridge(n);
34911
34913
  });
34912
34914
  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;
34915
+ const softQ = softText ? await generalizeSoft?.(softText) ?? generalizeSymbols(softText, lexiconsFor(store).symbols, 1) : void 0;
34914
34916
  if (seedNode?.label === "Package") {
34915
34917
  calls.push(
34916
34918
  cloud.packages(seedNode.id).then((pn) => {
@@ -35022,6 +35024,50 @@ var init_dual_augment = __esm({
35022
35024
  }
35023
35025
  });
35024
35026
 
35027
+ // src/daemon-generalize-client.ts
35028
+ function daemonLoopbackUrl(lockPath = globalDaemonLock()) {
35029
+ const url2 = readDaemonLock(lockPath)?.webUiUrl;
35030
+ if (!url2) return null;
35031
+ try {
35032
+ const u = new URL(url2);
35033
+ const isLoopback = u.hostname === "127.0.0.1" || u.hostname === "localhost" || u.hostname === "[::1]";
35034
+ return u.protocol === "http:" && isLoopback ? u.origin : null;
35035
+ } catch {
35036
+ return null;
35037
+ }
35038
+ }
35039
+ function daemonGeneralizer(workspaceRoot, opts = {}) {
35040
+ const baseUrl = opts.baseUrl ?? (() => daemonLoopbackUrl());
35041
+ const fetchFn = opts.fetchFn ?? fetch;
35042
+ const timeoutMs = opts.timeoutMs ?? DAEMON_GENERALIZE_TIMEOUT_MS;
35043
+ return async (text) => {
35044
+ try {
35045
+ const base = baseUrl();
35046
+ if (!base) return void 0;
35047
+ const res = await fetchFn(`${base}/api/generalize`, {
35048
+ method: "POST",
35049
+ headers: { "content-type": "application/json" },
35050
+ body: JSON.stringify({ workspaceRoot, text }),
35051
+ signal: AbortSignal.timeout(timeoutMs)
35052
+ });
35053
+ if (!res.ok) return void 0;
35054
+ const body2 = await res.json();
35055
+ return typeof body2.generalized === "string" ? body2.generalized : void 0;
35056
+ } catch {
35057
+ return void 0;
35058
+ }
35059
+ };
35060
+ }
35061
+ var DAEMON_GENERALIZE_TIMEOUT_MS;
35062
+ var init_daemon_generalize_client = __esm({
35063
+ "src/daemon-generalize-client.ts"() {
35064
+ "use strict";
35065
+ init_lock();
35066
+ init_paths();
35067
+ DAEMON_GENERALIZE_TIMEOUT_MS = 3e3;
35068
+ }
35069
+ });
35070
+
35025
35071
  // ../../packages/indexer/src/parse-cache.ts
35026
35072
  import { createHash as createHash6 } from "node:crypto";
35027
35073
  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 +44428,7 @@ async function runMcpServer(workspaceRoot, options = {}) {
44382
44428
  const store = openGraphStore({ path: paths.castalia });
44383
44429
  const handle2 = createMcpHandler(store, {
44384
44430
  ...buildToolContext(workspaceRoot),
44431
+ generalizeSoft: daemonGeneralizer(workspaceRoot),
44385
44432
  sharedDbPath: sharedStorePath()
44386
44433
  }, options);
44387
44434
  process.stdin.setEncoding("utf8");
@@ -44427,6 +44474,7 @@ var init_mcp = __esm({
44427
44474
  init_src8();
44428
44475
  init_dual_burst();
44429
44476
  init_paths();
44477
+ init_daemon_generalize_client();
44430
44478
  init_webui();
44431
44479
  init_reconcile();
44432
44480
  init_agent_signals();
@@ -44502,6 +44550,7 @@ var init_mcp = __esm({
44502
44550
  const dual = await dualAugment({
44503
44551
  store,
44504
44552
  cloud: ctx.cloud,
44553
+ ...ctx.generalizeSoft ? { generalizeSoft: ctx.generalizeSoft } : {},
44505
44554
  localHits,
44506
44555
  queryText: query,
44507
44556
  limit
@@ -44681,6 +44730,7 @@ var init_mcp = __esm({
44681
44730
  const dual = await dualAugment({
44682
44731
  store,
44683
44732
  cloud: ctx.cloud,
44733
+ ...ctx.generalizeSoft ? { generalizeSoft: ctx.generalizeSoft } : {},
44684
44734
  localHits,
44685
44735
  seedNode,
44686
44736
  limit: args2["limit"] != null ? Number(args2["limit"]) : 20
@@ -45252,6 +45302,7 @@ var init_mcp = __esm({
45252
45302
  const dual = await dualAugment({
45253
45303
  store,
45254
45304
  cloud: ctx.cloud,
45305
+ ...ctx.generalizeSoft ? { generalizeSoft: ctx.generalizeSoft } : {},
45255
45306
  localHits,
45256
45307
  seedNode: node2,
45257
45308
  limit
@@ -77444,6 +77495,32 @@ function emptyTagDispositions() {
77444
77495
  flagAsHandle: 0
77445
77496
  };
77446
77497
  }
77498
+ function parseCauseTag(content, sentence, threadId, dispositions) {
77499
+ const body2 = content.replace(/\s+/g, " ").trim();
77500
+ const cited = new RegExp(HANDLE_RE.source, "i").exec(body2);
77501
+ const handle2 = cited ? cited[1] ?? cited[2] : void 0;
77502
+ const base = { kind: "triage", sentence, ...threadId ? { threadId } : {} };
77503
+ if (handle2 && cited) {
77504
+ if (isPlaceholderHandle(handle2)) {
77505
+ dispositions.placeholderEcho++;
77506
+ return;
77507
+ }
77508
+ const mechanism = body2.slice(cited.index + cited[0].length).trim();
77509
+ if (!threadId && cited.index === 0 && mechanism) {
77510
+ if (mechanism.length < CAUSE_TEXT_MIN) {
77511
+ dispositions.causeTooShort++;
77512
+ return;
77513
+ }
77514
+ return { ...base, symptomHandle: handle2, causeText: mechanism };
77515
+ }
77516
+ return { ...base, handle: handle2 };
77517
+ }
77518
+ if (body2.length < CAUSE_TEXT_MIN) {
77519
+ dispositions.causeTooShort++;
77520
+ return;
77521
+ }
77522
+ return { ...base, causeText: body2 };
77523
+ }
77447
77524
  function addTagDispositions(a, b) {
77448
77525
  a.invalidVulnerability += b.invalidVulnerability;
77449
77526
  a.oversizeSegments += b.oversizeSegments;
@@ -77566,22 +77643,8 @@ function parseInlineTagsWithDispositions(text) {
77566
77643
  if (isFix) content = content.replace(FIX_PREFIX, "");
77567
77644
  else if (isCause) content = content.replace(CAUSE_PREFIX, "");
77568
77645
  if (isCause) {
77569
- HANDLE_RE.lastIndex = 0;
77570
- const h2 = HANDLE_RE.exec(content);
77571
- let handle2 = h2 ? h2[1] ?? h2[2] : void 0;
77572
- if (handle2 && isPlaceholderHandle(handle2)) {
77573
- dispositions.placeholderEcho++;
77574
- handle2 = void 0;
77575
- }
77576
- if (handle2) {
77577
- out2.push({ kind: "triage", handle: handle2, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
77578
- } else {
77579
- const raw3 = content.replace(/\s+/g, " ").trim();
77580
- if (raw3.length >= CAUSE_TEXT_MIN) {
77581
- const causeText = raw3;
77582
- out2.push({ kind: "triage", causeText, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
77583
- } else dispositions.causeTooShort++;
77584
- }
77646
+ const cause = parseCauseTag(content, sfield, verbThread, dispositions);
77647
+ if (cause) out2.push(cause);
77585
77648
  continue;
77586
77649
  }
77587
77650
  if (!isFix && NEG.test(clauseBefore(sentence, g.index))) {
@@ -77632,6 +77695,11 @@ function parseInlineTagsWithDispositions(text) {
77632
77695
  if (citeMatched.has(lm.index)) continue;
77633
77696
  const verbThread = lm[1] ?? void 0;
77634
77697
  const content = lm[2];
77698
+ if (!pass.isFix) {
77699
+ const cause = parseCauseTag(content, sfield, verbThread, dispositions);
77700
+ if (cause) out2.push(cause);
77701
+ continue;
77702
+ }
77635
77703
  HANDLE_RE.lastIndex = 0;
77636
77704
  const h = HANDLE_RE.exec(content);
77637
77705
  let handle2 = h ? h[1] ?? h[2] : void 0;
@@ -77639,16 +77707,6 @@ function parseInlineTagsWithDispositions(text) {
77639
77707
  dispositions.placeholderEcho++;
77640
77708
  handle2 = void 0;
77641
77709
  }
77642
- if (!pass.isFix) {
77643
- if (handle2) {
77644
- out2.push({ kind: "triage", handle: handle2, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
77645
- } else {
77646
- const causeText = content.replace(/\s+/g, " ").trim();
77647
- if (causeText.length >= CAUSE_TEXT_MIN)
77648
- out2.push({ kind: "triage", causeText, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
77649
- }
77650
- continue;
77651
- }
77652
77710
  if (handle2) {
77653
77711
  const inGroup = content.replace(new RegExp(HANDLE_RE.source, "gi"), " ").replace(/\s+/g, " ").trim();
77654
77712
  const r = inGroup.length >= FIX_RATIONALE_MIN ? inGroup : deriveFixRationale(sentence, lm.index, lm.index + lm[0].length);
@@ -78165,7 +78223,14 @@ function harvestInlineTags(store, text, opts) {
78165
78223
  }
78166
78224
  }
78167
78225
  } else if (tag.kind === "triage") {
78168
- const b = bindSymptom(tag.seq, tag.threadId);
78226
+ const symptomId = tag.symptomHandle ? resolveCited("triage", tag.symptomHandle) : null;
78227
+ const symptom = symptomId ? store.getNode(symptomId) : null;
78228
+ if (tag.symptomHandle && !symptom) continue;
78229
+ if (symptom && (symptom.label !== "Problem" || !symptom.description?.trim())) {
78230
+ plan.dispositions.unparseable++;
78231
+ continue;
78232
+ }
78233
+ const b = symptom ? { statement: symptom.description, problemId: symptom.id, evidence: "witnessed" } : bindSymptom(tag.seq, tag.threadId);
78169
78234
  const bound = {
78170
78235
  ...b.statement ? { symptomStatement: b.statement } : {},
78171
78236
  ...b.problemId ? { symptomProblemId: b.problemId } : {},
@@ -82804,7 +82869,7 @@ function createWatchBreaker(deps) {
82804
82869
  }
82805
82870
 
82806
82871
  // src/engine.ts
82807
- var DAEMON_VERSION = true ? "2.0.21-dev.3707" : "2.0.0-alpha.0";
82872
+ var DAEMON_VERSION = true ? "2.0.21-dev.3740" : "2.0.0-alpha.0";
82808
82873
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
82809
82874
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
82810
82875
  var GIT_OP_MUTE_MS = 4e3;
@@ -86970,6 +87035,98 @@ var ConsolidateWorker = class {
86970
87035
  }
86971
87036
  };
86972
87037
 
87038
+ // src/generalize-worker-client.ts
87039
+ import { Worker as Worker3 } from "node:worker_threads";
87040
+ var GENERALIZE_WORKER_IDLE_MS = 30 * 6e4;
87041
+ var GeneralizeWorker = class {
87042
+ worker = null;
87043
+ ready = null;
87044
+ pending = /* @__PURE__ */ new Map();
87045
+ nextId = 1;
87046
+ stopped = false;
87047
+ idleTimer = null;
87048
+ async generalize(castaliaPath, text) {
87049
+ if (this.stopped) throw new Error("generalize worker stopped");
87050
+ this.ensureWorker();
87051
+ this.touch();
87052
+ await this.ready;
87053
+ const id = this.nextId++;
87054
+ return new Promise((resolve10, reject) => {
87055
+ this.pending.set(id, { resolve: resolve10, reject });
87056
+ const req = { id, castaliaPath, text };
87057
+ this.worker.postMessage(req);
87058
+ });
87059
+ }
87060
+ touch() {
87061
+ if (this.idleTimer) clearTimeout(this.idleTimer);
87062
+ this.idleTimer = setTimeout(() => {
87063
+ if (this.pending.size > 0) return this.touch();
87064
+ const w = this.worker;
87065
+ this.worker = null;
87066
+ this.ready = null;
87067
+ void w?.terminate().catch(() => {
87068
+ });
87069
+ }, GENERALIZE_WORKER_IDLE_MS);
87070
+ this.idleTimer.unref?.();
87071
+ }
87072
+ ensureWorker() {
87073
+ if (this.worker) return;
87074
+ const worker = new Worker3(new URL("./generalize-worker.mjs", import.meta.url), {
87075
+ workerData: {},
87076
+ execArgv: process.execArgv,
87077
+ resourceLimits: workerResourceLimits()
87078
+ });
87079
+ noteIsolateSpawned();
87080
+ this.worker = worker;
87081
+ this.ready = new Promise((resolve10, reject) => {
87082
+ const onReady = (m) => {
87083
+ if (m?.ready) {
87084
+ worker.off("message", onReady);
87085
+ worker.off("error", onErr);
87086
+ resolve10();
87087
+ }
87088
+ };
87089
+ const onErr = (err2) => {
87090
+ worker.off("message", onReady);
87091
+ reject(err2);
87092
+ };
87093
+ worker.once("error", onErr);
87094
+ worker.on("message", onReady);
87095
+ });
87096
+ worker.on("message", (m) => {
87097
+ if (typeof m?.id !== "number") return;
87098
+ const p = this.pending.get(m.id);
87099
+ if (!p) return;
87100
+ this.pending.delete(m.id);
87101
+ if (m.ok && typeof m.result === "string") p.resolve(m.result);
87102
+ else p.reject(new Error(m.error ?? "generalization failed"));
87103
+ });
87104
+ const onGone = (err2) => {
87105
+ const e = err2 instanceof Error ? err2 : new Error("generalize worker exited");
87106
+ for (const [, p] of this.pending) p.reject(e);
87107
+ this.pending.clear();
87108
+ if (this.worker === worker) {
87109
+ this.worker = null;
87110
+ this.ready = null;
87111
+ }
87112
+ };
87113
+ worker.on("error", onGone);
87114
+ worker.on("exit", () => {
87115
+ noteIsolateExited();
87116
+ onGone();
87117
+ });
87118
+ }
87119
+ async stop() {
87120
+ this.stopped = true;
87121
+ if (this.idleTimer) clearTimeout(this.idleTimer);
87122
+ if (this.worker) {
87123
+ await this.worker.terminate().catch(() => {
87124
+ });
87125
+ this.worker = null;
87126
+ }
87127
+ }
87128
+ };
87129
+
86973
87130
  // src/prepare-store.ts
86974
87131
  init_src5();
86975
87132
  async function prepareStore(path2, root, workspaceId2) {
@@ -89925,6 +90082,7 @@ async function startMultiDaemon(opts = {}) {
89925
90082
  const sharedStore = openGraphStore({ path: sharedDbPath });
89926
90083
  const bundledRun = import.meta.url.endsWith(".mjs");
89927
90084
  const consolidateWorker = bundledRun && sharedDbPath !== ":memory:" ? new ConsolidateWorker() : null;
90085
+ const generalizeWorker = bundledRun ? new GeneralizeWorker() : null;
89928
90086
  startLoopLagMonitor();
89929
90087
  let baseUrl = "";
89930
90088
  const records = [];
@@ -91612,6 +91770,7 @@ async function startMultiDaemon(opts = {}) {
91612
91770
  await Promise.allSettled(attaching.values());
91613
91771
  for (const r of records) await r.engine.stop();
91614
91772
  if (consolidateWorker) await consolidateWorker.stop();
91773
+ if (generalizeWorker) await generalizeWorker.stop();
91615
91774
  try {
91616
91775
  sharedStore.close();
91617
91776
  } catch {
@@ -91640,6 +91799,33 @@ async function startMultiDaemon(opts = {}) {
91640
91799
  }
91641
91800
  });
91642
91801
  app.get("/api/sync", (c) => c.json(flushSnapshot()));
91802
+ const GENERALIZE_MAX_CHARS = 8e3;
91803
+ app.post("/api/generalize", async (c) => {
91804
+ const host = (c.req.header("host") ?? "").toLowerCase();
91805
+ if (!/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/.test(host)) return c.json({ error: "forbidden" }, 403);
91806
+ if (!(c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json")) {
91807
+ return c.json({ error: "json required" }, 415);
91808
+ }
91809
+ let body2;
91810
+ try {
91811
+ body2 = await c.req.json();
91812
+ } catch {
91813
+ return c.json({ error: "bad json" }, 400);
91814
+ }
91815
+ if (typeof body2.workspaceRoot !== "string" || typeof body2.text !== "string") {
91816
+ return c.json({ error: "workspaceRoot and text required" }, 400);
91817
+ }
91818
+ if (body2.text.length > GENERALIZE_MAX_CHARS) return c.json({ error: "text too long" }, 413);
91819
+ const entry = workspaceEntryForPath(pathResolve(body2.workspaceRoot));
91820
+ if (!entry) return c.json({ error: "unregistered workspace" }, 404);
91821
+ if (!generalizeWorker) return c.json({ error: "no worker" }, 503);
91822
+ try {
91823
+ const generalized = await generalizeWorker.generalize(workspacePaths(entry.path).castalia, body2.text);
91824
+ return c.json({ generalized });
91825
+ } catch (err2) {
91826
+ return c.json({ error: err2 instanceof Error ? err2.message : String(err2) }, 503);
91827
+ }
91828
+ });
91643
91829
  app.post("/api/tick", async (c) => {
91644
91830
  try {
91645
91831
  return c.json({ reports: Object.fromEntries(await daemon.tickAll()) });