@inerrata-corporation/errata 2.0.0-dev.34 → 2.0.0-dev.36

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
@@ -15268,6 +15268,85 @@ var init_edge_rules = __esm({
15268
15268
  }
15269
15269
  });
15270
15270
 
15271
+ // ../../packages/shared/src/symbol-intent.ts
15272
+ function extractIdentifierTokens(text) {
15273
+ const out2 = [];
15274
+ const seen = /* @__PURE__ */ new Set();
15275
+ for (const m of text.matchAll(TOKEN_RE2)) {
15276
+ if (!seen.has(m[0])) {
15277
+ seen.add(m[0]);
15278
+ out2.push(m[0]);
15279
+ }
15280
+ }
15281
+ return out2;
15282
+ }
15283
+ function summaryRejectionReason(summary, isKnownToken) {
15284
+ if (!summary.trim()) return "empty summary";
15285
+ if (summary.length > MAX_SYMBOL_SUMMARY_CHARS) return "summary too long";
15286
+ if (PATH_SHAPE_RE.test(summary)) return "contains a path shape";
15287
+ if (QUOTED_LITERAL_RE.test(summary.replace(CONTRACTION_RE, "$1$2")))
15288
+ return "contains a quoted literal";
15289
+ for (const tok of extractIdentifierTokens(summary)) {
15290
+ if (isKnownToken(tok)) return `contains verbatim identifier '${tok}'`;
15291
+ if (tok.includes(".")) {
15292
+ const segs = tok.split(".");
15293
+ for (let i2 = 0; i2 < segs.length; i2++) {
15294
+ let span = "";
15295
+ for (let j = i2; j < segs.length; j++) {
15296
+ span = span ? `${span}.${segs[j]}` : segs[j];
15297
+ if (span !== tok && span && isKnownToken(span))
15298
+ return `contains verbatim identifier '${span}'`;
15299
+ }
15300
+ }
15301
+ }
15302
+ }
15303
+ return null;
15304
+ }
15305
+ function substituteSymbolSummaries(text, summaryByToken) {
15306
+ if (summaryByToken.size === 0) return text;
15307
+ const present = extractIdentifierTokens(text).filter((t) => summaryByToken.has(t));
15308
+ present.sort((a, b) => b.length - a.length);
15309
+ let out2 = text;
15310
+ for (const key of present) {
15311
+ const re = new RegExp(`(?<![A-Za-z0-9_$.])${escapeRe2(key)}(?![A-Za-z0-9_$])`, "g");
15312
+ out2 = out2.replace(re, summaryByToken.get(key));
15313
+ }
15314
+ return out2;
15315
+ }
15316
+ function vetSidecarSummaries(sidecar) {
15317
+ if (!sidecar) return null;
15318
+ const keys = new Set(Object.keys(sidecar));
15319
+ if (keys.size === 0 || keys.size > MAX_SIDECAR_SYMBOLS) return null;
15320
+ const clean = /* @__PURE__ */ new Map();
15321
+ for (const [symbol2, entry] of Object.entries(sidecar)) {
15322
+ if (!entry || typeof entry.summary !== "string") continue;
15323
+ if (summaryRejectionReason(entry.summary, (t) => keys.has(t)) !== null) continue;
15324
+ clean.set(symbol2, entry.summary);
15325
+ }
15326
+ return clean.size > 0 ? clean : null;
15327
+ }
15328
+ var SYMBOL_SUMMARY_KINDS, MAX_SYMBOL_SUMMARY_CHARS, MAX_SIDECAR_SYMBOLS, TOKEN_RE2, RE_RESERVED, escapeRe2, PATH_SHAPE_RE, CONTRACTION_RE, QUOTED_LITERAL_RE;
15329
+ var init_symbol_intent = __esm({
15330
+ "../../packages/shared/src/symbol-intent.ts"() {
15331
+ "use strict";
15332
+ SYMBOL_SUMMARY_KINDS = [
15333
+ "function",
15334
+ "method",
15335
+ "class",
15336
+ "interface",
15337
+ "constant"
15338
+ ];
15339
+ MAX_SYMBOL_SUMMARY_CHARS = 300;
15340
+ MAX_SIDECAR_SYMBOLS = 200;
15341
+ TOKEN_RE2 = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
15342
+ RE_RESERVED = /[.*+?^${}()|[\]\\]/g;
15343
+ escapeRe2 = (s) => s.replace(RE_RESERVED, "\\$&");
15344
+ PATH_SHAPE_RE = /[\w.~-]+[/\\][\w.$~-]+/;
15345
+ CONTRACTION_RE = /([A-Za-z])'([A-Za-z])/g;
15346
+ QUOTED_LITERAL_RE = /"[^"]+"|'[^']+'|`[^`]+`/;
15347
+ }
15348
+ });
15349
+
15271
15350
  // ../../packages/shared/src/wire.ts
15272
15351
  function provenanceBand(source) {
15273
15352
  switch (source) {
@@ -15317,13 +15396,14 @@ function summarizeIngestResult(r) {
15317
15396
  ...r.patternReconciliation ? { patternReconciliation: r.patternReconciliation } : {}
15318
15397
  };
15319
15398
  }
15320
- var CLOUD_NODE_LABELS, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, MAX_NODES_PER_PAYLOAD, MAX_EDGES_PER_PAYLOAD, CastaliaIngestNodeSchema, RouteContextCountWireSchema, CastaliaIngestEdgeSchema, CastaliaIngestPayloadSchema;
15399
+ var CLOUD_NODE_LABELS, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, MAX_NODES_PER_PAYLOAD, MAX_EDGES_PER_PAYLOAD, CastaliaIngestNodeSchema, RouteContextCountWireSchema, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15321
15400
  var init_wire = __esm({
15322
15401
  "../../packages/shared/src/wire.ts"() {
15323
15402
  "use strict";
15324
15403
  init_zod();
15325
15404
  init_castalia();
15326
15405
  init_edge_rules();
15406
+ init_symbol_intent();
15327
15407
  CLOUD_NODE_LABELS = [
15328
15408
  ...SEMANTIC_NODE_LABELS,
15329
15409
  ...CONTEXT_NODE_LABELS
@@ -15373,12 +15453,24 @@ var init_wire = __esm({
15373
15453
  perContext: external_exports.record(external_exports.string(), RouteContextCountWireSchema).optional()
15374
15454
  }).optional()
15375
15455
  });
15456
+ SymbolSummarySidecarSchema = external_exports.record(
15457
+ external_exports.string().min(1).max(160),
15458
+ external_exports.object({
15459
+ kind: external_exports.enum(SYMBOL_SUMMARY_KINDS),
15460
+ summary: external_exports.string().min(1).max(MAX_SYMBOL_SUMMARY_CHARS)
15461
+ })
15462
+ ).refine((m) => Object.keys(m).length <= MAX_SIDECAR_SYMBOLS, {
15463
+ message: `symbolSummaries sidecar exceeds ${MAX_SIDECAR_SYMBOLS} entries`
15464
+ });
15376
15465
  CastaliaIngestPayloadSchema = external_exports.object({
15377
15466
  /** Client-supplied UUID — one batch; pairs with the payload digest for replay. */
15378
15467
  runId: external_exports.string().uuid(),
15379
15468
  source: external_exports.enum(INGEST_SOURCES),
15380
15469
  nodes: external_exports.array(CastaliaIngestNodeSchema).max(1e3),
15381
- edges: external_exports.array(CastaliaIngestEdgeSchema).max(5e3)
15470
+ edges: external_exports.array(CastaliaIngestEdgeSchema).max(5e3),
15471
+ /** Optional symbol→intent-summary sidecar (PP-symbol-intent). Only symbols
15472
+ * present in this payload's node text; server re-vets no-verbatim. */
15473
+ symbolSummaries: SymbolSummarySidecarSchema.optional()
15382
15474
  });
15383
15475
  }
15384
15476
  });
@@ -15443,12 +15535,16 @@ __export(src_exports, {
15443
15535
  JUSTIFICATION_EDGES: () => JUSTIFICATION_EDGES,
15444
15536
  MAX_EDGES_PER_PAYLOAD: () => MAX_EDGES_PER_PAYLOAD,
15445
15537
  MAX_NODES_PER_PAYLOAD: () => MAX_NODES_PER_PAYLOAD,
15538
+ MAX_SIDECAR_SYMBOLS: () => MAX_SIDECAR_SYMBOLS,
15539
+ MAX_SYMBOL_SUMMARY_CHARS: () => MAX_SYMBOL_SUMMARY_CHARS,
15446
15540
  PAGERANK_EXCLUSIONS: () => PAGERANK_EXCLUSIONS,
15447
15541
  PROBLEM_RESOLUTION: () => PROBLEM_RESOLUTION,
15448
15542
  RESOLUTION_EDGES: () => RESOLUTION_EDGES,
15449
15543
  RouteContextCountWireSchema: () => RouteContextCountWireSchema,
15450
15544
  SEMANTIC_NODE_LABELS: () => SEMANTIC_NODE_LABELS,
15451
15545
  STRUCTURAL_EDGES: () => STRUCTURAL_EDGES,
15546
+ SYMBOL_SUMMARY_KINDS: () => SYMBOL_SUMMARY_KINDS,
15547
+ SymbolSummarySidecarSchema: () => SymbolSummarySidecarSchema,
15452
15548
  TAXONOMY_EDGES: () => TAXONOMY_EDGES,
15453
15549
  TRANSFER_EDGES: () => TRANSFER_EDGES,
15454
15550
  TRIAGE_EDGES: () => TRIAGE_EDGES,
@@ -15468,6 +15564,7 @@ __export(src_exports, {
15468
15564
  detectStance: () => detectStance,
15469
15565
  digest: () => digest,
15470
15566
  errorSignatureEqual: () => errorSignatureEqual,
15567
+ extractIdentifierTokens: () => extractIdentifierTokens,
15471
15568
  genericCanonicalId: () => genericCanonicalId,
15472
15569
  identityId: () => identityId,
15473
15570
  isCausalProtected: () => isCausalProtected,
@@ -15493,9 +15590,12 @@ __export(src_exports, {
15493
15590
  resolveCanonicalId: () => resolveCanonicalId,
15494
15591
  sha256: () => sha256,
15495
15592
  sha256Short: () => sha256Short,
15593
+ substituteSymbolSummaries: () => substituteSymbolSummaries,
15496
15594
  summarizeIngestResult: () => summarizeIngestResult,
15595
+ summaryRejectionReason: () => summaryRejectionReason,
15497
15596
  toCloudAttrs: () => toCloudAttrs,
15498
- validateCastaliaPayload: () => validateCastaliaPayload
15597
+ validateCastaliaPayload: () => validateCastaliaPayload,
15598
+ vetSidecarSummaries: () => vetSidecarSummaries
15499
15599
  });
15500
15600
  var init_src = __esm({
15501
15601
  "../../packages/shared/src/index.ts"() {
@@ -15504,6 +15604,7 @@ var init_src = __esm({
15504
15604
  init_identity();
15505
15605
  init_wire();
15506
15606
  init_edge_rules();
15607
+ init_symbol_intent();
15507
15608
  init_hash();
15508
15609
  init_error_signature();
15509
15610
  init_canonicalize();
@@ -17246,7 +17347,7 @@ function buildPackageIndex(store) {
17246
17347
  }
17247
17348
  return idx;
17248
17349
  }
17249
- function escapeRe2(s) {
17350
+ function escapeRe3(s) {
17250
17351
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17251
17352
  }
17252
17353
  function matchPackagesInText(text, index) {
@@ -17256,7 +17357,7 @@ function matchPackagesInText(text, index) {
17256
17357
  for (const [key, node2] of index) {
17257
17358
  if (key.length < MIN_NAME_LEN) continue;
17258
17359
  if (!hay.includes(key)) continue;
17259
- const esc3 = escapeRe2(key);
17360
+ const esc3 = escapeRe3(key);
17260
17361
  const bounded = new RegExp(`(?<![\\w@/.\\-])${esc3}(?![\\w])`);
17261
17362
  if (!bounded.test(hay)) continue;
17262
17363
  const verMatch = new RegExp(`${esc3}\\s*@?\\s*v?(\\d+(?:\\.\\d+){0,2})\\b`).exec(hay);
@@ -20057,6 +20158,13 @@ function renderSnapshot(s) {
20057
20158
  lines.push(`- **Goals:** ${s.profile.goals.join("; ")}`);
20058
20159
  }
20059
20160
  lines.push("");
20161
+ if (s.pendingUpdate) {
20162
+ lines.push("### \u26A1 errata update available");
20163
+ lines.push(
20164
+ `\`${s.pendingUpdate.current}\` \u2192 \`${s.pendingUpdate.latest}\` on the \`${s.pendingUpdate.channel}\` channel. Tell your user \u2014 or run \`errata update\` yourself if they've asked you to keep errata current. It restarts the daemon; that's safe mid-session (hooks fail open, MCP reconnects).`
20165
+ );
20166
+ lines.push("");
20167
+ }
20060
20168
  if (s.needsRevisit.length > 0) {
20061
20169
  lines.push("### \u26A0\uFE0F Needs revisit \u2014 a fact these rested on changed");
20062
20170
  lines.push(
@@ -20227,6 +20335,7 @@ function assembleAgentContext(opts) {
20227
20335
  now: opts.now
20228
20336
  });
20229
20337
  if (opts.edgeElicitation) snapshot.edgeElicitation = opts.edgeElicitation;
20338
+ if (opts.pendingUpdate) snapshot.pendingUpdate = opts.pendingUpdate;
20230
20339
  if (opts.remote && opts.remote.length > 0) {
20231
20340
  const seen = /* @__PURE__ */ new Set();
20232
20341
  const remote = opts.remote.filter((n) => {
@@ -20499,6 +20608,20 @@ var init_oauth = __esm({
20499
20608
 
20500
20609
  // ../../packages/cloud-client/src/client.ts
20501
20610
  import { randomUUID } from "node:crypto";
20611
+ function sidecarForNodes(sidecar, nodes) {
20612
+ if (!sidecar) return void 0;
20613
+ const present = /* @__PURE__ */ new Set();
20614
+ for (const n of nodes) for (const tok of extractIdentifierTokens(n.description)) present.add(tok);
20615
+ const filtered = {};
20616
+ let count = 0;
20617
+ for (const [symbol2, entry] of Object.entries(sidecar)) {
20618
+ if (present.has(symbol2)) {
20619
+ filtered[symbol2] = entry;
20620
+ count++;
20621
+ }
20622
+ }
20623
+ return count > 0 ? filtered : void 0;
20624
+ }
20502
20625
  function toWirePayload(batch, runId) {
20503
20626
  const nodes = batch.nodes.map((n) => ({
20504
20627
  canonicalId: n.id,
@@ -20517,7 +20640,8 @@ function toWirePayload(batch, runId) {
20517
20640
  ...typeof e.confidence === "number" ? { confidence: clamp012(e.confidence) } : {}
20518
20641
  }
20519
20642
  }));
20520
- return { runId, source: "daemon", nodes, edges };
20643
+ const symbolSummaries = sidecarForNodes(batch.symbolSummaries, nodes);
20644
+ return { runId, source: "daemon", nodes, edges, ...symbolSummaries ? { symbolSummaries } : {} };
20521
20645
  }
20522
20646
  function clamp012(x) {
20523
20647
  return Math.max(0, Math.min(1, x));
@@ -23931,21 +24055,32 @@ function isDistinctiveIdentifier(name2) {
23931
24055
  if (/^[A-Z]/.test(name2) && name2.length >= 4) return true;
23932
24056
  return name2.length >= 8;
23933
24057
  }
23934
- function buildSymbolLexicon(store) {
24058
+ function buildSymbolLexicon(store, summariesByBodyHash2) {
23935
24059
  const lex = /* @__PURE__ */ new Map();
24060
+ const candidates = [];
23936
24061
  for (const [label, kind] of SYMBOL_LABELS) {
23937
24062
  for (const n of store.findNodesByLabel(label)) {
24063
+ const bodyHash = typeof n.attrs["bodyHash"] === "string" ? n.attrs["bodyHash"] : void 0;
24064
+ const summary = bodyHash ? summariesByBodyHash2?.get(bodyHash) : void 0;
23938
24065
  for (const name2 of [n.description, String(n.attrs["qname"] ?? "")]) {
23939
- if (name2 && isDistinctiveIdentifier(name2)) lex.set(name2, kind);
24066
+ if (name2 && isDistinctiveIdentifier(name2)) {
24067
+ if (!lex.has(name2)) lex.set(name2, { kind });
24068
+ if (summary) candidates.push([name2, summary]);
24069
+ }
23940
24070
  }
23941
24071
  }
23942
24072
  }
24073
+ for (const [name2, summary] of candidates) {
24074
+ if (summaryRejectionReason(summary, (tok) => lex.has(tok)) === null) {
24075
+ lex.get(name2).summary = summary;
24076
+ }
24077
+ }
23943
24078
  return lex;
23944
24079
  }
23945
24080
  function generalizeSymbols(text, lexicon, level = 1) {
23946
24081
  const present = [];
23947
24082
  const seen = /* @__PURE__ */ new Set();
23948
- for (const m of text.matchAll(TOKEN_RE2)) {
24083
+ for (const m of text.matchAll(TOKEN_RE3)) {
23949
24084
  const tok = m[0];
23950
24085
  if (!seen.has(tok) && lexicon.has(tok)) {
23951
24086
  seen.add(tok);
@@ -23955,17 +24090,19 @@ function generalizeSymbols(text, lexicon, level = 1) {
23955
24090
  present.sort((a, b) => b.length - a.length);
23956
24091
  let out2 = text;
23957
24092
  for (const key of present) {
23958
- const phrase = KIND_PHRASE[lexicon.get(key)][level === 1 ? "l1" : "l2"];
23959
- const re = new RegExp(`(?<![A-Za-z0-9_$.])${escapeRe3(key)}(?![A-Za-z0-9_$])`, "g");
24093
+ const entry = lexicon.get(key);
24094
+ const phrase = level === 1 && entry.summary ? entry.summary : KIND_PHRASE[entry.kind][level === 1 ? "l1" : "l2"];
24095
+ const re = new RegExp(`(?<![A-Za-z0-9_$.])${escapeRe4(key)}(?![A-Za-z0-9_$])`, "g");
23960
24096
  out2 = out2.replace(re, phrase);
23961
24097
  }
23962
24098
  return generalize(out2, { level }).text;
23963
24099
  }
23964
- var SYMBOL_LABELS, KIND_PHRASE, RE_RESERVED, escapeRe3, TOKEN_RE2;
24100
+ var SYMBOL_LABELS, KIND_PHRASE, RE_RESERVED2, escapeRe4, TOKEN_RE3;
23965
24101
  var init_generalize_graph = __esm({
23966
24102
  "src/generalize-graph.ts"() {
23967
24103
  "use strict";
23968
24104
  init_src8();
24105
+ init_src();
23969
24106
  SYMBOL_LABELS = [
23970
24107
  ["Function", "function"],
23971
24108
  ["Method", "method"],
@@ -23980,9 +24117,9 @@ var init_generalize_graph = __esm({
23980
24117
  interface: { l1: "an interface", l2: "an interface" },
23981
24118
  constant: { l1: "a constant", l2: "a value" }
23982
24119
  };
23983
- RE_RESERVED = /[.*+?^${}()|[\]\\]/g;
23984
- escapeRe3 = (s) => s.replace(RE_RESERVED, "\\$&");
23985
- TOKEN_RE2 = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
24120
+ RE_RESERVED2 = /[.*+?^${}()|[\]\\]/g;
24121
+ escapeRe4 = (s) => s.replace(RE_RESERVED2, "\\$&");
24122
+ TOKEN_RE3 = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
23986
24123
  }
23987
24124
  });
23988
24125
 
@@ -24087,7 +24224,7 @@ async function dualAugment(opts) {
24087
24224
  let seedProvenance = "local";
24088
24225
  const calls = [];
24089
24226
  try {
24090
- const lexicon = buildSymbolLexicon(store);
24227
+ const lexicon = buildSymbolLexicon(store, opts.summaries);
24091
24228
  const hardIds = [.../* @__PURE__ */ new Set([...seedNode ? [seedNode.id] : [], ...localHits.map((h) => h.id)])].filter((nid) => {
24092
24229
  const n = seedNode && nid === seedNode.id ? seedNode : store.getNode(nid);
24093
24230
  return !!n && isHardBridge(n);
@@ -25912,6 +26049,177 @@ var init_src10 = __esm({
25912
26049
  }
25913
26050
  });
25914
26051
 
26052
+ // src/symbol-summaries.ts
26053
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
26054
+ import { join as join6 } from "node:path";
26055
+ function symbolSummariesPath(configDir) {
26056
+ return join6(configDir, "symbol-summaries.json");
26057
+ }
26058
+ function loadSymbolSummaryCache(configDir) {
26059
+ const p = symbolSummariesPath(configDir);
26060
+ if (existsSync7(p)) {
26061
+ try {
26062
+ const raw2 = JSON.parse(readFileSync5(p, "utf8"));
26063
+ if (raw2 && raw2.version === 1 && raw2.entries && typeof raw2.entries === "object") {
26064
+ return { version: 1, entries: raw2.entries };
26065
+ }
26066
+ } catch {
26067
+ }
26068
+ }
26069
+ return { version: 1, entries: {} };
26070
+ }
26071
+ function saveSymbolSummaryCache(configDir, cache) {
26072
+ writeFileSync6(symbolSummariesPath(configDir), JSON.stringify(cache, null, 2), "utf8");
26073
+ }
26074
+ function summariesByBodyHash(cache) {
26075
+ const m = /* @__PURE__ */ new Map();
26076
+ for (const [bodyHash, e] of Object.entries(cache.entries)) {
26077
+ if (!e.rejected && typeof e.summary === "string" && e.summary.trim()) m.set(bodyHash, e.summary);
26078
+ }
26079
+ return m;
26080
+ }
26081
+ function parseSummaryJson(text, expected) {
26082
+ const start2 = text.indexOf("[");
26083
+ const end = text.lastIndexOf("]");
26084
+ if (start2 < 0 || end <= start2) return new Array(expected).fill(null);
26085
+ let arr;
26086
+ try {
26087
+ arr = JSON.parse(text.slice(start2, end + 1));
26088
+ } catch {
26089
+ return new Array(expected).fill(null);
26090
+ }
26091
+ if (!Array.isArray(arr)) return new Array(expected).fill(null);
26092
+ const out2 = [];
26093
+ for (let i2 = 0; i2 < expected; i2++) {
26094
+ const v = arr[i2];
26095
+ out2.push(typeof v === "string" && v.trim() ? v.trim().slice(0, MAX_SYMBOL_SUMMARY_CHARS) : null);
26096
+ }
26097
+ return out2;
26098
+ }
26099
+ function haikuIntentSummarizer(apiKey) {
26100
+ if (!apiKey) return void 0;
26101
+ return async (symbols) => {
26102
+ try {
26103
+ const listing = symbols.map(
26104
+ (s, i2) => `### ${i2 + 1}. kind: ${s.kind}
26105
+ name: ${s.name}
26106
+ ${s.snippet ? `\`\`\`
26107
+ ${s.snippet}
26108
+ \`\`\`` : "(no source available \u2014 describe from the name)"}`
26109
+ ).join("\n\n");
26110
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
26111
+ method: "POST",
26112
+ headers: {
26113
+ "x-api-key": apiKey,
26114
+ "anthropic-version": "2023-06-01",
26115
+ "content-type": "application/json"
26116
+ },
26117
+ body: JSON.stringify({
26118
+ model: SUMMARY_MODEL,
26119
+ max_tokens: 2e3,
26120
+ messages: [{ role: "user", content: `${SUMMARY_PROMPT}
26121
+
26122
+ SYMBOLS:
26123
+ ${listing}` }]
26124
+ })
26125
+ });
26126
+ if (!resp.ok) return symbols.map(() => null);
26127
+ const json2 = await resp.json();
26128
+ return parseSummaryJson(json2.content?.find((c) => c.type === "text")?.text ?? "", symbols.length);
26129
+ } catch {
26130
+ return symbols.map(() => null);
26131
+ }
26132
+ };
26133
+ }
26134
+ function envIntentSummarizer() {
26135
+ if (process.env["ERRATA_SYMBOL_SUMMARIES"] !== "1") return void 0;
26136
+ return haikuIntentSummarizer(process.env["ANTHROPIC_API_KEY"] ?? null);
26137
+ }
26138
+ function readSnippet(workspaceRoot, attrs) {
26139
+ const relPath = attrs["relPath"];
26140
+ const startByte = attrs["startByte"];
26141
+ const endByte = attrs["endByte"];
26142
+ if (typeof relPath !== "string" || typeof startByte !== "number" || typeof endByte !== "number") {
26143
+ return void 0;
26144
+ }
26145
+ try {
26146
+ const buf = readFileSync5(join6(workspaceRoot, ...relPath.split("/")));
26147
+ const slice = buf.subarray(Math.max(0, startByte), Math.min(buf.length, endByte));
26148
+ const text = slice.toString("utf8");
26149
+ return text.length > MAX_SNIPPET_CHARS ? text.slice(0, MAX_SNIPPET_CHARS) : text;
26150
+ } catch {
26151
+ return void 0;
26152
+ }
26153
+ }
26154
+ async function runSymbolSummarySweep(store, workspaceRoot, configDir, summarizer, opts = {}) {
26155
+ const maxSymbols = opts.maxSymbols ?? DEFAULT_MAX_SYMBOLS_PER_SWEEP;
26156
+ const batchSize = Math.max(1, opts.batchSize ?? DEFAULT_BATCH_SIZE);
26157
+ const cache = loadSymbolSummaryCache(configDir);
26158
+ const candidates = [];
26159
+ const seen = /* @__PURE__ */ new Set();
26160
+ for (const [label, kind] of SUMMARY_LABELS) {
26161
+ for (const n of store.findNodesByLabel(label)) {
26162
+ const bodyHash = n.attrs["bodyHash"];
26163
+ if (typeof bodyHash !== "string" || !bodyHash || seen.has(bodyHash)) continue;
26164
+ if (cache.entries[bodyHash]) continue;
26165
+ const name2 = n.description;
26166
+ if (!name2 || !isDistinctiveIdentifier(name2)) continue;
26167
+ seen.add(bodyHash);
26168
+ const snippet = readSnippet(workspaceRoot, n.attrs);
26169
+ candidates.push({ bodyHash, name: name2, kind, ...snippet ? { snippet } : {} });
26170
+ }
26171
+ }
26172
+ const todo = candidates.slice(0, maxSymbols);
26173
+ const remaining = candidates.length - todo.length;
26174
+ if (todo.length === 0) return { generated: 0, rejected: 0, remaining };
26175
+ const lexicon = buildSymbolLexicon(store);
26176
+ const isKnown = (tok) => lexicon.has(tok);
26177
+ let generated = 0;
26178
+ let rejected = 0;
26179
+ for (let i2 = 0; i2 < todo.length; i2 += batchSize) {
26180
+ const batch = todo.slice(i2, i2 + batchSize);
26181
+ const phrases = await summarizer(batch);
26182
+ const now = Date.now();
26183
+ for (let j = 0; j < batch.length; j++) {
26184
+ const sym = batch[j];
26185
+ const phrase = phrases[j];
26186
+ if (phrase == null) continue;
26187
+ if (summaryRejectionReason(phrase, isKnown) === null) {
26188
+ cache.entries[sym.bodyHash] = { summary: phrase, model: SUMMARY_MODEL, createdAt: now };
26189
+ generated++;
26190
+ } else {
26191
+ cache.entries[sym.bodyHash] = { rejected: true, model: SUMMARY_MODEL, createdAt: now };
26192
+ rejected++;
26193
+ }
26194
+ }
26195
+ saveSymbolSummaryCache(configDir, cache);
26196
+ }
26197
+ return { generated, rejected, remaining };
26198
+ }
26199
+ var SUMMARY_LABELS, MAX_SNIPPET_CHARS, DEFAULT_MAX_SYMBOLS_PER_SWEEP, DEFAULT_BATCH_SIZE, SUMMARY_MODEL, SUMMARY_PROMPT;
26200
+ var init_symbol_summaries = __esm({
26201
+ "src/symbol-summaries.ts"() {
26202
+ "use strict";
26203
+ init_src();
26204
+ init_generalize_graph();
26205
+ SUMMARY_LABELS = [
26206
+ ["Function", "function"],
26207
+ ["Method", "method"],
26208
+ ["Class", "class"]
26209
+ ];
26210
+ MAX_SNIPPET_CHARS = 1500;
26211
+ DEFAULT_MAX_SYMBOLS_PER_SWEEP = 64;
26212
+ DEFAULT_BATCH_SIZE = 16;
26213
+ SUMMARY_MODEL = "claude-haiku-4-5-20251001";
26214
+ SUMMARY_PROMPT = `For each code symbol below, write ONE short English phrase stating its intent/role, shaped like "a <kind> that <does what>" (e.g. "a function that binds the server's listen port").
26215
+ HARD RULES \u2014 a violating phrase is discarded:
26216
+ - NEVER include any identifier, symbol name, variable, type name, file path, or module name from the code \u2014 describe meaning in plain English words only.
26217
+ - NEVER include quoted strings, literals, numbers copied from the code, or code syntax.
26218
+ - One phrase per symbol, under 200 characters, lowercase start, no trailing period.
26219
+ Output a JSON array of strings, one per symbol in the SAME ORDER (use null for a symbol you cannot describe). Output JSON only, no prose.`;
26220
+ }
26221
+ });
26222
+
25915
26223
  // ../../packages/indexer/src/simhash.ts
25916
26224
  function fnv1a64(s) {
25917
26225
  let h = FNV_OFFSET;
@@ -26135,9 +26443,9 @@ var init_identity2 = __esm({
26135
26443
  });
26136
26444
 
26137
26445
  // ../../packages/indexer/src/pipeline.ts
26138
- import { appendFileSync, readFileSync as readFileSync5, statSync } from "node:fs";
26446
+ import { appendFileSync, readFileSync as readFileSync6, statSync } from "node:fs";
26139
26447
  import { readdir } from "node:fs/promises";
26140
- import { extname, join as join6, relative as relative2, sep } from "node:path";
26448
+ import { extname, join as join7, relative as relative2, sep } from "node:path";
26141
26449
  import { createHash as createHash3 } from "node:crypto";
26142
26450
  import { execFileSync } from "node:child_process";
26143
26451
  function nowTs() {
@@ -26253,7 +26561,7 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
26253
26561
  for (const rel of [...changedRelPaths]) {
26254
26562
  let h;
26255
26563
  try {
26256
- h = createHash3("sha256").update(readFileSync5(join6(rootPath, rel))).digest("hex");
26564
+ h = createHash3("sha256").update(readFileSync6(join7(rootPath, rel))).digest("hex");
26257
26565
  } catch {
26258
26566
  continue;
26259
26567
  }
@@ -26300,13 +26608,13 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
26300
26608
  let parsedFiles = 0;
26301
26609
  for (const rel of changedRelPaths) {
26302
26610
  if (parsedFiles++ > 0) await new Promise((r2) => setImmediate(r2));
26303
- const abs = join6(rootPath, ...rel.split("/"));
26611
+ const abs = join7(rootPath, ...rel.split("/"));
26304
26612
  const ext = extname(abs).toLowerCase();
26305
26613
  const provider = providers.find((p) => p.fileExtensions.includes(ext));
26306
26614
  if (!provider) continue;
26307
26615
  let symbols;
26308
26616
  try {
26309
- symbols = provider.extractSymbols(readFileSync5(abs, "utf8"), abs);
26617
+ symbols = provider.extractSymbols(readFileSync6(abs, "utf8"), abs);
26310
26618
  } catch {
26311
26619
  continue;
26312
26620
  }
@@ -26606,7 +26914,7 @@ async function runIndexer(store, opts) {
26606
26914
  report.filesSkipped++;
26607
26915
  continue;
26608
26916
  }
26609
- source = readFileSync5(f.absPath, "utf8");
26917
+ source = readFileSync6(f.absPath, "utf8");
26610
26918
  } catch {
26611
26919
  report.filesSkipped++;
26612
26920
  continue;
@@ -26658,7 +26966,7 @@ async function runIndexer(store, opts) {
26658
26966
  const depth = fileNode.relPath.split("/").length;
26659
26967
  if (depth !== 3) continue;
26660
26968
  try {
26661
- const pkg = JSON.parse(readFileSync5(fileNode.absPath, "utf8"));
26969
+ const pkg = JSON.parse(readFileSync6(fileNode.absPath, "utf8"));
26662
26970
  if (!pkg.name) continue;
26663
26971
  const pkgDir = fileNode.relPath.replace(/\/package\.json$/, "");
26664
26972
  const candidates = [
@@ -27040,7 +27348,7 @@ async function scan(root, current, ignores, providers, out2) {
27040
27348
  for (const ent of entries) {
27041
27349
  if (ignores.has(ent.name)) continue;
27042
27350
  if (ent.name.startsWith(".") && ent.name !== ".") continue;
27043
- const abs = join6(current, ent.name);
27351
+ const abs = join7(current, ent.name);
27044
27352
  if (ent.isDirectory()) {
27045
27353
  await scan(root, abs, ignores, providers, out2);
27046
27354
  } else if (ent.isFile()) {
@@ -27086,7 +27394,7 @@ function gitListFiles(root, ignores, providers) {
27086
27394
  if (segs.some((s) => ignores.has(s) || s.startsWith(".") && s.length > 1)) {
27087
27395
  continue;
27088
27396
  }
27089
- const abs = join6(root, rel);
27397
+ const abs = join7(root, rel);
27090
27398
  let size;
27091
27399
  try {
27092
27400
  size = statSync(abs).size;
@@ -27109,7 +27417,7 @@ function upsertFile(store, id, f, workspaceId2) {
27109
27417
  const now = nowTs();
27110
27418
  let contentHash;
27111
27419
  try {
27112
- contentHash = createHash3("sha256").update(readFileSync5(f.absPath)).digest("hex");
27420
+ contentHash = createHash3("sha256").update(readFileSync6(f.absPath)).digest("hex");
27113
27421
  } catch {
27114
27422
  }
27115
27423
  const node2 = {
@@ -31471,8 +31779,8 @@ ${JSON.stringify(symbolNames, null, 2)}`);
31471
31779
 
31472
31780
  // ../../packages/indexer/src/languages/tree-sitter-loader.ts
31473
31781
  import { fileURLToPath } from "node:url";
31474
- import { dirname as dirname4, join as join7 } from "node:path";
31475
- import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
31782
+ import { dirname as dirname4, join as join8 } from "node:path";
31783
+ import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
31476
31784
  import { createRequire as createRequire2 } from "node:module";
31477
31785
  function entryDir() {
31478
31786
  try {
@@ -31486,27 +31794,27 @@ function entryDir() {
31486
31794
  }
31487
31795
  function findWasmDir() {
31488
31796
  const here = entryDir();
31489
- const seaWasm = join7(here, "resources", "wasm");
31490
- if (existsSync7(join7(seaWasm, "tree-sitter-typescript.wasm"))) return seaWasm;
31797
+ const seaWasm = join8(here, "resources", "wasm");
31798
+ if (existsSync8(join8(seaWasm, "tree-sitter-typescript.wasm"))) return seaWasm;
31491
31799
  let dir = here;
31492
31800
  for (let i2 = 0; i2 < 6; i2++) {
31493
- const flat = join7(
31801
+ const flat = join8(
31494
31802
  dir,
31495
31803
  "node_modules",
31496
31804
  "@vscode",
31497
31805
  "tree-sitter-wasm",
31498
31806
  "wasm"
31499
31807
  );
31500
- if (existsSync7(join7(flat, "tree-sitter-typescript.wasm"))) return flat;
31808
+ if (existsSync8(join8(flat, "tree-sitter-typescript.wasm"))) return flat;
31501
31809
  dir = dirname4(dir);
31502
31810
  }
31503
31811
  let root = here;
31504
31812
  for (let i2 = 0; i2 < 6; i2++) {
31505
- const pnpmDir = join7(root, "node_modules", ".pnpm");
31506
- if (existsSync7(pnpmDir)) {
31813
+ const pnpmDir = join8(root, "node_modules", ".pnpm");
31814
+ if (existsSync8(pnpmDir)) {
31507
31815
  for (const entry of readdirSync2(pnpmDir)) {
31508
31816
  if (entry.startsWith("@vscode+tree-sitter-wasm@")) {
31509
- const candidate = join7(
31817
+ const candidate = join8(
31510
31818
  pnpmDir,
31511
31819
  entry,
31512
31820
  "node_modules",
@@ -31514,7 +31822,7 @@ function findWasmDir() {
31514
31822
  "tree-sitter-wasm",
31515
31823
  "wasm"
31516
31824
  );
31517
- if (existsSync7(join7(candidate, "tree-sitter-typescript.wasm"))) {
31825
+ if (existsSync8(join8(candidate, "tree-sitter-typescript.wasm"))) {
31518
31826
  return candidate;
31519
31827
  }
31520
31828
  }
@@ -31528,27 +31836,27 @@ function findWasmDir() {
31528
31836
  }
31529
31837
  function findRuntimeDir() {
31530
31838
  const here = entryDir();
31531
- const seaRuntime = join7(here, "resources", "wasm");
31532
- if (existsSync7(join7(seaRuntime, "web-tree-sitter.wasm"))) return seaRuntime;
31839
+ const seaRuntime = join8(here, "resources", "wasm");
31840
+ if (existsSync8(join8(seaRuntime, "web-tree-sitter.wasm"))) return seaRuntime;
31533
31841
  let dir = here;
31534
31842
  for (let i2 = 0; i2 < 6; i2++) {
31535
- const flat = join7(dir, "node_modules", "web-tree-sitter");
31536
- if (existsSync7(join7(flat, "web-tree-sitter.wasm"))) return flat;
31843
+ const flat = join8(dir, "node_modules", "web-tree-sitter");
31844
+ if (existsSync8(join8(flat, "web-tree-sitter.wasm"))) return flat;
31537
31845
  dir = dirname4(dir);
31538
31846
  }
31539
31847
  let root = here;
31540
31848
  for (let i2 = 0; i2 < 6; i2++) {
31541
- const pnpmDir = join7(root, "node_modules", ".pnpm");
31542
- if (existsSync7(pnpmDir)) {
31849
+ const pnpmDir = join8(root, "node_modules", ".pnpm");
31850
+ if (existsSync8(pnpmDir)) {
31543
31851
  for (const entry of readdirSync2(pnpmDir)) {
31544
31852
  if (entry.startsWith("web-tree-sitter@")) {
31545
- const candidate = join7(
31853
+ const candidate = join8(
31546
31854
  pnpmDir,
31547
31855
  entry,
31548
31856
  "node_modules",
31549
31857
  "web-tree-sitter"
31550
31858
  );
31551
- if (existsSync7(join7(candidate, "web-tree-sitter.wasm"))) {
31859
+ if (existsSync8(join8(candidate, "web-tree-sitter.wasm"))) {
31552
31860
  return candidate;
31553
31861
  }
31554
31862
  }
@@ -31565,7 +31873,7 @@ async function loadWebTreeSitter() {
31565
31873
  void err2;
31566
31874
  }
31567
31875
  const here = entryDir();
31568
- const seaResourceBase = join7(here, "resources", "_resolve.js");
31876
+ const seaResourceBase = join8(here, "resources", "_resolve.js");
31569
31877
  const resourceRequire = createRequire2(seaResourceBase);
31570
31878
  return resourceRequire("web-tree-sitter");
31571
31879
  }
@@ -31580,9 +31888,9 @@ async function ensureTreeSitterReady() {
31580
31888
  await Parser2.init({
31581
31889
  locateFile: (name2) => {
31582
31890
  if (name2 === "tree-sitter.wasm" || name2 === "web-tree-sitter.wasm") {
31583
- return join7(runtime, name2);
31891
+ return join8(runtime, name2);
31584
31892
  }
31585
- return join7(grammars, name2);
31893
+ return join8(grammars, name2);
31586
31894
  }
31587
31895
  });
31588
31896
  })();
@@ -31594,7 +31902,7 @@ async function loadGrammar(name2) {
31594
31902
  if (cached2) return cached2;
31595
31903
  if (!languageClass) throw new Error("tree-sitter not initialized");
31596
31904
  const grammars = findWasmDir();
31597
- const lang = await languageClass.load(join7(grammars, `${name2}.wasm`));
31905
+ const lang = await languageClass.load(join8(grammars, `${name2}.wasm`));
31598
31906
  grammarCache.set(name2, lang);
31599
31907
  return lang;
31600
31908
  }
@@ -34496,7 +34804,7 @@ var init_src11 = __esm({
34496
34804
  // src/reconcile.ts
34497
34805
  import { execFileSync as execFileSync2 } from "node:child_process";
34498
34806
  import { readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
34499
- import { join as join8, relative as relative3, sep as sep2 } from "node:path";
34807
+ import { join as join9, relative as relative3, sep as sep2 } from "node:path";
34500
34808
  function gitSourceFiles(root) {
34501
34809
  let stdout;
34502
34810
  try {
@@ -34511,7 +34819,7 @@ function gitSourceFiles(root) {
34511
34819
  const out2 = [];
34512
34820
  for (const rel of stdout.split("\0")) {
34513
34821
  if (!rel || !SOURCE_RE.test(rel)) continue;
34514
- const abs = join8(root, rel);
34822
+ const abs = join9(root, rel);
34515
34823
  if (IGNORED.test(abs)) continue;
34516
34824
  out2.push(abs);
34517
34825
  }
@@ -34526,7 +34834,7 @@ function* walkSource(dir) {
34526
34834
  }
34527
34835
  for (const e of entries) {
34528
34836
  const name2 = String(e.name);
34529
- const full = join8(dir, name2);
34837
+ const full = join9(dir, name2);
34530
34838
  if (IGNORED.test(full)) continue;
34531
34839
  if (e.isDirectory()) yield* walkSource(full);
34532
34840
  else if (SOURCE_RE.test(name2)) yield full;
@@ -34604,7 +34912,7 @@ __export(mcp_exports, {
34604
34912
  runTool: () => runTool,
34605
34913
  searchGraph: () => searchGraph
34606
34914
  });
34607
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
34915
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
34608
34916
  import { resolve } from "node:path";
34609
34917
  function collectEnrichmentPulls(store, opts = {}) {
34610
34918
  const pending = store.findNodesByLabel("Solution").filter((s) => s.attrs["enrichmentPending"] === true).filter((s) => !opts.onlyUnsurfaced || s.attrs["enrichmentSurfaced"] !== true);
@@ -34717,10 +35025,10 @@ function showNode(store, node2, maxLines) {
34717
35025
  if (!relPath) return { found: false, reason: "no file owner for node" };
34718
35026
  const workspaceRoot = process.cwd();
34719
35027
  const absPath = resolve(workspaceRoot, relPath);
34720
- if (!existsSync8(absPath)) {
35028
+ if (!existsSync9(absPath)) {
34721
35029
  return { found: false, reason: `file not found on disk: ${absPath}` };
34722
35030
  }
34723
- const source = readFileSync6(absPath, "utf8");
35031
+ const source = readFileSync7(absPath, "utf8");
34724
35032
  const attrs = node2.attrs;
34725
35033
  let startByte = attrs["bodyStartByte"] ?? attrs["startByte"];
34726
35034
  let endByte = attrs["bodyEndByte"] ?? attrs["endByte"];
@@ -34933,7 +35241,20 @@ function buildToolContext() {
34933
35241
  async function runMcpServer(workspaceRoot) {
34934
35242
  const paths = workspacePaths(workspaceRoot);
34935
35243
  const store = openGraphStore({ path: paths.castalia });
34936
- const handle2 = createMcpHandler(store, buildToolContext());
35244
+ let summariesMemo = null;
35245
+ const symbolSummaries = () => {
35246
+ const now = Date.now();
35247
+ if (!summariesMemo || now - summariesMemo.at > 6e4) {
35248
+ let map2 = /* @__PURE__ */ new Map();
35249
+ try {
35250
+ map2 = summariesByBodyHash(loadSymbolSummaryCache(paths.configDir));
35251
+ } catch {
35252
+ }
35253
+ summariesMemo = { at: now, map: map2 };
35254
+ }
35255
+ return summariesMemo.map;
35256
+ };
35257
+ const handle2 = createMcpHandler(store, { ...buildToolContext(), symbolSummaries });
34937
35258
  process.stdin.setEncoding("utf8");
34938
35259
  let buffer = "";
34939
35260
  process.stdin.on("data", (chunk) => {
@@ -34972,6 +35293,7 @@ var init_mcp = __esm({
34972
35293
  init_src2();
34973
35294
  init_src10();
34974
35295
  init_paths();
35296
+ init_symbol_summaries();
34975
35297
  init_webui();
34976
35298
  init_reconcile();
34977
35299
  init_agent_signals();
@@ -35044,7 +35366,14 @@ var init_mcp = __esm({
35044
35366
  score: h.pageRank,
35045
35367
  hops: 0
35046
35368
  }));
35047
- const dual = await dualAugment({ store, cloud: ctx.cloud, localHits, queryText: query, limit });
35369
+ const dual = await dualAugment({
35370
+ store,
35371
+ cloud: ctx.cloud,
35372
+ localHits,
35373
+ queryText: query,
35374
+ limit,
35375
+ ...ctx.symbolSummaries ? { summaries: ctx.symbolSummaries() } : {}
35376
+ });
35048
35377
  if (dual.status === "merged") {
35049
35378
  return {
35050
35379
  query,
@@ -35209,7 +35538,8 @@ var init_mcp = __esm({
35209
35538
  cloud: ctx.cloud,
35210
35539
  localHits,
35211
35540
  seedNode,
35212
- limit: args2["limit"] != null ? Number(args2["limit"]) : 20
35541
+ limit: args2["limit"] != null ? Number(args2["limit"]) : 20,
35542
+ ...ctx.symbolSummaries ? { summaries: ctx.symbolSummaries() } : {}
35213
35543
  });
35214
35544
  if (dual.status === "merged") {
35215
35545
  return {
@@ -35724,7 +36054,14 @@ var init_mcp = __esm({
35724
36054
  score: h.score,
35725
36055
  hops: 0
35726
36056
  }));
35727
- const dual = await dualAugment({ store, cloud: ctx.cloud, localHits, seedNode: node2, limit });
36057
+ const dual = await dualAugment({
36058
+ store,
36059
+ cloud: ctx.cloud,
36060
+ localHits,
36061
+ seedNode: node2,
36062
+ limit,
36063
+ ...ctx.symbolSummaries ? { summaries: ctx.symbolSummaries() } : {}
36064
+ });
35728
36065
  if (dual.status === "merged") {
35729
36066
  return {
35730
36067
  seedId: id,
@@ -35915,7 +36252,7 @@ var init_mcp = __esm({
35915
36252
  inputSchema: { type: "object", properties: {} },
35916
36253
  handler: () => {
35917
36254
  const path2 = sharedStorePath();
35918
- if (!existsSync8(path2)) return { count: 0, pending: [] };
36255
+ if (!existsSync9(path2)) return { count: 0, pending: [] };
35919
36256
  const shared = openGraphStore({ path: path2 });
35920
36257
  try {
35921
36258
  const pending = pendingAbstractions(shared);
@@ -35946,7 +36283,7 @@ var init_mcp = __esm({
35946
36283
  },
35947
36284
  handler: (args2) => {
35948
36285
  const path2 = sharedStorePath();
35949
- if (!existsSync8(path2)) return { count: 0, routes: [] };
36286
+ if (!existsSync9(path2)) return { count: 0, routes: [] };
35950
36287
  const shared = openGraphStore({ path: path2 });
35951
36288
  try {
35952
36289
  const result = triageOf(shared, {
@@ -36331,8 +36668,8 @@ __export(vfile_exports, {
36331
36668
  resolvePath: () => resolvePath,
36332
36669
  segmentsOf: () => segmentsOf
36333
36670
  });
36334
- import { writeFileSync as writeFileSync6 } from "node:fs";
36335
- import { join as join9, resolve as resolve2, sep as sep3 } from "node:path";
36671
+ import { writeFileSync as writeFileSync7 } from "node:fs";
36672
+ import { join as join10, resolve as resolve2, sep as sep3 } from "node:path";
36336
36673
  function segmentsOf(rawPath) {
36337
36674
  let p = rawPath.replace(/\\/g, "/");
36338
36675
  p = p.replace(/^.*\.errata\/g\//, "").replace(/^\/?g\//, "").replace(/^\/+/, "");
@@ -36406,14 +36743,14 @@ async function renderVFile(rawPath, store, ctx = {}) {
36406
36743
  }
36407
36744
  async function materializeVFile(rawPath, workspaceRoot, store, ctx = {}) {
36408
36745
  const segs = segmentsOf(rawPath);
36409
- const gRoot = join9(workspaceRoot, ".errata", "g");
36746
+ const gRoot = join10(workspaceRoot, ".errata", "g");
36410
36747
  const abs = resolve2(gRoot, ...segs.length ? segs : ["index"]);
36411
36748
  if (abs !== gRoot && !abs.startsWith(gRoot + sep3)) {
36412
36749
  throw new Error(`refusing to materialize outside .errata/g: ${rawPath}`);
36413
36750
  }
36414
36751
  const text = await renderVFile(rawPath, store, ctx);
36415
36752
  ensureParent(abs);
36416
- writeFileSync6(abs, text, "utf8");
36753
+ writeFileSync7(abs, text, "utf8");
36417
36754
  return abs;
36418
36755
  }
36419
36756
  async function materializeOverview(workspaceRoot, store, ctx = {}) {
@@ -36498,25 +36835,25 @@ the tool surface; the path is the thin, composable channel.
36498
36835
 
36499
36836
  // src/outbox.ts
36500
36837
  import {
36501
- existsSync as existsSync9,
36838
+ existsSync as existsSync10,
36502
36839
  readdirSync as readdirSync4,
36503
- readFileSync as readFileSync7,
36840
+ readFileSync as readFileSync8,
36504
36841
  renameSync,
36505
36842
  unlinkSync,
36506
- writeFileSync as writeFileSync7
36843
+ writeFileSync as writeFileSync8
36507
36844
  } from "node:fs";
36508
- import { join as join10 } from "node:path";
36845
+ import { join as join11 } from "node:path";
36509
36846
  import { createHash as createHash11 } from "node:crypto";
36510
36847
  function enqueueOutbox(paths, payload) {
36511
36848
  ensureDir(paths.outbox);
36512
36849
  const id = createHash11("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
36513
- const file2 = join10(paths.outbox, `${Date.now()}-${id}.json`);
36514
- writeFileSync7(file2, JSON.stringify(payload, null, 2), "utf8");
36850
+ const file2 = join11(paths.outbox, `${Date.now()}-${id}.json`);
36851
+ writeFileSync8(file2, JSON.stringify(payload, null, 2), "utf8");
36515
36852
  return file2;
36516
36853
  }
36517
36854
  async function flushOutbox(paths, client, store) {
36518
36855
  ensureDir(paths.outbox);
36519
- const entries = existsSync9(paths.outbox) ? readdirSync4(paths.outbox) : [];
36856
+ const entries = existsSync10(paths.outbox) ? readdirSync4(paths.outbox) : [];
36520
36857
  let uploaded = 0;
36521
36858
  let failed = 0;
36522
36859
  const quarantine = (abs, reason) => {
@@ -36527,10 +36864,10 @@ async function flushOutbox(paths, client, store) {
36527
36864
  }
36528
36865
  };
36529
36866
  for (const f of entries.filter((e) => e.endsWith(".json")).sort()) {
36530
- const abs = join10(paths.outbox, f);
36867
+ const abs = join11(paths.outbox, f);
36531
36868
  let payload;
36532
36869
  try {
36533
- payload = JSON.parse(readFileSync7(abs, "utf8"));
36870
+ payload = JSON.parse(readFileSync8(abs, "utf8"));
36534
36871
  } catch {
36535
36872
  failed++;
36536
36873
  quarantine(abs, "unparseable JSON");
@@ -36558,7 +36895,7 @@ async function flushOutbox(paths, client, store) {
36558
36895
  }
36559
36896
  }
36560
36897
  }
36561
- const remainingFiles = existsSync9(paths.outbox) ? readdirSync4(paths.outbox).filter((e) => e.endsWith(".json")) : [];
36898
+ const remainingFiles = existsSync10(paths.outbox) ? readdirSync4(paths.outbox).filter((e) => e.endsWith(".json")) : [];
36562
36899
  return { uploaded, failed, remaining: remainingFiles.length };
36563
36900
  }
36564
36901
  var init_outbox = __esm({
@@ -36577,7 +36914,7 @@ __export(webui_exports, {
36577
36914
  findFileByPath: () => findFileByPath,
36578
36915
  recallForFile: () => recallForFile
36579
36916
  });
36580
- import { join as join11 } from "node:path";
36917
+ import { join as join12 } from "node:path";
36581
36918
  function fileUriToFsPath(raw2) {
36582
36919
  let p = raw2.replace(/^file:\/\//, "").replace(/\\/g, "/");
36583
36920
  if (/^\/[A-Za-z]:/.test(p)) p = p.slice(1);
@@ -36938,7 +37275,7 @@ function buildWebUi(deps) {
36938
37275
  } catch {
36939
37276
  return c.json({});
36940
37277
  }
36941
- const block = readManagedBlockWithHash(join11(deps.paths.root, "AGENTS.md"));
37278
+ const block = readManagedBlockWithHash(join12(deps.paths.root, "AGENTS.md"));
36942
37279
  const decision = decideContextInject({
36943
37280
  event: String(body2["hook_event_name"] ?? "UserPromptSubmit"),
36944
37281
  source: String(body2["source"] ?? ""),
@@ -37540,12 +37877,12 @@ var init_report_render = __esm({
37540
37877
 
37541
37878
  // src/cli.ts
37542
37879
  init_src5();
37543
- import { closeSync as closeSync2, existsSync as existsSync19, openSync as openSync2, readFileSync as readFileSync18, renameSync as renameSync3, statSync as statSync5 } from "node:fs";
37544
- import { join as join22 } from "node:path";
37880
+ import { closeSync as closeSync2, existsSync as existsSync20, openSync as openSync2, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync5 } from "node:fs";
37881
+ import { join as join23 } from "node:path";
37545
37882
  import { spawn as spawn3 } from "node:child_process";
37546
37883
 
37547
37884
  // src/daemon.ts
37548
- import { existsSync as existsSync15, writeFileSync as writeFileSync12 } from "node:fs";
37885
+ import { existsSync as existsSync16, writeFileSync as writeFileSync13 } from "node:fs";
37549
37886
 
37550
37887
  // ../../node_modules/.pnpm/@hono+node-server@1.19.11_hono@4.12.8/node_modules/@hono/node-server/dist/index.mjs
37551
37888
  import { createServer as createServerHTTP } from "http";
@@ -38125,8 +38462,8 @@ init_config();
38125
38462
 
38126
38463
  // src/engine.ts
38127
38464
  import { execFileSync as execFileSync3 } from "node:child_process";
38128
- import { existsSync as existsSync14, statSync as statSync4, appendFileSync as appendFileSync2, readdirSync as readdirSync7, renameSync as renameSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
38129
- import { join as join19, relative as relative6, sep as sep4 } from "node:path";
38465
+ import { existsSync as existsSync15, statSync as statSync4, appendFileSync as appendFileSync2, readdirSync as readdirSync7, renameSync as renameSync2, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "node:fs";
38466
+ import { join as join20, relative as relative6, sep as sep4 } from "node:path";
38130
38467
 
38131
38468
  // ../../node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
38132
38469
  import { stat as statcb } from "fs";
@@ -39988,8 +40325,8 @@ init_src10();
39988
40325
  init_review2();
39989
40326
 
39990
40327
  // src/turn.ts
39991
- import { closeSync, existsSync as existsSync10, fstatSync, openSync, readdirSync as readdirSync5, readSync, statSync as statSync3 } from "node:fs";
39992
- import { basename as basename3, dirname as dirname7, join as join14 } from "node:path";
40328
+ import { closeSync, existsSync as existsSync11, fstatSync, openSync, readdirSync as readdirSync5, readSync, statSync as statSync3 } from "node:fs";
40329
+ import { basename as basename3, dirname as dirname7, join as join15 } from "node:path";
39993
40330
  import { homedir as homedir3 } from "node:os";
39994
40331
  function readTail(path2, maxBytes) {
39995
40332
  let fd;
@@ -40098,11 +40435,11 @@ function turnsSince(turns, mark) {
40098
40435
  return at >= 0 ? turns.slice(at + 1) : turns;
40099
40436
  }
40100
40437
  function claudeProjectDir(cwd, home = homedir3()) {
40101
- return join14(home, ".claude", "projects", cwd.replace(/[\\/:.]/g, "-"));
40438
+ return join15(home, ".claude", "projects", cwd.replace(/[\\/:.]/g, "-"));
40102
40439
  }
40103
40440
  function recentTranscripts(cwd, opts = {}) {
40104
40441
  const dir = claudeProjectDir(cwd, opts.home ?? homedir3());
40105
- if (!existsSync10(dir)) return [];
40442
+ if (!existsSync11(dir)) return [];
40106
40443
  let names;
40107
40444
  try {
40108
40445
  names = readdirSync5(dir);
@@ -40113,7 +40450,7 @@ function recentTranscripts(cwd, opts = {}) {
40113
40450
  const refs = [];
40114
40451
  for (const name2 of names) {
40115
40452
  if (!name2.endsWith(".jsonl")) continue;
40116
- const path2 = join14(dir, name2);
40453
+ const path2 = join15(dir, name2);
40117
40454
  let mtimeMs;
40118
40455
  try {
40119
40456
  mtimeMs = statSync3(path2).mtimeMs;
@@ -40128,8 +40465,8 @@ function recentTranscripts(cwd, opts = {}) {
40128
40465
  }
40129
40466
  function subagentTranscripts(mainTranscriptPath, sessionId) {
40130
40467
  if (!mainTranscriptPath || !sessionId) return [];
40131
- const dir = join14(dirname7(mainTranscriptPath), sessionId, "subagents");
40132
- if (!existsSync10(dir)) return [];
40468
+ const dir = join15(dirname7(mainTranscriptPath), sessionId, "subagents");
40469
+ if (!existsSync11(dir)) return [];
40133
40470
  let names;
40134
40471
  try {
40135
40472
  names = readdirSync5(dir);
@@ -40139,7 +40476,7 @@ function subagentTranscripts(mainTranscriptPath, sessionId) {
40139
40476
  const refs = [];
40140
40477
  for (const name2 of names) {
40141
40478
  if (!name2.endsWith(".jsonl")) continue;
40142
- const path2 = join14(dir, name2);
40479
+ const path2 = join15(dir, name2);
40143
40480
  let mtimeMs;
40144
40481
  try {
40145
40482
  mtimeMs = statSync3(path2).mtimeMs;
@@ -40156,7 +40493,7 @@ function subagentTranscripts(mainTranscriptPath, sessionId) {
40156
40493
  init_src();
40157
40494
  init_src2();
40158
40495
  init_agent_signals();
40159
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
40496
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
40160
40497
  var NEG = /n['']t|\b(?:not|never|no|none|neither|nor|unrelated|irrelevant|would|might|could|if)\b/i;
40161
40498
  var CITE_GROUP_RE = /\(((?:[^()\n]|\([^()\n]*\)){1,200})\)/g;
40162
40499
  var HANDLE_RE = /\[([a-z0-9][\w-]{0,40})\]|((?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,})/gi;
@@ -40406,13 +40743,13 @@ function writePrimingHandles(path2, nodes, now = Date.now()) {
40406
40743
  entries.sort((a, b) => (b[1].seenAt ?? 0) - (a[1].seenAt ?? 0));
40407
40744
  entries.length = HANDLE_MAP_MAX;
40408
40745
  }
40409
- writeFileSync8(path2, JSON.stringify(Object.fromEntries(entries)));
40746
+ writeFileSync9(path2, JSON.stringify(Object.fromEntries(entries)));
40410
40747
  } catch {
40411
40748
  }
40412
40749
  }
40413
40750
  function readPrimingHandles(path2) {
40414
40751
  try {
40415
- return JSON.parse(readFileSync8(path2, "utf8"));
40752
+ return JSON.parse(readFileSync9(path2, "utf8"));
40416
40753
  } catch {
40417
40754
  return {};
40418
40755
  }
@@ -40668,6 +41005,7 @@ ${conversation}` }]
40668
41005
  }
40669
41006
 
40670
41007
  // src/engine.ts
41008
+ init_symbol_summaries();
40671
41009
  init_reconcile();
40672
41010
 
40673
41011
  // src/episode.ts
@@ -40864,11 +41202,11 @@ init_outbox();
40864
41202
  init_src8();
40865
41203
  init_src();
40866
41204
  init_src2();
40867
- import { readFileSync as readFileSync9 } from "node:fs";
40868
- import { join as join15 } from "node:path";
41205
+ import { readFileSync as readFileSync10 } from "node:fs";
41206
+ import { join as join16 } from "node:path";
40869
41207
  function loadClaimIgnorePatterns(workspaceRoot) {
40870
41208
  try {
40871
- return readFileSync9(join15(workspaceRoot, ".errataignore"), "utf8").split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#")).map((l) => l.toLowerCase());
41209
+ return readFileSync10(join16(workspaceRoot, ".errataignore"), "utf8").split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#")).map((l) => l.toLowerCase());
40872
41210
  } catch {
40873
41211
  return [];
40874
41212
  }
@@ -41160,22 +41498,22 @@ async function pullCloudTriage(shared, cloud, profile, limit = 50) {
41160
41498
  }
41161
41499
 
41162
41500
  // src/git-sensor.ts
41163
- import { existsSync as existsSync11, readFileSync as readFileSync10, watch as fsWatch } from "node:fs";
41164
- import { join as join16 } from "node:path";
41501
+ import { existsSync as existsSync12, readFileSync as readFileSync11, watch as fsWatch } from "node:fs";
41502
+ import { join as join17 } from "node:path";
41165
41503
  function readFirstLine(path2) {
41166
41504
  try {
41167
- return readFileSync10(path2, "utf8").split(/\r?\n/, 1)[0].trim();
41505
+ return readFileSync11(path2, "utf8").split(/\r?\n/, 1)[0].trim();
41168
41506
  } catch {
41169
41507
  return null;
41170
41508
  }
41171
41509
  }
41172
41510
  function readGitRefState(gitDir) {
41173
- const head2 = readFirstLine(join16(gitDir, "HEAD"));
41511
+ const head2 = readFirstLine(join17(gitDir, "HEAD"));
41174
41512
  const m = head2 ? /^ref:\s*refs\/heads\/(.+)$/.exec(head2) : null;
41175
41513
  const branch = m ? m[1] : null;
41176
41514
  let sha2 = null;
41177
41515
  if (branch) {
41178
- sha2 = readFirstLine(join16(gitDir, "refs", "heads", branch));
41516
+ sha2 = readFirstLine(join17(gitDir, "refs", "heads", branch));
41179
41517
  if (!sha2) sha2 = shaFromPackedRefs(gitDir, `refs/heads/${branch}`);
41180
41518
  } else if (head2 && /^[0-9a-f]{7,40}$/i.test(head2)) {
41181
41519
  sha2 = head2;
@@ -41183,13 +41521,13 @@ function readGitRefState(gitDir) {
41183
41521
  return {
41184
41522
  branch,
41185
41523
  sha: sha2,
41186
- mergeHeadExists: existsSync11(join16(gitDir, "MERGE_HEAD")),
41187
- origHeadExists: existsSync11(join16(gitDir, "ORIG_HEAD"))
41524
+ mergeHeadExists: existsSync12(join17(gitDir, "MERGE_HEAD")),
41525
+ origHeadExists: existsSync12(join17(gitDir, "ORIG_HEAD"))
41188
41526
  };
41189
41527
  }
41190
41528
  function shaFromPackedRefs(gitDir, ref) {
41191
41529
  try {
41192
- for (const line of readFileSync10(join16(gitDir, "packed-refs"), "utf8").split(/\r?\n/)) {
41530
+ for (const line of readFileSync11(join17(gitDir, "packed-refs"), "utf8").split(/\r?\n/)) {
41193
41531
  const [sha2, name2] = line.split(/\s+/);
41194
41532
  if (name2 === ref && sha2) return sha2;
41195
41533
  }
@@ -41223,7 +41561,7 @@ function startGitSensor(gitDir, onEvent, opts = {}) {
41223
41561
  const settle = () => {
41224
41562
  if (timer) clearTimeout(timer);
41225
41563
  timer = setTimeout(() => {
41226
- if (existsSync11(join16(gitDir, "index.lock"))) {
41564
+ if (existsSync12(join17(gitDir, "index.lock"))) {
41227
41565
  settle();
41228
41566
  return;
41229
41567
  }
@@ -41234,7 +41572,7 @@ function startGitSensor(gitDir, onEvent, opts = {}) {
41234
41572
  }, debounceMs);
41235
41573
  };
41236
41574
  for (const sub of ["HEAD", "logs/HEAD", "MERGE_HEAD", "ORIG_HEAD"]) {
41237
- const p = join16(gitDir, sub);
41575
+ const p = join17(gitDir, sub);
41238
41576
  try {
41239
41577
  watchers.push(fsWatch(p, settle));
41240
41578
  } catch {
@@ -41459,21 +41797,21 @@ var TelemetryRecorder = class {
41459
41797
 
41460
41798
  // src/skills.ts
41461
41799
  import {
41462
- existsSync as existsSync12,
41800
+ existsSync as existsSync13,
41463
41801
  mkdirSync as mkdirSync5,
41464
- readFileSync as readFileSync11,
41802
+ readFileSync as readFileSync12,
41465
41803
  readdirSync as readdirSync6,
41466
41804
  unlinkSync as unlinkSync2,
41467
- writeFileSync as writeFileSync9
41805
+ writeFileSync as writeFileSync10
41468
41806
  } from "node:fs";
41469
- import { basename as basename4, join as join17 } from "node:path";
41807
+ import { basename as basename4, join as join18 } from "node:path";
41470
41808
  function skillFileName(id) {
41471
41809
  return `${id.replace(/[^A-Za-z0-9_.-]/g, "_")}.md`;
41472
41810
  }
41473
41811
  function readSkillManifest(manifestPath) {
41474
- if (!existsSync12(manifestPath)) return [];
41812
+ if (!existsSync13(manifestPath)) return [];
41475
41813
  try {
41476
- const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
41814
+ const parsed = JSON.parse(readFileSync12(manifestPath, "utf8"));
41477
41815
  return (parsed.skills ?? []).map((s) => ({
41478
41816
  title: s.title ?? "",
41479
41817
  layer: s.layer ?? "technique",
@@ -41496,7 +41834,7 @@ async function syncSkills(paths, client, seed) {
41496
41834
  for (const s of res.skills) {
41497
41835
  const fileName = skillFileName(s.id);
41498
41836
  keep.add(fileName);
41499
- writeFileSync9(join17(paths.skillsDir, fileName), s.markdown, "utf8");
41837
+ writeFileSync10(join18(paths.skillsDir, fileName), s.markdown, "utf8");
41500
41838
  rows.push({
41501
41839
  id: s.id,
41502
41840
  title: s.title,
@@ -41510,13 +41848,13 @@ async function syncSkills(paths, client, seed) {
41510
41848
  if (!f.endsWith(".md")) continue;
41511
41849
  if (keep.has(basename4(f))) continue;
41512
41850
  try {
41513
- unlinkSync2(join17(paths.skillsDir, f));
41851
+ unlinkSync2(join18(paths.skillsDir, f));
41514
41852
  pruned++;
41515
41853
  } catch {
41516
41854
  }
41517
41855
  }
41518
41856
  rows.sort((a, b) => a.id.localeCompare(b.id));
41519
- writeFileSync9(
41857
+ writeFileSync10(
41520
41858
  paths.skillsManifest,
41521
41859
  JSON.stringify({ generatedAt: Date.now(), skills: rows }, null, 2),
41522
41860
  "utf8"
@@ -41653,30 +41991,30 @@ var CausalBuffer = class {
41653
41991
  // src/profile.ts
41654
41992
  init_src2();
41655
41993
  init_paths();
41656
- import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "node:fs";
41994
+ import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
41657
41995
  import { createHash as createHash12 } from "node:crypto";
41658
- import { join as join18 } from "node:path";
41996
+ import { join as join19 } from "node:path";
41659
41997
  function workspaceId(root) {
41660
41998
  return "wp_" + createHash12("sha256").update(root).digest("hex").slice(0, 12);
41661
41999
  }
41662
42000
  function loadProfile(root) {
41663
42001
  const p = workspacePaths(root);
41664
- if (!existsSync13(p.workspaceJson)) return null;
41665
- return JSON.parse(readFileSync12(p.workspaceJson, "utf8"));
42002
+ if (!existsSync14(p.workspaceJson)) return null;
42003
+ return JSON.parse(readFileSync13(p.workspaceJson, "utf8"));
41666
42004
  }
41667
42005
  function saveProfile(root, profile) {
41668
42006
  const p = workspacePaths(root);
41669
42007
  ensureDir(p.configDir);
41670
- writeFileSync10(p.workspaceJson, JSON.stringify(profile, null, 2), "utf8");
42008
+ writeFileSync11(p.workspaceJson, JSON.stringify(profile, null, 2), "utf8");
41671
42009
  }
41672
42010
  function autodetectProfile(root) {
41673
42011
  const id = workspaceId(root);
41674
42012
  const name2 = root.split(/[\\/]/).filter(Boolean).pop() ?? "workspace";
41675
42013
  const p = emptyProfile(id, name2);
41676
- const pkgPath = join18(root, "package.json");
41677
- if (existsSync13(pkgPath)) {
42014
+ const pkgPath = join19(root, "package.json");
42015
+ if (existsSync14(pkgPath)) {
41678
42016
  try {
41679
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
42017
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
41680
42018
  p.languages.push("typescript", "javascript");
41681
42019
  const nodeVer = pkg.engines?.node ?? "node";
41682
42020
  p.stack.push(`node@${nodeVer}`);
@@ -41697,10 +42035,10 @@ function autodetectProfile(root) {
41697
42035
  } catch {
41698
42036
  }
41699
42037
  }
41700
- const pyproject = join18(root, "pyproject.toml");
41701
- if (existsSync13(pyproject)) {
42038
+ const pyproject = join19(root, "pyproject.toml");
42039
+ if (existsSync14(pyproject)) {
41702
42040
  try {
41703
- const txt = readFileSync12(pyproject, "utf8");
42041
+ const txt = readFileSync13(pyproject, "utf8");
41704
42042
  const py = /python\s*=\s*"([^"]+)"/.exec(txt)?.[1];
41705
42043
  p.languages.push("python");
41706
42044
  p.stack.push(`python@${py ?? "3"}`);
@@ -41711,16 +42049,16 @@ function autodetectProfile(root) {
41711
42049
  } catch {
41712
42050
  }
41713
42051
  }
41714
- const reqs = join18(root, "requirements.txt");
41715
- if (existsSync13(reqs)) {
42052
+ const reqs = join19(root, "requirements.txt");
42053
+ if (existsSync14(reqs)) {
41716
42054
  if (!p.languages.includes("python")) p.languages.push("python");
41717
42055
  if (!p.stack.includes("python@3")) p.stack.push("python@3");
41718
42056
  }
41719
- if (existsSync13(join18(root, "go.mod"))) {
42057
+ if (existsSync14(join19(root, "go.mod"))) {
41720
42058
  p.languages.push("go");
41721
42059
  p.stack.push("go");
41722
42060
  }
41723
- if (existsSync13(join18(root, "Cargo.toml"))) {
42061
+ if (existsSync14(join19(root, "Cargo.toml"))) {
41724
42062
  p.languages.push("rust");
41725
42063
  p.stack.push("rust");
41726
42064
  }
@@ -41878,7 +42216,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
41878
42216
  }
41879
42217
 
41880
42218
  // src/engine.ts
41881
- var DAEMON_VERSION = true ? "2.0.0-dev.34" : "2.0.0-alpha.0";
42219
+ var DAEMON_VERSION = true ? "2.0.0-dev.36" : "2.0.0-alpha.0";
41882
42220
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
41883
42221
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
41884
42222
  var GIT_OP_MUTE_MS = 4e3;
@@ -41888,7 +42226,7 @@ var TURN_REPLAY_LOOKBACK_MS = 7 * 24 * 60 * 6e4;
41888
42226
  function appendIdentityAudit(path2, record2, line) {
41889
42227
  if (!record2.accepted && record2.score <= 0) return;
41890
42228
  try {
41891
- if (existsSync14(path2) && statSync4(path2).size >= IDENTITY_AUDIT_MAX_BYTES) {
42229
+ if (existsSync15(path2) && statSync4(path2).size >= IDENTITY_AUDIT_MAX_BYTES) {
41892
42230
  renameSync2(path2, `${path2}.1`);
41893
42231
  }
41894
42232
  appendFileSync2(path2, line);
@@ -41898,14 +42236,14 @@ function appendIdentityAudit(path2, record2, line) {
41898
42236
  var yieldToLoop = () => new Promise((r) => setImmediate(r));
41899
42237
  function loadTurnCursors(path2) {
41900
42238
  try {
41901
- return new Map(Object.entries(JSON.parse(readFileSync13(path2, "utf8"))));
42239
+ return new Map(Object.entries(JSON.parse(readFileSync14(path2, "utf8"))));
41902
42240
  } catch {
41903
42241
  return /* @__PURE__ */ new Map();
41904
42242
  }
41905
42243
  }
41906
42244
  function saveTurnCursors(path2, cursors) {
41907
42245
  try {
41908
- writeFileSync11(path2, JSON.stringify(Object.fromEntries(cursors)), "utf8");
42246
+ writeFileSync12(path2, JSON.stringify(Object.fromEntries(cursors)), "utf8");
41909
42247
  } catch {
41910
42248
  }
41911
42249
  }
@@ -41927,7 +42265,7 @@ function gitSourceWatchTargets(root) {
41927
42265
  ["-C", root, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory", "-z"],
41928
42266
  { encoding: "utf8", maxBuffer: 256 * 1024 * 1024, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
41929
42267
  );
41930
- ignoredDirs = igOut.split("\0").filter(Boolean).map((d) => d.replace(/\/+$/, "")).filter((d) => !IGNORED_PATH.test(join19(root, d) + sep4));
42268
+ ignoredDirs = igOut.split("\0").filter(Boolean).map((d) => d.replace(/\/+$/, "")).filter((d) => !IGNORED_PATH.test(join20(root, d) + sep4));
41931
42269
  } catch {
41932
42270
  }
41933
42271
  const hasIgnoredChild = (dir) => ignoredDirs.some((ig) => ig.startsWith(dir + "/"));
@@ -41939,19 +42277,19 @@ function gitSourceWatchTargets(root) {
41939
42277
  if (!f.startsWith(prefix)) continue;
41940
42278
  const rest2 = f.slice(prefix.length);
41941
42279
  if (rest2.includes("/")) children.add(dir === "" ? rest2.slice(0, rest2.indexOf("/")) : dir + "/" + rest2.slice(0, rest2.indexOf("/")));
41942
- else targets.add(join19(root, f));
42280
+ else targets.add(join20(root, f));
41943
42281
  }
41944
42282
  for (const c of children) {
41945
- if (IGNORED_PATH.test(join19(root, c) + sep4)) continue;
42283
+ if (IGNORED_PATH.test(join20(root, c) + sep4)) continue;
41946
42284
  if (hasIgnoredChild(c)) addUnder(c);
41947
- else targets.add(join19(root, c));
42285
+ else targets.add(join20(root, c));
41948
42286
  }
41949
42287
  };
41950
42288
  addUnder("");
41951
42289
  if (targets.size > 0) return [...targets];
41952
42290
  } catch {
41953
42291
  }
41954
- return readdirSync7(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_PATH.test(join19(root, String(e.name)) + sep4)).map((e) => join19(root, String(e.name)));
42292
+ return readdirSync7(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_PATH.test(join20(root, String(e.name)) + sep4)).map((e) => join20(root, String(e.name)));
41955
42293
  }
41956
42294
  function createWorkspaceEngine(opts) {
41957
42295
  const paths = workspacePaths(opts.workspaceRoot);
@@ -42103,7 +42441,7 @@ function createWorkspaceEngine(opts) {
42103
42441
  const srcPaths = diff.changedPaths.filter((p) => /\.(ts|tsx|js|jsx|mjs|cjs|py)$/i.test(p));
42104
42442
  let episodeId2;
42105
42443
  if (srcPaths.length > 0) {
42106
- const abs = srcPaths.map((p) => join19(opts.workspaceRoot, p));
42444
+ const abs = srcPaths.map((p) => join20(opts.workspaceRoot, p));
42107
42445
  try {
42108
42446
  const r = await runReindexPass(
42109
42447
  `git-reindex:${profile.name} (${abs.length} files)`,
@@ -42134,8 +42472,8 @@ function createWorkspaceEngine(opts) {
42134
42472
  `[errata] git: ${ev.kind} ${ev.newSha.slice(0, 7)} by ${meta3.authorName} \u2014 ${diff.changedPaths.length} file(s), ${diff.renames.length} rename(s)`
42135
42473
  );
42136
42474
  };
42137
- const gitDir = join19(opts.workspaceRoot, ".git");
42138
- if (existsSync14(gitDir)) {
42475
+ const gitDir = join20(opts.workspaceRoot, ".git");
42476
+ if (existsSync15(gitDir)) {
42139
42477
  stopGit = startGitSensor(gitDir, (ev) => {
42140
42478
  void handleGitEvent(ev);
42141
42479
  });
@@ -42191,18 +42529,20 @@ function createWorkspaceEngine(opts) {
42191
42529
  };
42192
42530
  const prebuilt = passWorker ? await passWorker.snapshot(snapOpts).catch(() => null) : null;
42193
42531
  const doneRender = prebuilt ? null : markPass(`context-render-inline:${profile.name}`);
42532
+ const pendingUpdate = opts.pendingUpdate?.() ?? null;
42194
42533
  const { body: body2, snapshot } = assembleAgentContext({
42195
42534
  store,
42196
42535
  ...snapOpts,
42197
42536
  remote: remotePriors,
42198
42537
  ...elicit ? { edgeElicitation: { instruction: PRIOR_TAG_INSTRUCTION } } : {},
42199
- ...prebuilt ? { snapshot: prebuilt } : {}
42538
+ ...prebuilt ? { snapshot: prebuilt } : {},
42539
+ ...pendingUpdate ? { pendingUpdate } : {}
42200
42540
  });
42201
42541
  doneRender?.();
42202
- const target = join19(opts.workspaceRoot, "AGENTS.md");
42542
+ const target = join20(opts.workspaceRoot, "AGENTS.md");
42203
42543
  writeManagedBlock(target, { body: body2 });
42204
42544
  if (elicit) {
42205
- writePrimingHandles(join19(paths.configDir, "priming-handles.json"), [
42545
+ writePrimingHandles(join20(paths.configDir, "priming-handles.json"), [
42206
42546
  ...snapshot.recentProblems.map((r) => r.node),
42207
42547
  // Resolved-band handles: the ✓ problem AND its Solution are citable
42208
42548
  // (a fix tag on an already-resolved problem no-ops idempotently; the
@@ -42372,7 +42712,7 @@ function createWorkspaceEngine(opts) {
42372
42712
  resultSummary: { exitCode: e.exitCode ?? 1, errorTokens: e.errorTokens }
42373
42713
  });
42374
42714
  };
42375
- const turnCursorPath = join19(paths.configDir, "turn-cursors.json");
42715
+ const turnCursorPath = join20(paths.configDir, "turn-cursors.json");
42376
42716
  const lastTurnUuid = loadTurnCursors(turnCursorPath);
42377
42717
  const sessionLastProblem = /* @__PURE__ */ new Map();
42378
42718
  const sessionThreads = /* @__PURE__ */ new Map();
@@ -42395,7 +42735,7 @@ function createWorkspaceEngine(opts) {
42395
42735
  const t = Date.now();
42396
42736
  let processedTurns = 0;
42397
42737
  const elicit = isEdgeElicitationEnabled();
42398
- const handleMap = elicit ? readPrimingHandles(join19(paths.configDir, "priming-handles.json")) : {};
42738
+ const handleMap = elicit ? readPrimingHandles(join20(paths.configDir, "priming-handles.json")) : {};
42399
42739
  const wsRoot = (opts.workspaceRoot ?? "").replace(/\\/g, "/");
42400
42740
  const toRel = (abs) => {
42401
42741
  const p = abs.replace(/\\/g, "/");
@@ -42864,6 +43204,10 @@ function createWorkspaceEngine(opts) {
42864
43204
  emitEvent(ev) {
42865
43205
  log.append(ev);
42866
43206
  },
43207
+ markContextDirty() {
43208
+ contextDirty = true;
43209
+ refreshContextNow();
43210
+ },
42867
43211
  async tick() {
42868
43212
  maybeRefreshRemotePriors();
42869
43213
  const report = {
@@ -42908,7 +43252,21 @@ function createWorkspaceEngine(opts) {
42908
43252
  return report;
42909
43253
  },
42910
43254
  async nightly() {
42911
- return (await runNightly()).report;
43255
+ const report = (await runNightly()).report;
43256
+ const summarizer = opts.intentSummarizer ?? envIntentSummarizer();
43257
+ if (summarizer) {
43258
+ try {
43259
+ const s = await runSymbolSummarySweep(store, opts.workspaceRoot, paths.configDir, summarizer);
43260
+ if (s.generated > 0 || s.rejected > 0) {
43261
+ console.log(
43262
+ `[errata] symbol summaries: +${s.generated}${s.rejected > 0 ? ` (${s.rejected} rejected by no-verbatim check)` : ""}${s.remaining > 0 ? `, ${s.remaining} pending` : ""}`
43263
+ );
43264
+ }
43265
+ } catch (err2) {
43266
+ console.warn("[errata] symbol summary sweep failed:", err2 instanceof Error ? err2.message : err2);
43267
+ }
43268
+ }
43269
+ return report;
42912
43270
  },
42913
43271
  async embedSettled() {
42914
43272
  try {
@@ -42996,7 +43354,7 @@ function createWorkspaceEngine(opts) {
42996
43354
  console.log(
42997
43355
  "[errata] sync skipped \u2014 cloud sync consent is off (enable with `errata consent sync on`)"
42998
43356
  );
42999
- const pending = existsSync14(paths.outbox) ? readdirSync7(paths.outbox).filter((f) => f.endsWith(".json")).length : 0;
43357
+ const pending = existsSync15(paths.outbox) ? readdirSync7(paths.outbox).filter((f) => f.endsWith(".json")).length : 0;
43000
43358
  return { uploaded: 0, failed: 0, remaining: pending };
43001
43359
  }
43002
43360
  try {
@@ -43083,7 +43441,7 @@ async function startDaemon(opts) {
43083
43441
  reviewUrl: () => webUiUrl + "/review"
43084
43442
  });
43085
43443
  const writeLockFile = (url2) => {
43086
- writeFileSync12(
43444
+ writeFileSync13(
43087
43445
  engine.paths.daemonLock,
43088
43446
  JSON.stringify({ pid: process.pid, webUiUrl: url2, startedAt: Date.now() }),
43089
43447
  "utf8"
@@ -43126,7 +43484,7 @@ async function startDaemon(opts) {
43126
43484
  );
43127
43485
  await engine.stop();
43128
43486
  try {
43129
- if (existsSync15(engine.paths.daemonLock)) {
43487
+ if (existsSync16(engine.paths.daemonLock)) {
43130
43488
  }
43131
43489
  } catch {
43132
43490
  }
@@ -43143,16 +43501,16 @@ async function listenServer(fetchFn, port) {
43143
43501
 
43144
43502
  // src/registry.ts
43145
43503
  init_paths();
43146
- import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "node:fs";
43147
- import { join as join20 } from "node:path";
43504
+ import { existsSync as existsSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync14 } from "node:fs";
43505
+ import { join as join21 } from "node:path";
43148
43506
  function registryPath() {
43149
- return process.env["ERRATA_REGISTRY_PATH"] ?? join20(globalDir(), "workspaces.json");
43507
+ return process.env["ERRATA_REGISTRY_PATH"] ?? join21(globalDir(), "workspaces.json");
43150
43508
  }
43151
43509
  function read() {
43152
43510
  const p = registryPath();
43153
- if (!existsSync16(p)) return { version: 1, workspaces: {} };
43511
+ if (!existsSync17(p)) return { version: 1, workspaces: {} };
43154
43512
  try {
43155
- const parsed = JSON.parse(readFileSync14(p, "utf8"));
43513
+ const parsed = JSON.parse(readFileSync15(p, "utf8"));
43156
43514
  return { version: 1, workspaces: parsed.workspaces ?? {} };
43157
43515
  } catch {
43158
43516
  return { version: 1, workspaces: {} };
@@ -43160,7 +43518,7 @@ function read() {
43160
43518
  }
43161
43519
  function write(reg) {
43162
43520
  ensureDir(globalDir());
43163
- writeFileSync13(registryPath(), JSON.stringify(reg, null, 2), "utf8");
43521
+ writeFileSync14(registryPath(), JSON.stringify(reg, null, 2), "utf8");
43164
43522
  }
43165
43523
  function registerWorkspace(profile, root, now = Date.now()) {
43166
43524
  const reg = read();
@@ -43177,7 +43535,7 @@ function pruneMissingWorkspaces() {
43177
43535
  const reg = read();
43178
43536
  const removed = [];
43179
43537
  for (const [id, entry] of Object.entries(reg.workspaces)) {
43180
- if (!existsSync16(entry.path)) {
43538
+ if (!existsSync17(entry.path)) {
43181
43539
  removed.push(entry);
43182
43540
  delete reg.workspaces[id];
43183
43541
  }
@@ -43186,13 +43544,13 @@ function pruneMissingWorkspaces() {
43186
43544
  return removed;
43187
43545
  }
43188
43546
  function workspaceStatus(entry) {
43189
- const missing = !existsSync16(entry.path);
43547
+ const missing = !existsSync17(entry.path);
43190
43548
  const lockPath = workspacePaths(entry.path).daemonLock;
43191
43549
  let running = false;
43192
43550
  let webUiUrl = null;
43193
- if (existsSync16(lockPath)) {
43551
+ if (existsSync17(lockPath)) {
43194
43552
  try {
43195
- const lock = JSON.parse(readFileSync14(lockPath, "utf8"));
43553
+ const lock = JSON.parse(readFileSync15(lockPath, "utf8"));
43196
43554
  if (lock.pid && lock.webUiUrl && pidAlive(lock.pid)) {
43197
43555
  running = true;
43198
43556
  webUiUrl = lock.webUiUrl;
@@ -43220,7 +43578,7 @@ function pidAlive(pid) {
43220
43578
  // src/multi.ts
43221
43579
  init_dist();
43222
43580
  init_src4();
43223
- import { readFileSync as readFileSync17, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
43581
+ import { readFileSync as readFileSync18, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "node:fs";
43224
43582
 
43225
43583
  // src/principle-sync.ts
43226
43584
  init_src4();
@@ -43247,8 +43605,8 @@ async function syncPrinciples(store, cloud, opts) {
43247
43605
  init_reconcile();
43248
43606
 
43249
43607
  // src/lockfile-auto.ts
43250
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
43251
- import { join as join21 } from "node:path";
43608
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
43609
+ import { join as join22 } from "node:path";
43252
43610
 
43253
43611
  // src/package-index.ts
43254
43612
  init_src();
@@ -43512,11 +43870,11 @@ function runLockfilePass(opts) {
43512
43870
  { file: "package-lock.json", parse: parsePackageLockJson }
43513
43871
  ];
43514
43872
  for (const c of candidates) {
43515
- const p = join21(opts.root, c.file);
43516
- if (!existsSync17(p)) continue;
43873
+ const p = join22(opts.root, c.file);
43874
+ if (!existsSync18(p)) continue;
43517
43875
  let sbom;
43518
43876
  try {
43519
- sbom = c.parse(readFileSync15(p, "utf8"));
43877
+ sbom = c.parse(readFileSync16(p, "utf8"));
43520
43878
  } catch {
43521
43879
  continue;
43522
43880
  }
@@ -43671,6 +44029,27 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
43671
44029
  }
43672
44030
  }
43673
44031
  if (nodes.length === 0 && anchorEdges.length === 0) return null;
44032
+ let symbolSummaries;
44033
+ if (opts.lexicon && opts.lexicon.size > 0 && nodes.length > 0) {
44034
+ const lexicon = opts.lexicon;
44035
+ const isKnown = (tok) => lexicon.has(tok);
44036
+ const sidecar = {};
44037
+ let count = 0;
44038
+ outer: for (const n of nodes) {
44039
+ if (!INSTANCE_LABELS.includes(n.label)) continue;
44040
+ for (const tok of extractIdentifierTokens(n.description)) {
44041
+ if (sidecar[tok]) continue;
44042
+ const entry = lexicon.get(tok);
44043
+ if (!entry?.summary) continue;
44044
+ const scrubbed = generalize(entry.summary, { level }).text.trim();
44045
+ if (!scrubbed || summaryRejectionReason(scrubbed, isKnown) !== null) continue;
44046
+ if (count >= MAX_SIDECAR_SYMBOLS) break outer;
44047
+ sidecar[tok] = { kind: entry.kind, summary: scrubbed };
44048
+ count++;
44049
+ }
44050
+ }
44051
+ if (count > 0) symbolSummaries = sidecar;
44052
+ }
43674
44053
  const edges = [];
43675
44054
  for (const id of seen) {
43676
44055
  for (const e of store.outEdges(id, [...INSTANCE_EDGES])) {
@@ -43685,12 +44064,17 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
43685
44064
  // co-locates the few Language/Package stubs with the first instance chunk,
43686
44065
  // maximizing in-payload endpoint resolution.
43687
44066
  nodes: [...contextNodes, ...nodes],
43688
- edges
44067
+ edges,
44068
+ // Spread-if-present: an absent sidecar keeps `base` — and therefore the
44069
+ // payloadDigest — byte-identical to the pre-sidecar batch (backward compat).
44070
+ ...symbolSummaries ? { symbolSummaries } : {}
43689
44071
  };
43690
44072
  return { ...base, payloadDigest: digest(base), anchorDigests };
43691
44073
  }
43692
44074
 
43693
44075
  // src/multi.ts
44076
+ init_generalize_graph();
44077
+ init_symbol_summaries();
43694
44078
  init_webui();
43695
44079
 
43696
44080
  // src/consolidate-worker-client.ts
@@ -43766,7 +44150,7 @@ var ConsolidateWorker = class {
43766
44150
  init_paths();
43767
44151
 
43768
44152
  // src/lock.ts
43769
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
44153
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
43770
44154
  function isProcessAlive(pid) {
43771
44155
  if (!pid || pid <= 0) return false;
43772
44156
  try {
@@ -43777,9 +44161,9 @@ function isProcessAlive(pid) {
43777
44161
  }
43778
44162
  }
43779
44163
  function readDaemonLock(lockPath) {
43780
- if (!existsSync18(lockPath)) return null;
44164
+ if (!existsSync19(lockPath)) return null;
43781
44165
  try {
43782
- const lock = JSON.parse(readFileSync16(lockPath, "utf8"));
44166
+ const lock = JSON.parse(readFileSync17(lockPath, "utf8"));
43783
44167
  return typeof lock.pid === "number" ? lock : null;
43784
44168
  } catch {
43785
44169
  return null;
@@ -43792,6 +44176,90 @@ function isDaemonAlive(lockPath) {
43792
44176
 
43793
44177
  // src/multi.ts
43794
44178
  init_config();
44179
+
44180
+ // src/update.ts
44181
+ import { spawn as spawn2 } from "node:child_process";
44182
+ var PACKAGE_NAME = "@inerrata-corporation/errata";
44183
+ async function latestPublished(channel, timeoutMs = 4e3) {
44184
+ const ctrl = new AbortController();
44185
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
44186
+ try {
44187
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`, {
44188
+ headers: { accept: "application/vnd.npm.install-v1+json" },
44189
+ signal: ctrl.signal
44190
+ });
44191
+ if (!res.ok) return null;
44192
+ const body2 = await res.json();
44193
+ return body2["dist-tags"]?.[channel] ?? null;
44194
+ } catch {
44195
+ return null;
44196
+ } finally {
44197
+ clearTimeout(timer);
44198
+ }
44199
+ }
44200
+ async function checkForUpdate(channel) {
44201
+ const latest = await latestPublished(channel);
44202
+ return {
44203
+ current: DAEMON_VERSION,
44204
+ latest,
44205
+ updateAvailable: latest !== null && latest !== DAEMON_VERSION
44206
+ };
44207
+ }
44208
+ var POLL_INTERVAL_MS = {
44209
+ dev: 6 * 60 * 6e4,
44210
+ latest: 24 * 60 * 6e4
44211
+ };
44212
+ function startUpdatePoller(opts) {
44213
+ const check2 = opts.check ?? (() => checkForUpdate(opts.channel));
44214
+ const base = opts.intervalMs ?? POLL_INTERVAL_MS[opts.channel];
44215
+ let pending = null;
44216
+ let inFlight = false;
44217
+ let timer = null;
44218
+ let stopped = false;
44219
+ const runOnce = async () => {
44220
+ if (inFlight) return;
44221
+ inFlight = true;
44222
+ try {
44223
+ const status = await check2();
44224
+ if (status.latest === null) return;
44225
+ const next = status.updateAvailable ? { current: status.current, latest: status.latest, channel: opts.channel } : null;
44226
+ const changed = (next?.latest ?? null) !== (pending?.latest ?? null);
44227
+ pending = next;
44228
+ if (changed) opts.onChange?.(pending);
44229
+ } catch {
44230
+ } finally {
44231
+ inFlight = false;
44232
+ }
44233
+ };
44234
+ const schedule = () => {
44235
+ if (stopped) return;
44236
+ const jitter = 1 + (Math.random() - 0.5) * 0.2;
44237
+ timer = setTimeout(() => {
44238
+ void runOnce().finally(schedule);
44239
+ }, base * jitter);
44240
+ timer.unref?.();
44241
+ };
44242
+ void runOnce().finally(schedule);
44243
+ return {
44244
+ current: () => pending,
44245
+ stop: () => {
44246
+ stopped = true;
44247
+ if (timer) clearTimeout(timer);
44248
+ }
44249
+ };
44250
+ }
44251
+ function runNpmInstall(channel) {
44252
+ return new Promise((resolve5) => {
44253
+ const child = spawn2("npm", ["install", "-g", `${PACKAGE_NAME}@${channel}`], {
44254
+ stdio: "inherit",
44255
+ shell: true
44256
+ });
44257
+ child.on("close", (code) => resolve5(code ?? 1));
44258
+ child.on("error", () => resolve5(1));
44259
+ });
44260
+ }
44261
+
44262
+ // src/multi.ts
43795
44263
  init_cloud_endpoint_policy();
43796
44264
  init_cloud_auth();
43797
44265
  function normPath(p) {
@@ -43858,6 +44326,15 @@ async function startMultiDaemon(opts = {}) {
43858
44326
  startLoopLagMonitor();
43859
44327
  let baseUrl = "";
43860
44328
  const records = [];
44329
+ const updatePoller = opts.updateCheck ? startUpdatePoller({
44330
+ channel: loadConfig().updateChannel,
44331
+ onChange: (pending) => {
44332
+ if (pending) {
44333
+ console.log(`\u26A1 errata update available: ${pending.current} \u2192 ${pending.latest} \u2014 run: errata update`);
44334
+ }
44335
+ for (const r of records) r.engine.markContextDirty();
44336
+ }
44337
+ }) : null;
43861
44338
  const boundaryListeners = [];
43862
44339
  let boundaryFlushing = false;
43863
44340
  const fireBoundary = () => {
@@ -43876,6 +44353,7 @@ async function startMultiDaemon(opts = {}) {
43876
44353
  apiKey: opts.apiKey,
43877
44354
  reviewUrl: () => `${baseUrl}/ws/${id}/review`,
43878
44355
  sharedStore,
44356
+ pendingUpdate: () => updatePoller?.current() ?? null,
43879
44357
  ...opts.skipWatchers ? { skipWatchers: true } : {}
43880
44358
  });
43881
44359
  const webApp = buildWebUi({
@@ -43933,7 +44411,7 @@ async function startMultiDaemon(opts = {}) {
43933
44411
  records.push(rec);
43934
44412
  app.route(`/ws/${rec.id}`, rec.webApp);
43935
44413
  try {
43936
- writeFileSync14(
44414
+ writeFileSync15(
43937
44415
  rec.engine.paths.daemonLock,
43938
44416
  JSON.stringify({ pid: process.pid, webUiUrl: `${baseUrl}/ws/${rec.id}`, startedAt: Date.now() }),
43939
44417
  "utf8"
@@ -43965,6 +44443,7 @@ async function startMultiDaemon(opts = {}) {
43965
44443
  "/",
43966
44444
  (c) => c.json({
43967
44445
  daemonVersion: DAEMON_VERSION,
44446
+ pendingUpdate: updatePoller?.current() ?? null,
43968
44447
  projects: records.map((r) => ({
43969
44448
  id: r.id,
43970
44449
  name: r.entry.name,
@@ -44115,7 +44594,7 @@ async function startMultiDaemon(opts = {}) {
44115
44594
  baseUrl = `http://127.0.0.1:${port}`;
44116
44595
  try {
44117
44596
  ensureDir(globalDir());
44118
- writeFileSync14(
44597
+ writeFileSync15(
44119
44598
  lockPath,
44120
44599
  JSON.stringify({ pid: process.pid, webUiUrl: baseUrl, startedAt: Date.now() }),
44121
44600
  "utf8"
@@ -44124,7 +44603,7 @@ async function startMultiDaemon(opts = {}) {
44124
44603
  }
44125
44604
  for (const r of records) {
44126
44605
  try {
44127
- writeFileSync14(
44606
+ writeFileSync15(
44128
44607
  r.engine.paths.daemonLock,
44129
44608
  JSON.stringify({ pid: process.pid, webUiUrl: `${baseUrl}/ws/${r.id}`, startedAt: Date.now() }),
44130
44609
  "utf8"
@@ -44336,8 +44815,15 @@ async function startMultiDaemon(opts = {}) {
44336
44815
  const res = await client.ingest(context);
44337
44816
  uploaded += res.accepted;
44338
44817
  }
44818
+ let lexicon;
44819
+ try {
44820
+ const summaries = summariesByBodyHash(loadSymbolSummaryCache(r.engine.paths.configDir));
44821
+ if (summaries.size > 0) lexicon = buildSymbolLexicon(store, summaries);
44822
+ } catch {
44823
+ }
44339
44824
  const instances = buildInstanceIngest(store, r.engine.profile, DAEMON_VERSION, ignore, {
44340
- includePackages: cfg2.consent.contributePackages
44825
+ includePackages: cfg2.consent.contributePackages,
44826
+ ...lexicon ? { lexicon } : {}
44341
44827
  });
44342
44828
  if (instances) {
44343
44829
  const res = await client.ingest(instances);
@@ -44420,13 +44906,14 @@ async function startMultiDaemon(opts = {}) {
44420
44906
  },
44421
44907
  async stop() {
44422
44908
  try {
44423
- const cur = readFileSync17(lockPath, "utf8");
44909
+ const cur = readFileSync18(lockPath, "utf8");
44424
44910
  if (JSON.parse(cur).pid === process.pid) unlinkSync3(lockPath);
44425
44911
  } catch {
44426
44912
  }
44427
44913
  await new Promise(
44428
44914
  (res) => server.close(res)
44429
44915
  );
44916
+ updatePoller?.stop();
44430
44917
  for (const r of records) await r.engine.stop();
44431
44918
  if (consolidateWorker) await consolidateWorker.stop();
44432
44919
  try {
@@ -44584,45 +45071,6 @@ async function loginOAuthLoopback(opts) {
44584
45071
  return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
44585
45072
  }
44586
45073
 
44587
- // src/update.ts
44588
- import { spawn as spawn2 } from "node:child_process";
44589
- var PACKAGE_NAME = "@inerrata-corporation/errata";
44590
- async function latestPublished(channel, timeoutMs = 4e3) {
44591
- const ctrl = new AbortController();
44592
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
44593
- try {
44594
- const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`, {
44595
- headers: { accept: "application/vnd.npm.install-v1+json" },
44596
- signal: ctrl.signal
44597
- });
44598
- if (!res.ok) return null;
44599
- const body2 = await res.json();
44600
- return body2["dist-tags"]?.[channel] ?? null;
44601
- } catch {
44602
- return null;
44603
- } finally {
44604
- clearTimeout(timer);
44605
- }
44606
- }
44607
- async function checkForUpdate(channel) {
44608
- const latest = await latestPublished(channel);
44609
- return {
44610
- current: DAEMON_VERSION,
44611
- latest,
44612
- updateAvailable: latest !== null && latest !== DAEMON_VERSION
44613
- };
44614
- }
44615
- function runNpmInstall(channel) {
44616
- return new Promise((resolve5) => {
44617
- const child = spawn2("npm", ["install", "-g", `${PACKAGE_NAME}@${channel}`], {
44618
- stdio: "inherit",
44619
- shell: true
44620
- });
44621
- child.on("close", (code) => resolve5(code ?? 1));
44622
- child.on("error", () => resolve5(1));
44623
- });
44624
- }
44625
-
44626
45074
  // src/cli.ts
44627
45075
  init_paths();
44628
45076
 
@@ -44912,21 +45360,21 @@ async function cmdInit() {
44912
45360
  if (!skipHooks) {
44913
45361
  console.log("");
44914
45362
  console.log("installing harness hooks...");
44915
- const { existsSync: existsSync20 } = await import("node:fs");
44916
- const { join: join23 } = await import("node:path");
45363
+ const { existsSync: existsSync21 } = await import("node:fs");
45364
+ const { join: join24 } = await import("node:path");
44917
45365
  try {
44918
45366
  await installClaudeHooks(port);
44919
45367
  } catch (err2) {
44920
45368
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
44921
45369
  }
44922
- if (existsSync20(join23(ROOT, ".cursor"))) {
45370
+ if (existsSync21(join24(ROOT, ".cursor"))) {
44923
45371
  try {
44924
45372
  await installCursorMcpConfig();
44925
45373
  } catch (err2) {
44926
45374
  console.warn(` \u26A0\uFE0F Cursor MCP config failed: ${err2 instanceof Error ? err2.message : err2}`);
44927
45375
  }
44928
45376
  }
44929
- if (existsSync20(join23(ROOT, ".codex"))) {
45377
+ if (existsSync21(join24(ROOT, ".codex"))) {
44930
45378
  try {
44931
45379
  await installCodexHooks(port);
44932
45380
  } catch (err2) {
@@ -45043,8 +45491,8 @@ async function cmdStatus() {
45043
45491
  console.log(` stack: ${profile.stack.join(", ") || "(none)"}`);
45044
45492
  console.log(` languages: ${profile.languages.join(", ") || "(none)"}`);
45045
45493
  }
45046
- console.log(` graph db: ${existsSync19(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
45047
- console.log(` event log: ${existsSync19(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
45494
+ console.log(` graph db: ${existsSync20(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
45495
+ console.log(` event log: ${existsSync20(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
45048
45496
  const lockPath = globalDaemonLock();
45049
45497
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
45050
45498
  console.log(
@@ -45340,11 +45788,11 @@ async function cmdLogout() {
45340
45788
  }
45341
45789
  async function cmdReview() {
45342
45790
  const paths = workspacePaths(ROOT);
45343
- if (!existsSync19(paths.reviewQueue)) {
45791
+ if (!existsSync20(paths.reviewQueue)) {
45344
45792
  console.log("(review queue empty)");
45345
45793
  return;
45346
45794
  }
45347
- const queue = JSON.parse(readFileSync18(paths.reviewQueue, "utf8"));
45795
+ const queue = JSON.parse(readFileSync19(paths.reviewQueue, "utf8"));
45348
45796
  if (queue.length === 0) {
45349
45797
  console.log("(review queue empty)");
45350
45798
  return;
@@ -45974,7 +46422,7 @@ async function gatherRepo(store, ws) {
45974
46422
  };
45975
46423
  }
45976
46424
  async function gatherReportData(generatedAt) {
45977
- const { existsSync: existsSync20 } = await import("node:fs");
46425
+ const { existsSync: existsSync21 } = await import("node:fs");
45978
46426
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src4(), src_exports2));
45979
46427
  const cfg = loadConfig();
45980
46428
  const outbound = cfg.consent.sync ? "auto" : "off";
@@ -45982,7 +46430,7 @@ async function gatherReportData(generatedAt) {
45982
46430
  for (const ws of listWorkspaces()) {
45983
46431
  if (ws.missing) continue;
45984
46432
  const dbPath = workspacePaths(ws.path).castalia;
45985
- if (!existsSync20(dbPath)) continue;
46433
+ if (!existsSync21(dbPath)) continue;
45986
46434
  let store = null;
45987
46435
  try {
45988
46436
  store = openGraphStore2({ path: dbPath });
@@ -46013,7 +46461,7 @@ async function gatherReportData(generatedAt) {
46013
46461
  };
46014
46462
  }
46015
46463
  async function cmdReport(args2) {
46016
- const { mkdirSync: mkdirSync6, writeFileSync: writeFileSync15 } = await import("node:fs");
46464
+ const { mkdirSync: mkdirSync6, writeFileSync: writeFileSync16 } = await import("node:fs");
46017
46465
  const { renderReport: renderReport2 } = await Promise.resolve().then(() => (init_report_render(), report_render_exports));
46018
46466
  const includeFutureVerbs = args2.includes("--future-verbs");
46019
46467
  const now = /* @__PURE__ */ new Date();
@@ -46026,8 +46474,8 @@ async function cmdReport(args2) {
46026
46474
  const outDir = workspacePaths(ROOT).configDir;
46027
46475
  mkdirSync6(outDir, { recursive: true });
46028
46476
  const files = renderReport2(data, { includeFutureVerbs });
46029
- for (const f of files) writeFileSync15(join22(outDir, f.name), f.html, "utf8");
46030
- const indexPath = join22(outDir, "report.html");
46477
+ for (const f of files) writeFileSync16(join23(outDir, f.name), f.html, "utf8");
46478
+ const indexPath = join23(outDir, "report.html");
46031
46479
  console.log(`report \u2192 ${indexPath}`);
46032
46480
  console.log(` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`);
46033
46481
  console.log(` open: file://${indexPath.replace(/\\/g, "/")}`);
@@ -46147,15 +46595,15 @@ function hookRelayCommand(port, path2) {
46147
46595
  return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
46148
46596
  }
46149
46597
  async function installClaudeHooks(port) {
46150
- const { mkdirSync: mkdirSync6, existsSync: existsSync20, readFileSync: readFileSync19, writeFileSync: writeFileSync15 } = await import("node:fs");
46151
- const { join: join23 } = await import("node:path");
46152
- const dir = join23(ROOT, ".claude");
46153
- if (!existsSync20(dir)) mkdirSync6(dir, { recursive: true });
46154
- const file2 = join23(dir, "settings.json");
46598
+ const { mkdirSync: mkdirSync6, existsSync: existsSync21, readFileSync: readFileSync20, writeFileSync: writeFileSync16 } = await import("node:fs");
46599
+ const { join: join24 } = await import("node:path");
46600
+ const dir = join24(ROOT, ".claude");
46601
+ if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
46602
+ const file2 = join24(dir, "settings.json");
46155
46603
  let settings = {};
46156
- if (existsSync20(file2)) {
46604
+ if (existsSync21(file2)) {
46157
46605
  try {
46158
- settings = JSON.parse(readFileSync19(file2, "utf8"));
46606
+ settings = JSON.parse(readFileSync20(file2, "utf8"));
46159
46607
  } catch {
46160
46608
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
46161
46609
  process.exit(2);
@@ -46200,21 +46648,21 @@ async function installClaudeHooks(port) {
46200
46648
  dropErrata(list);
46201
46649
  list.push({ hooks: [{ type: "command", command: injectCmd }] });
46202
46650
  }
46203
- writeFileSync15(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
46651
+ writeFileSync16(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
46204
46652
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
46205
46653
  await installClaudeMcpConfig();
46206
46654
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
46207
46655
  }
46208
46656
  async function installClaudeMcpConfig() {
46209
- const { mkdirSync: mkdirSync6, existsSync: existsSync20, readFileSync: readFileSync19, writeFileSync: writeFileSync15 } = await import("node:fs");
46210
- const { join: join23, dirname: dirname8 } = await import("node:path");
46211
- const file2 = join23(ROOT, ".mcp.json");
46657
+ const { mkdirSync: mkdirSync6, existsSync: existsSync21, readFileSync: readFileSync20, writeFileSync: writeFileSync16 } = await import("node:fs");
46658
+ const { join: join24, dirname: dirname8 } = await import("node:path");
46659
+ const file2 = join24(ROOT, ".mcp.json");
46212
46660
  const dir = dirname8(file2);
46213
- if (!existsSync20(dir)) mkdirSync6(dir, { recursive: true });
46661
+ if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
46214
46662
  let cfg = {};
46215
- if (existsSync20(file2)) {
46663
+ if (existsSync21(file2)) {
46216
46664
  try {
46217
- cfg = JSON.parse(readFileSync19(file2, "utf8"));
46665
+ cfg = JSON.parse(readFileSync20(file2, "utf8"));
46218
46666
  } catch {
46219
46667
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
46220
46668
  process.exit(2);
@@ -46222,21 +46670,21 @@ async function installClaudeMcpConfig() {
46222
46670
  }
46223
46671
  cfg.mcpServers ??= {};
46224
46672
  cfg.mcpServers["errata"] = errataMcpInvocation();
46225
- writeFileSync15(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
46673
+ writeFileSync16(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
46226
46674
  console.log(`installed Claude Code MCP server config \u2192 ${file2}`);
46227
46675
  console.log(` Claude Code will spawn \`errata mcp\` on workspace open.`);
46228
46676
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
46229
46677
  }
46230
46678
  async function installCursorMcpConfig() {
46231
- const { mkdirSync: mkdirSync6, existsSync: existsSync20, readFileSync: readFileSync19, writeFileSync: writeFileSync15 } = await import("node:fs");
46232
- const { join: join23 } = await import("node:path");
46233
- const dir = join23(ROOT, ".cursor");
46234
- if (!existsSync20(dir)) mkdirSync6(dir, { recursive: true });
46235
- const file2 = join23(dir, "mcp.json");
46679
+ const { mkdirSync: mkdirSync6, existsSync: existsSync21, readFileSync: readFileSync20, writeFileSync: writeFileSync16 } = await import("node:fs");
46680
+ const { join: join24 } = await import("node:path");
46681
+ const dir = join24(ROOT, ".cursor");
46682
+ if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
46683
+ const file2 = join24(dir, "mcp.json");
46236
46684
  let cfg = {};
46237
- if (existsSync20(file2)) {
46685
+ if (existsSync21(file2)) {
46238
46686
  try {
46239
- cfg = JSON.parse(readFileSync19(file2, "utf8"));
46687
+ cfg = JSON.parse(readFileSync20(file2, "utf8"));
46240
46688
  } catch {
46241
46689
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
46242
46690
  process.exit(2);
@@ -46244,7 +46692,7 @@ async function installCursorMcpConfig() {
46244
46692
  }
46245
46693
  cfg.mcpServers ??= {};
46246
46694
  cfg.mcpServers["errata"] = errataMcpInvocation();
46247
- writeFileSync15(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
46695
+ writeFileSync16(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
46248
46696
  console.log(`installed Cursor MCP server config \u2192 ${file2}`);
46249
46697
  console.log(` Cursor will spawn \`errata mcp\` on workspace open.`);
46250
46698
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses`);
@@ -46252,16 +46700,16 @@ async function installCursorMcpConfig() {
46252
46700
  console.log(` \u26A0\uFE0F reload Cursor (Cmd/Ctrl-Shift-P \u2192 "Reload Window") to pick it up.`);
46253
46701
  }
46254
46702
  async function installCodexHooks(port) {
46255
- const { mkdirSync: mkdirSync6, existsSync: existsSync20, readFileSync: readFileSync19, writeFileSync: writeFileSync15 } = await import("node:fs");
46256
- const { join: join23 } = await import("node:path");
46257
- const dir = join23(ROOT, ".codex");
46258
- if (!existsSync20(dir)) mkdirSync6(dir, { recursive: true });
46259
- const file2 = join23(dir, "config.toml");
46703
+ const { mkdirSync: mkdirSync6, existsSync: existsSync21, readFileSync: readFileSync20, writeFileSync: writeFileSync16 } = await import("node:fs");
46704
+ const { join: join24 } = await import("node:path");
46705
+ const dir = join24(ROOT, ".codex");
46706
+ if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
46707
+ const file2 = join24(dir, "config.toml");
46260
46708
  const BEGIN = `# >>> errata hooks (errata-managed)`;
46261
46709
  const END = `# <<< errata hooks`;
46262
46710
  let existing = "";
46263
- if (existsSync20(file2)) {
46264
- existing = readFileSync19(file2, "utf8");
46711
+ if (existsSync21(file2)) {
46712
+ existing = readFileSync20(file2, "utf8");
46265
46713
  const beginIdx = existing.indexOf(BEGIN);
46266
46714
  const endIdx = existing.indexOf(END);
46267
46715
  if (beginIdx >= 0 && endIdx > beginIdx) {
@@ -46290,7 +46738,7 @@ ${END}
46290
46738
  const final = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
46291
46739
 
46292
46740
  ${block}` : existing + (existing.endsWith("\n\n") ? "" : "\n") + block;
46293
- writeFileSync15(file2, final, "utf8");
46741
+ writeFileSync16(file2, final, "utf8");
46294
46742
  console.log(`installed Codex hooks \u2192 ${file2}`);
46295
46743
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
46296
46744
  console.log("");
@@ -46409,7 +46857,7 @@ async function cmdDash(args2) {
46409
46857
  const reindexOnStart = !args2.includes("--no-reindex");
46410
46858
  const skipEmbed = !args2.includes("--embed");
46411
46859
  const skipWatchers = args2.includes("--no-watch");
46412
- const handle2 = await startMultiDaemon({ webPort: port, reindexOnStart, skipEmbed, skipWatchers });
46860
+ const handle2 = await startMultiDaemon({ webPort: port, reindexOnStart, skipEmbed, skipWatchers, updateCheck: true });
46413
46861
  console.log(`errata multi-daemon running`);
46414
46862
  console.log(` endpoint: ${handle2.url} (JSON \u2014 the human view is \`errata report\`)`);
46415
46863
  console.log(` projects: ${handle2.records.length}`);
@@ -46422,11 +46870,6 @@ async function cmdDash(args2) {
46422
46870
  console.log(` indexing all projects in the background${skipEmbed ? " (--no-embed)" : ""}\u2026`);
46423
46871
  }
46424
46872
  console.log(`Press Ctrl-C to stop.`);
46425
- void checkForUpdate(loadConfig().updateChannel).then((u) => {
46426
- if (u.updateAvailable) {
46427
- console.log(`\u26A1 errata update available: ${u.current} \u2192 ${u.latest} \u2014 run: errata update`);
46428
- }
46429
- });
46430
46873
  const notifyEnabled = loadConfig().notifications && !process.env["ERRATA_NO_NOTIFY"];
46431
46874
  const QUIESCENCE_MS = 9e4;
46432
46875
  let quiesceTimer = null;
@@ -46558,7 +47001,7 @@ async function cmdDash(args2) {
46558
47001
  await yieldToLoop2();
46559
47002
  try {
46560
47003
  const items = selectDurableMemory(handle2.sharedStore, r.engine.profile);
46561
- const res = bleedRules(join22(r.root, ".claude", "rules"), items);
47004
+ const res = bleedRules(join23(r.root, ".claude", "rules"), items);
46562
47005
  if (res.written || res.pruned) {
46563
47006
  console.log(
46564
47007
  `[rules:${r.entry.name}] ${res.written} collective principle(s) \u2192 .claude/rules` + (res.pruned ? `, ${res.pruned} pruned` : "")