@inerrata-corporation/errata 2.0.2-dev.302 → 2.0.2-dev.307

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.
Files changed (2) hide show
  1. package/errata.mjs +136 -32
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -28119,6 +28119,96 @@ var init_src10 = __esm({
28119
28119
  }
28120
28120
  });
28121
28121
 
28122
+ // src/llm-provider.ts
28123
+ function chatProvider() {
28124
+ return (process.env["EXTRACTION_PROVIDER"] ?? "anthropic").toLowerCase() === "azure" ? "azure" : "anthropic";
28125
+ }
28126
+ function deploymentFor(model) {
28127
+ const key = `MODEL_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_OPENAI`;
28128
+ return process.env[key] ?? process.env["AZURE_OPENAI_DEPLOYMENT"];
28129
+ }
28130
+ function isChatConfigured(model) {
28131
+ if (chatProvider() === "azure") {
28132
+ return Boolean(
28133
+ process.env["AZURE_OPENAI_API_KEY"] && process.env["AZURE_OPENAI_ENDPOINT"] && deploymentFor(model)
28134
+ );
28135
+ }
28136
+ return Boolean(process.env["ANTHROPIC_API_KEY"]);
28137
+ }
28138
+ async function chat(req) {
28139
+ const provider = chatProvider();
28140
+ try {
28141
+ if (provider === "azure") {
28142
+ const endpoint = (process.env["AZURE_OPENAI_ENDPOINT"] ?? "").replace(/\/+$/, "");
28143
+ const version2 = process.env["AZURE_OPENAI_API_VERSION"] ?? "2024-10-21";
28144
+ const deployment = deploymentFor(req.model);
28145
+ if (!endpoint || !deployment) {
28146
+ console.warn("[errata] llm: azure selected but endpoint/deployment missing \u2014 skipping");
28147
+ return null;
28148
+ }
28149
+ const resp2 = await fetch(
28150
+ `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${version2}`,
28151
+ {
28152
+ method: "POST",
28153
+ headers: {
28154
+ "api-key": process.env["AZURE_OPENAI_API_KEY"] ?? "",
28155
+ "content-type": "application/json"
28156
+ },
28157
+ body: JSON.stringify({
28158
+ messages: [
28159
+ ...req.system ? [{ role: "system", content: req.system }] : [],
28160
+ { role: "user", content: req.user }
28161
+ ],
28162
+ // `max_completion_tokens`: the newer deployments reject `max_tokens`.
28163
+ max_completion_tokens: req.maxTokens
28164
+ })
28165
+ }
28166
+ );
28167
+ if (!resp2.ok) {
28168
+ console.warn(
28169
+ `[errata] llm: azure ${resp2.status} \u2014 ${(await resp2.text().catch(() => "")).slice(0, 200)}`
28170
+ );
28171
+ return null;
28172
+ }
28173
+ const json3 = await resp2.json();
28174
+ return json3.choices?.[0]?.message?.content ?? null;
28175
+ }
28176
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
28177
+ method: "POST",
28178
+ headers: {
28179
+ "x-api-key": process.env["ANTHROPIC_API_KEY"] ?? "",
28180
+ "anthropic-version": "2023-06-01",
28181
+ "content-type": "application/json"
28182
+ },
28183
+ body: JSON.stringify({
28184
+ model: req.model,
28185
+ max_tokens: req.maxTokens,
28186
+ ...req.system ? { system: req.system } : {},
28187
+ messages: [{ role: "user", content: req.user }]
28188
+ })
28189
+ });
28190
+ if (!resp.ok) {
28191
+ console.warn(
28192
+ `[errata] llm: anthropic ${resp.status} \u2014 ${(await resp.text().catch(() => "")).slice(0, 200)}`
28193
+ );
28194
+ return null;
28195
+ }
28196
+ const json2 = await resp.json();
28197
+ return json2.content?.find((c) => c.type === "text")?.text ?? null;
28198
+ } catch (err2) {
28199
+ console.warn(
28200
+ `[errata] llm: ${provider} transport failed \u2014`,
28201
+ err2 instanceof Error ? err2.message : err2
28202
+ );
28203
+ return null;
28204
+ }
28205
+ }
28206
+ var init_llm_provider = __esm({
28207
+ "src/llm-provider.ts"() {
28208
+ "use strict";
28209
+ }
28210
+ });
28211
+
28122
28212
  // src/symbol-summaries.ts
28123
28213
  import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
28124
28214
  import { join as join7 } from "node:path";
@@ -28148,6 +28238,19 @@ function summariesByBodyHash(cache) {
28148
28238
  }
28149
28239
  return m;
28150
28240
  }
28241
+ function buildDocIndex(store) {
28242
+ const byName = /* @__PURE__ */ new Map();
28243
+ for (const c of store.findNodesByLabel("Comment")) {
28244
+ const name2 = c.attrs["attachedTo"];
28245
+ const kind = c.attrs["kind"];
28246
+ if (typeof name2 !== "string" || !name2) continue;
28247
+ if (typeof kind !== "string" || !DOC_COMMENT_KINDS.has(kind)) continue;
28248
+ const text = typeof c.attrs["text"] === "string" ? c.attrs["text"] : c.description;
28249
+ if (!text?.trim()) continue;
28250
+ if (!byName.has(name2)) byName.set(name2, text.trim().slice(0, MAX_DOC_CHARS));
28251
+ }
28252
+ return byName;
28253
+ }
28151
28254
  function parseSummaryJson(text, expected) {
28152
28255
  const start2 = text.indexOf("[");
28153
28256
  const end = text.lastIndexOf("]");
@@ -28166,44 +28269,32 @@ function parseSummaryJson(text, expected) {
28166
28269
  }
28167
28270
  return out2;
28168
28271
  }
28169
- function haikuIntentSummarizer(apiKey) {
28170
- if (!apiKey) return void 0;
28272
+ function haikuIntentSummarizer(_apiKey) {
28273
+ if (!isChatConfigured(SUMMARY_MODEL)) return void 0;
28171
28274
  return async (symbols) => {
28172
- try {
28173
- const listing = symbols.map(
28174
- (s, i2) => `### ${i2 + 1}. kind: ${s.kind}
28275
+ const listing = symbols.map(
28276
+ (s, i2) => `### ${i2 + 1}. kind: ${s.kind}
28175
28277
  name: ${s.name}
28176
- ${s.snippet ? `\`\`\`
28278
+ ` + (s.doc ? `doc: ${s.doc}
28279
+ ` : "") + `${s.snippet ? `\`\`\`
28177
28280
  ${s.snippet}
28178
28281
  \`\`\`` : "(no source available \u2014 describe from the name)"}`
28179
- ).join("\n\n");
28180
- const resp = await fetch("https://api.anthropic.com/v1/messages", {
28181
- method: "POST",
28182
- headers: {
28183
- "x-api-key": apiKey,
28184
- "anthropic-version": "2023-06-01",
28185
- "content-type": "application/json"
28186
- },
28187
- body: JSON.stringify({
28188
- model: SUMMARY_MODEL,
28189
- max_tokens: 2e3,
28190
- messages: [{ role: "user", content: `${SUMMARY_PROMPT}
28282
+ ).join("\n\n");
28283
+ const text = await chat({
28284
+ model: SUMMARY_MODEL,
28285
+ user: `${SUMMARY_PROMPT}
28191
28286
 
28192
28287
  SYMBOLS:
28193
- ${listing}` }]
28194
- })
28195
- });
28196
- if (!resp.ok) return symbols.map(() => null);
28197
- const json2 = await resp.json();
28198
- return parseSummaryJson(json2.content?.find((c) => c.type === "text")?.text ?? "", symbols.length);
28199
- } catch {
28200
- return symbols.map(() => null);
28201
- }
28288
+ ${listing}`,
28289
+ maxTokens: 2e3
28290
+ });
28291
+ if (text === null) return symbols.map(() => null);
28292
+ return parseSummaryJson(text, symbols.length);
28202
28293
  };
28203
28294
  }
28204
28295
  function envIntentSummarizer() {
28205
28296
  if (process.env["ERRATA_SYMBOL_SUMMARIES"] !== "1") return void 0;
28206
- return haikuIntentSummarizer(process.env["ANTHROPIC_API_KEY"] ?? null);
28297
+ return haikuIntentSummarizer();
28207
28298
  }
28208
28299
  function readSnippet(workspaceRoot, attrs) {
28209
28300
  const relPath = attrs["relPath"];
@@ -28227,6 +28318,7 @@ async function runSymbolSummarySweep(store, workspaceRoot, configDir, summarizer
28227
28318
  const cache = loadSymbolSummaryCache(configDir);
28228
28319
  const candidates = [];
28229
28320
  const seen = /* @__PURE__ */ new Set();
28321
+ const docs = buildDocIndex(store);
28230
28322
  for (const [label, kind] of SUMMARY_LABELS) {
28231
28323
  for (const n of store.findNodesByLabel(label)) {
28232
28324
  const bodyHash = n.attrs["bodyHash"];
@@ -28236,7 +28328,15 @@ async function runSymbolSummarySweep(store, workspaceRoot, configDir, summarizer
28236
28328
  if (!name2 || !isDistinctiveIdentifier(name2)) continue;
28237
28329
  seen.add(bodyHash);
28238
28330
  const snippet = readSnippet(workspaceRoot, n.attrs);
28239
- candidates.push({ bodyHash, name: name2, kind, ...snippet ? { snippet } : {} });
28331
+ const qname = typeof n.attrs["qname"] === "string" ? n.attrs["qname"] : void 0;
28332
+ const doc = docs.get(name2) ?? (qname ? docs.get(qname) : void 0);
28333
+ candidates.push({
28334
+ bodyHash,
28335
+ name: name2,
28336
+ kind,
28337
+ ...snippet ? { snippet } : {},
28338
+ ...doc ? { doc } : {}
28339
+ });
28240
28340
  }
28241
28341
  }
28242
28342
  const todo = candidates.slice(0, maxSymbols);
@@ -28266,12 +28366,13 @@ async function runSymbolSummarySweep(store, workspaceRoot, configDir, summarizer
28266
28366
  }
28267
28367
  return { generated, rejected, remaining };
28268
28368
  }
28269
- var SUMMARY_LABELS, MAX_SNIPPET_CHARS, DEFAULT_MAX_SYMBOLS_PER_SWEEP, DEFAULT_BATCH_SIZE, SUMMARY_MODEL, SUMMARY_PROMPT;
28369
+ var SUMMARY_LABELS, MAX_SNIPPET_CHARS, DEFAULT_MAX_SYMBOLS_PER_SWEEP, DEFAULT_BATCH_SIZE, DOC_COMMENT_KINDS, MAX_DOC_CHARS, SUMMARY_MODEL, SUMMARY_PROMPT;
28270
28370
  var init_symbol_summaries = __esm({
28271
28371
  "src/symbol-summaries.ts"() {
28272
28372
  "use strict";
28273
28373
  init_src();
28274
28374
  init_generalize_graph();
28375
+ init_llm_provider();
28275
28376
  SUMMARY_LABELS = [
28276
28377
  ["Function", "function"],
28277
28378
  ["Method", "method"],
@@ -28280,10 +28381,13 @@ var init_symbol_summaries = __esm({
28280
28381
  MAX_SNIPPET_CHARS = 1500;
28281
28382
  DEFAULT_MAX_SYMBOLS_PER_SWEEP = 64;
28282
28383
  DEFAULT_BATCH_SIZE = 16;
28384
+ DOC_COMMENT_KINDS = /* @__PURE__ */ new Set(["docstring", "explanation"]);
28385
+ MAX_DOC_CHARS = 600;
28283
28386
  SUMMARY_MODEL = "claude-haiku-4-5-20251001";
28284
28387
  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").
28388
+ Some symbols include a "doc:" line \u2014 the author's own description of what it is for. When present TRUST IT as the primary source of intent and translate it into the required shape; the code is secondary evidence. Carry the SPECIFIC job the symbol does, not the category it belongs to: "a function that releases a pooled connection on every exit path including cancellation" is useful; "a function that handles cleanup" is not.
28285
28389
  HARD RULES \u2014 a violating phrase is discarded:
28286
- - 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.
28390
+ - NEVER include any identifier, symbol name, variable, type name, file path, or module name from the code or the doc \u2014 describe meaning in plain English words only.
28287
28391
  - NEVER include quoted strings, literals, numbers copied from the code, or code syntax.
28288
28392
  - One phrase per symbol, under 200 characters, lowercase start, no trailing period.
28289
28393
  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.`;
@@ -52902,7 +53006,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
52902
53006
  }
52903
53007
 
52904
53008
  // src/engine.ts
52905
- var DAEMON_VERSION = true ? "2.0.2-dev.302" : "2.0.0-alpha.0";
53009
+ var DAEMON_VERSION = true ? "2.0.2-dev.307" : "2.0.0-alpha.0";
52906
53010
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
52907
53011
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
52908
53012
  var GIT_OP_MUTE_MS = 4e3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.302",
3
+ "version": "2.0.2-dev.307",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {