@inerrata-corporation/errata 2.0.8-dev.2896 → 2.0.8-dev.2916

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.
@@ -16137,6 +16137,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
16137
16137
  `;
16138
16138
  var TEMPORAL_INDEX_SQL = `
16139
16139
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
16140
+ -- Covering + partial index for labelFingerprint over the instance labels: the
16141
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
16142
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
16143
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
16144
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
16145
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
16140
16146
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
16141
16147
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
16142
16148
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -16207,6 +16213,7 @@ function rowToEdge(r) {
16207
16213
  attrs: JSON.parse(r.attrs_json)
16208
16214
  };
16209
16215
  }
16216
+ var FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
16210
16217
  var SqliteGraphStore = class {
16211
16218
  db;
16212
16219
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -16683,6 +16690,32 @@ var SqliteGraphStore = class {
16683
16690
  attrs: JSON.parse(r.attrs_json)
16684
16691
  }));
16685
16692
  }
16693
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
16694
+ const page = this.db.prepare(
16695
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
16696
+ );
16697
+ let after = 0;
16698
+ for (; ; ) {
16699
+ const rows = page.all(after, label, pageSize);
16700
+ for (const r of rows) {
16701
+ yield {
16702
+ id: r.id,
16703
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
16704
+ qname: typeof r.qname === "string" ? r.qname : null
16705
+ };
16706
+ }
16707
+ if (rows.length < pageSize) return;
16708
+ after = rows[rows.length - 1].rid;
16709
+ }
16710
+ }
16711
+ recentNodeIds(label, limit) {
16712
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
16713
+ return rows.map((r) => r.id);
16714
+ }
16715
+ hasLiveNodes() {
16716
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
16717
+ return r.e === 1;
16718
+ }
16686
16719
  findNodeIdentitiesByLabel(label) {
16687
16720
  const rows = this.stmts.identitiesByLabel.all(label);
16688
16721
  return rows.map((r) => ({
@@ -16830,6 +16863,15 @@ var SqliteGraphStore = class {
16830
16863
  }
16831
16864
  labelFingerprint(labels) {
16832
16865
  if (labels.length === 0) return "0";
16866
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
16867
+ const r2 = this.db.prepare(
16868
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
16869
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
16870
+ FROM nodes INDEXED BY nodes_instance_fingerprint
16871
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
16872
+ ).get();
16873
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
16874
+ }
16833
16875
  const r = this.db.prepare(
16834
16876
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
16835
16877
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
package/errata.mjs CHANGED
@@ -17438,7 +17438,7 @@ function rowToEdge(r) {
17438
17438
  function openGraphStore(opts) {
17439
17439
  return new SqliteGraphStore(opts);
17440
17440
  }
17441
- var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, SqliteGraphStore;
17441
+ var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, SqliteGraphStore;
17442
17442
  var init_store = __esm({
17443
17443
  "../../packages/local-graph/src/store.ts"() {
17444
17444
  "use strict";
@@ -17559,6 +17559,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
17559
17559
  `;
17560
17560
  TEMPORAL_INDEX_SQL = `
17561
17561
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
17562
+ -- Covering + partial index for labelFingerprint over the instance labels: the
17563
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
17564
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
17565
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
17566
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
17567
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
17562
17568
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
17563
17569
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
17564
17570
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -17572,6 +17578,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
17572
17578
  -- every edited file of every turn, and a scan parses every Problem's attrs.
17573
17579
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
17574
17580
  `;
17581
+ FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
17575
17582
  SqliteGraphStore = class {
17576
17583
  db;
17577
17584
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -18048,6 +18055,32 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18048
18055
  attrs: JSON.parse(r.attrs_json)
18049
18056
  }));
18050
18057
  }
18058
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
18059
+ const page = this.db.prepare(
18060
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
18061
+ );
18062
+ let after = 0;
18063
+ for (; ; ) {
18064
+ const rows = page.all(after, label, pageSize);
18065
+ for (const r of rows) {
18066
+ yield {
18067
+ id: r.id,
18068
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
18069
+ qname: typeof r.qname === "string" ? r.qname : null
18070
+ };
18071
+ }
18072
+ if (rows.length < pageSize) return;
18073
+ after = rows[rows.length - 1].rid;
18074
+ }
18075
+ }
18076
+ recentNodeIds(label, limit) {
18077
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
18078
+ return rows.map((r) => r.id);
18079
+ }
18080
+ hasLiveNodes() {
18081
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
18082
+ return r.e === 1;
18083
+ }
18051
18084
  findNodeIdentitiesByLabel(label) {
18052
18085
  const rows = this.stmts.identitiesByLabel.all(label);
18053
18086
  return rows.map((r) => ({
@@ -18195,6 +18228,15 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18195
18228
  }
18196
18229
  labelFingerprint(labels) {
18197
18230
  if (labels.length === 0) return "0";
18231
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
18232
+ const r2 = this.db.prepare(
18233
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18234
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
18235
+ FROM nodes INDEXED BY nodes_instance_fingerprint
18236
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
18237
+ ).get();
18238
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
18239
+ }
18198
18240
  const r = this.db.prepare(
18199
18241
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18200
18242
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
@@ -23978,6 +24020,7 @@ __export(src_exports2, {
23978
24020
  CODE_REACH_EDGES: () => CODE_REACH_EDGES,
23979
24021
  CREDIT_FAMILY: () => CREDIT_FAMILY,
23980
24022
  DEFAULT_MIN_COVERAGE: () => DEFAULT_MIN_COVERAGE,
24023
+ FINGERPRINT_INDEXED_LABELS: () => FINGERPRINT_INDEXED_LABELS,
23981
24024
  FIX_CANDIDATE_ASK_LIMIT: () => FIX_CANDIDATE_ASK_LIMIT,
23982
24025
  FOLD_EMBED_AUTO_COSINE: () => FOLD_EMBED_AUTO_COSINE,
23983
24026
  FOLD_JUDGE_MIN_COSINE: () => FOLD_JUDGE_MIN_COSINE,
@@ -29131,6 +29174,12 @@ function statuslineText(sessionId, now = Date.now()) {
29131
29174
  if (!hit || now - hit.at > STATUS_CARD_TTL_MS) return IDLE_STATUS;
29132
29175
  return renderStatusCard(hit.card);
29133
29176
  }
29177
+ function statuslineDir(body2) {
29178
+ const ws = body2["workspace"];
29179
+ const current = ws && typeof ws === "object" ? ws["current_dir"] : void 0;
29180
+ if (typeof current === "string" && current) return current;
29181
+ return typeof body2["cwd"] === "string" && body2["cwd"] ? body2["cwd"] : null;
29182
+ }
29134
29183
  function recallCardEnabled(env2 = process.env) {
29135
29184
  const v = (env2["ERRATA_RECALL_CARD"] ?? "").trim().toLowerCase();
29136
29185
  return !(v === "0" || v === "off" || v === "false" || v === "no");
@@ -71393,7 +71442,7 @@ var init_report_render = __esm({
71393
71442
 
71394
71443
  // src/cli.ts
71395
71444
  init_src6();
71396
- import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync35, renameSync as renameSync4, statSync as statSync10 } from "node:fs";
71445
+ import { closeSync as closeSync2, existsSync as existsSync35, openSync as openSync2, readFileSync as readFileSync36, renameSync as renameSync4, statSync as statSync10 } from "node:fs";
71397
71446
  import { join as join43, resolve as pathResolve2 } from "node:path";
71398
71447
  import { spawn as spawn5 } from "node:child_process";
71399
71448
 
@@ -72715,9 +72764,9 @@ var NodeFsHandler = class {
72715
72764
  if (this.fsw.closed) {
72716
72765
  return;
72717
72766
  }
72718
- const dirname13 = sysPath.dirname(file2);
72767
+ const dirname14 = sysPath.dirname(file2);
72719
72768
  const basename8 = sysPath.basename(file2);
72720
- const parent = this.fsw._getWatchedDir(dirname13);
72769
+ const parent = this.fsw._getWatchedDir(dirname14);
72721
72770
  let prevStats = stats;
72722
72771
  if (parent.has(basename8))
72723
72772
  return;
@@ -72744,7 +72793,7 @@ var NodeFsHandler = class {
72744
72793
  prevStats = newStats2;
72745
72794
  }
72746
72795
  } catch (error48) {
72747
- this.fsw._remove(dirname13, basename8);
72796
+ this.fsw._remove(dirname14, basename8);
72748
72797
  }
72749
72798
  } else if (parent.has(basename8)) {
72750
72799
  const at = newStats.atimeMs;
@@ -80218,7 +80267,7 @@ function createWatchBreaker(deps) {
80218
80267
  }
80219
80268
 
80220
80269
  // src/engine.ts
80221
- var DAEMON_VERSION = true ? "2.0.8-dev.2896" : "2.0.0-alpha.0";
80270
+ var DAEMON_VERSION = true ? "2.0.8-dev.2916" : "2.0.0-alpha.0";
80222
80271
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80223
80272
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80224
80273
  var GIT_OP_MUTE_MS = 4e3;
@@ -80785,7 +80834,7 @@ function createWorkspaceEngine(opts) {
80785
80834
  lastRemoteAt = now;
80786
80835
  try {
80787
80836
  const norm = normStackTokens;
80788
- const seed = store.findNodesByLabel("Problem").sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 6).map((p) => p.id);
80837
+ const seed = store.recentNodeIds("Problem", 6);
80789
80838
  const q = {
80790
80839
  stack: outboundStack(),
80791
80840
  domains: norm(profile.domains),
@@ -84124,7 +84173,7 @@ function buildReverseSymbolMap(opts) {
84124
84173
  function* reverseSymbolEntries(opts) {
84125
84174
  const { store, salt, projectId } = opts;
84126
84175
  for (const kind of ANCHOR_SYMBOL_KINDS2) {
84127
- for (const n of store.findNodeIdentitiesByLabel(kind)) {
84176
+ for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
84128
84177
  const { relPath, qname } = n;
84129
84178
  if (relPath === null || qname === null) continue;
84130
84179
  const sym = projectSymbolId(salt, projectId, relPath, qname, kind);
@@ -84132,7 +84181,7 @@ function* reverseSymbolEntries(opts) {
84132
84181
  yield [projectSaltedNodeId(sym, salt), n.id];
84133
84182
  }
84134
84183
  }
84135
- for (const n of store.findNodeIdentitiesByLabel("File")) {
84184
+ for (const n of store.iterateNodeIdentitiesByLabel("File")) {
84136
84185
  if (n.relPath === null) continue;
84137
84186
  const sym = projectSymbolId(salt, projectId, n.relPath, FILE_ANCHOR_QNAME, "File");
84138
84187
  yield [sym, n.id];
@@ -84172,7 +84221,7 @@ function buildOwnKnowledgeMap(opts) {
84172
84221
  function* ownKnowledgeEntries(opts) {
84173
84222
  const { store, salt } = opts;
84174
84223
  for (const kind of ANCHOR_SOURCE_KINDS) {
84175
- for (const n of store.findNodeIdentitiesByLabel(kind)) {
84224
+ for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
84176
84225
  if (n.id.startsWith("projn_")) continue;
84177
84226
  yield [projectSaltedNodeId(n.id, salt), n.id];
84178
84227
  }
@@ -86483,7 +86532,7 @@ async function startMultiDaemon(opts = {}) {
86483
86532
  registerWorkspace(rec.engine.profile, rec.root);
86484
86533
  } catch {
86485
86534
  }
86486
- if (wakeIndexPlan(rec.engine.store.nodeCount(), opts.reindexOnStart !== false) === "full-index") {
86535
+ if (wakeIndexPlan(rec.engine.store.hasLiveNodes() ? 1 : 0, opts.reindexOnStart !== false) === "full-index") {
86487
86536
  void enqueueWakeIndex(async () => {
86488
86537
  const done = markPass(`wake-index:${rec.entry.name}`);
86489
86538
  try {
@@ -86706,6 +86755,15 @@ async function startMultiDaemon(opts = {}) {
86706
86755
  app.post("/api/context-inject", (c) => forward(c, cwdOf, { affinity: "follow" }));
86707
86756
  app.post("/api/statusline", async (c) => {
86708
86757
  const body2 = await c.req.json().catch(() => ({}));
86758
+ const dir = statuslineDir(body2);
86759
+ if (dir !== null) {
86760
+ const d = normPath(dir);
86761
+ const known = listWorkspaceEntries().some((e) => {
86762
+ const root = normPath(e.path);
86763
+ return d === root || d.startsWith(`${root}/`);
86764
+ });
86765
+ if (!known) return c.text("");
86766
+ }
86709
86767
  return c.text(statuslineText(String(body2["session_id"] ?? "")));
86710
86768
  });
86711
86769
  for (const r of records) {
@@ -88566,9 +88624,10 @@ function consolidationGapMs(lastPassMs, policy) {
88566
88624
  }
88567
88625
 
88568
88626
  // src/claude-statusline.ts
88569
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync26 } from "node:fs";
88570
- import { join as join42 } from "node:path";
88571
- var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks claude
88627
+ import { existsSync as existsSync34, mkdirSync as mkdirSync10, readFileSync as readFileSync35, writeFileSync as writeFileSync26 } from "node:fs";
88628
+ import { homedir as homedir8 } from "node:os";
88629
+ import { dirname as dirname13, join as join42 } from "node:path";
88630
+ var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata init / install-hooks claude
88572
88631
  import { readFileSync } from 'node:fs';
88573
88632
 
88574
88633
  try {
@@ -88594,15 +88653,57 @@ function buildStatusLine(port, nodePath, relayPath) {
88594
88653
  function isErrataStatusLine(value) {
88595
88654
  return !!value && typeof value === "object" && typeof value["command"] === "string" && value["command"].includes(STATUSLINE_RELAY_FILE);
88596
88655
  }
88597
- function installStatusLine(settings, root, port, nodePath = process.execPath) {
88598
- if (settings["statusLine"] !== void 0 && !isErrataStatusLine(settings["statusLine"])) return false;
88599
- const dir = join42(root, ".claude", "hooks");
88600
- const relay = join42(dir, STATUSLINE_RELAY_FILE);
88601
- mkdirSync10(dir, { recursive: true });
88602
- writeFileSync26(relay, STATUSLINE_RELAY_SOURCE, "utf8");
88603
- settings["statusLine"] = buildStatusLine(port, nodePath, relay);
88656
+ function removeProjectStatusLine(settings) {
88657
+ if (!isErrataStatusLine(settings["statusLine"])) return false;
88658
+ delete settings["statusLine"];
88604
88659
  return true;
88605
88660
  }
88661
+ function userClaudeSettingsPath() {
88662
+ const base = process.env["CLAUDE_CONFIG_DIR"] ?? join42(homedir8(), ".claude");
88663
+ return join42(base, "settings.json");
88664
+ }
88665
+ function ensureUserStatusLine(opts) {
88666
+ const env2 = opts.env ?? process.env;
88667
+ const off = (env2["ERRATA_STATUSLINE"] ?? "").trim().toLowerCase();
88668
+ if (off === "0" || off === "off" || off === "false" || off === "no") return "disabled";
88669
+ const settingsPath = opts.userSettingsPath ?? userClaudeSettingsPath();
88670
+ let settings = {};
88671
+ if (existsSync34(settingsPath)) {
88672
+ try {
88673
+ const parsed = JSON.parse(readFileSync35(settingsPath, "utf8"));
88674
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "unreadable";
88675
+ settings = parsed;
88676
+ } catch {
88677
+ return "unreadable";
88678
+ }
88679
+ }
88680
+ if (settings["statusLine"] !== void 0 && !isErrataStatusLine(settings["statusLine"])) return "kept-own";
88681
+ const relay = join42(opts.relayDir, STATUSLINE_RELAY_FILE);
88682
+ mkdirSync10(opts.relayDir, { recursive: true });
88683
+ if (!existsSync34(relay) || readFileSync35(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
88684
+ writeFileSync26(relay, STATUSLINE_RELAY_SOURCE, "utf8");
88685
+ }
88686
+ const next = buildStatusLine(opts.port, opts.nodePath ?? process.execPath, relay);
88687
+ if (JSON.stringify(settings["statusLine"]) === JSON.stringify(next)) return "unchanged";
88688
+ settings["statusLine"] = next;
88689
+ mkdirSync10(dirname13(settingsPath), { recursive: true });
88690
+ writeFileSync26(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
88691
+ return "installed";
88692
+ }
88693
+ function describeStatusLineOutcome(outcome) {
88694
+ switch (outcome) {
88695
+ case "installed":
88696
+ return "status line: errata recall card in colour (user settings; ERRATA_STATUSLINE=off opts out)";
88697
+ case "unchanged":
88698
+ return "status line: errata recall card already set up";
88699
+ case "kept-own":
88700
+ return "status line: kept your own \u2014 errata's recall card shows inline only";
88701
+ case "disabled":
88702
+ return "status line: skipped (ERRATA_STATUSLINE=off)";
88703
+ case "unreadable":
88704
+ return "status line: skipped \u2014 ~/.claude/settings.json is not valid JSON";
88705
+ }
88706
+ }
88606
88707
 
88607
88708
  // src/opencode-plugin.ts
88608
88709
  var OPENCODE_PLUGIN_MARKER = "// @inerrata opencode plugin \u2014 errata-managed (errata install-hooks opencode)";
@@ -89009,14 +89110,14 @@ async function cmdInit() {
89009
89110
  if (!skipHooks) {
89010
89111
  console.log("");
89011
89112
  console.log("installing harness hooks...");
89012
- const { existsSync: existsSync35 } = await import("node:fs");
89113
+ const { existsSync: existsSync36 } = await import("node:fs");
89013
89114
  const { join: join44 } = await import("node:path");
89014
89115
  try {
89015
89116
  await installClaudeHooks(port);
89016
89117
  } catch (err2) {
89017
89118
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
89018
89119
  }
89019
- if (existsSync35(join44(ROOT, ".cursor"))) {
89120
+ if (existsSync36(join44(ROOT, ".cursor"))) {
89020
89121
  try {
89021
89122
  await installCursorMcpConfig();
89022
89123
  } catch (err2) {
@@ -89118,6 +89219,7 @@ async function cmdStart() {
89118
89219
  await ensureProfile();
89119
89220
  const profile = loadProfile(ROOT);
89120
89221
  if (profile) registerWorkspace(profile, ROOT);
89222
+ if (profile) console.log(`errata: ${ensureStatusLine(resolveHookPort())}`);
89121
89223
  const lockPath = globalDaemonLock();
89122
89224
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
89123
89225
  const where = profile ? `${profile.name} (${profile.id})` : ROOT;
@@ -89205,9 +89307,9 @@ async function cmdStatus() {
89205
89307
  console.log(` stack: ${profile.stack.join(", ") || "(none)"}`);
89206
89308
  console.log(` languages: ${profile.languages.join(", ") || "(none)"}`);
89207
89309
  }
89208
- console.log(` graph db: ${existsSync34(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
89209
- console.log(` event log: ${existsSync34(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
89210
- if (existsSync34(paths.castalia)) {
89310
+ console.log(` graph db: ${existsSync35(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
89311
+ console.log(` event log: ${existsSync35(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
89312
+ if (existsSync35(paths.castalia)) {
89211
89313
  try {
89212
89314
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
89213
89315
  const store = openGraphStore2({ path: paths.castalia });
@@ -89840,6 +89942,7 @@ async function cmdLink(args2) {
89840
89942
  process.exitCode = 1;
89841
89943
  return;
89842
89944
  }
89945
+ console.log(ensureStatusLine(resolveHookPort()));
89843
89946
  delete profile.projectLinkDisabled;
89844
89947
  const remote = flag("--remote");
89845
89948
  if (remote) {
@@ -90176,11 +90279,11 @@ function cmdInstallationProfile(args2) {
90176
90279
  }
90177
90280
  async function cmdReview() {
90178
90281
  const paths = workspacePaths(ROOT);
90179
- if (!existsSync34(paths.reviewQueue)) {
90282
+ if (!existsSync35(paths.reviewQueue)) {
90180
90283
  console.log("(review queue empty)");
90181
90284
  return;
90182
90285
  }
90183
- const queue = JSON.parse(readFileSync35(paths.reviewQueue, "utf8"));
90286
+ const queue = JSON.parse(readFileSync36(paths.reviewQueue, "utf8"));
90184
90287
  if (queue.length === 0) {
90185
90288
  console.log("(review queue empty)");
90186
90289
  return;
@@ -90851,7 +90954,7 @@ async function gatherRepo(store, ws) {
90851
90954
  };
90852
90955
  }
90853
90956
  async function gatherReportData(generatedAt) {
90854
- const { existsSync: existsSync35 } = await import("node:fs");
90957
+ const { existsSync: existsSync36 } = await import("node:fs");
90855
90958
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
90856
90959
  const cfg = loadConfig();
90857
90960
  const outbound = cfg.consent.sync ? "auto" : "off";
@@ -90859,7 +90962,7 @@ async function gatherReportData(generatedAt) {
90859
90962
  for (const ws of listWorkspaces()) {
90860
90963
  if (ws.missing) continue;
90861
90964
  const dbPath = workspacePaths(ws.path).castalia;
90862
- if (!existsSync35(dbPath)) continue;
90965
+ if (!existsSync36(dbPath)) continue;
90863
90966
  let store = null;
90864
90967
  try {
90865
90968
  store = openGraphStore2({ path: dbPath });
@@ -91024,13 +91127,13 @@ async function cmdInstallHooks(args2) {
91024
91127
  }
91025
91128
  }
91026
91129
  async function installOpencodePlugin(port) {
91027
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91130
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91028
91131
  const { join: join44 } = await import("node:path");
91029
91132
  const dir = join44(ROOT, ".opencode", "plugin");
91030
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91133
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91031
91134
  const file2 = join44(dir, "inerrata.js");
91032
- if (existsSync35(file2)) {
91033
- const head2 = readFileSync36(file2, "utf8").split("\n")[0] ?? "";
91135
+ if (existsSync36(file2)) {
91136
+ const head2 = readFileSync37(file2, "utf8").split("\n")[0] ?? "";
91034
91137
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
91035
91138
  console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
91036
91139
  process.exit(2);
@@ -91039,9 +91142,9 @@ async function installOpencodePlugin(port) {
91039
91142
  writeFileSync27(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
91040
91143
  const cfgFile = join44(ROOT, "opencode.json");
91041
91144
  let cfg = { $schema: "https://opencode.ai/config.json" };
91042
- if (existsSync35(cfgFile)) {
91145
+ if (existsSync36(cfgFile)) {
91043
91146
  try {
91044
- cfg = JSON.parse(readFileSync36(cfgFile, "utf8"));
91147
+ cfg = JSON.parse(readFileSync37(cfgFile, "utf8"));
91045
91148
  } catch {
91046
91149
  console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
91047
91150
  process.exit(2);
@@ -91071,16 +91174,23 @@ function errataMcpInvocation() {
91071
91174
  const { cmd: cmd2, args: args2 } = selfArgv("mcp");
91072
91175
  return { command: cmd2, args: args2 };
91073
91176
  }
91177
+ function ensureStatusLine(port) {
91178
+ try {
91179
+ return describeStatusLineOutcome(ensureUserStatusLine({ port, relayDir: globalDir() }));
91180
+ } catch (err2) {
91181
+ return `status line: not installed (${err2 instanceof Error ? err2.message : err2})`;
91182
+ }
91183
+ }
91074
91184
  async function installClaudeHooks(port) {
91075
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91185
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91076
91186
  const { join: join44 } = await import("node:path");
91077
91187
  const dir = join44(ROOT, ".claude");
91078
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91188
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91079
91189
  const file2 = join44(dir, "settings.json");
91080
91190
  let settings = {};
91081
- if (existsSync35(file2)) {
91191
+ if (existsSync36(file2)) {
91082
91192
  try {
91083
- settings = JSON.parse(readFileSync36(file2, "utf8"));
91193
+ settings = JSON.parse(readFileSync37(file2, "utf8"));
91084
91194
  } catch {
91085
91195
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
91086
91196
  process.exit(2);
@@ -91127,18 +91237,10 @@ async function installClaudeHooks(port) {
91127
91237
  dropErrata(list);
91128
91238
  list.push({ hooks: [{ type: "command", command: injectCmd }] });
91129
91239
  }
91130
- const { homedir: homedir8 } = await import("node:os");
91131
- let userStatusLine;
91132
- try {
91133
- userStatusLine = JSON.parse(readFileSync36(join44(homedir8(), ".claude", "settings.json"), "utf8"))["statusLine"];
91134
- } catch {
91135
- }
91136
- const statusLineInstalled = (userStatusLine === void 0 || isErrataStatusLine(userStatusLine)) && installStatusLine(settings, ROOT, port);
91240
+ removeProjectStatusLine(settings);
91137
91241
  writeFileSync27(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
91138
91242
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
91139
- console.log(
91140
- statusLineInstalled ? " status line: errata recall card in colour (daemon env ERRATA_RECALL_CARD=off hides the cards)" : " status line: kept your own \u2014 errata's recall card shows inline only"
91141
- );
91243
+ console.log(` ${ensureStatusLine(port)}`);
91142
91244
  await installClaudeMcpConfig();
91143
91245
  const claudeMd = join44(ROOT, "CLAUDE.md");
91144
91246
  const recall = writeManagedBlock(claudeMd, { body: RECALL_FIRST_BLOCK });
@@ -91152,15 +91254,15 @@ async function installClaudeHooks(port) {
91152
91254
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
91153
91255
  }
91154
91256
  async function installClaudeMcpConfig() {
91155
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91156
- const { join: join44, dirname: dirname13 } = await import("node:path");
91257
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91258
+ const { join: join44, dirname: dirname14 } = await import("node:path");
91157
91259
  const file2 = join44(ROOT, ".mcp.json");
91158
- const dir = dirname13(file2);
91159
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91260
+ const dir = dirname14(file2);
91261
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91160
91262
  let cfg = {};
91161
- if (existsSync35(file2)) {
91263
+ if (existsSync36(file2)) {
91162
91264
  try {
91163
- cfg = JSON.parse(readFileSync36(file2, "utf8"));
91265
+ cfg = JSON.parse(readFileSync37(file2, "utf8"));
91164
91266
  } catch {
91165
91267
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
91166
91268
  process.exit(2);
@@ -91174,15 +91276,15 @@ async function installClaudeMcpConfig() {
91174
91276
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
91175
91277
  }
91176
91278
  async function installCursorMcpConfig() {
91177
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91279
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91178
91280
  const { join: join44 } = await import("node:path");
91179
91281
  const dir = join44(ROOT, ".cursor");
91180
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91282
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91181
91283
  const file2 = join44(dir, "mcp.json");
91182
91284
  let cfg = {};
91183
- if (existsSync35(file2)) {
91285
+ if (existsSync36(file2)) {
91184
91286
  try {
91185
- cfg = JSON.parse(readFileSync36(file2, "utf8"));
91287
+ cfg = JSON.parse(readFileSync37(file2, "utf8"));
91186
91288
  } catch {
91187
91289
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
91188
91290
  process.exit(2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.8-dev.2896",
3
+ "version": "2.0.8-dev.2916",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24519,6 +24519,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
24519
24519
  `;
24520
24520
  var TEMPORAL_INDEX_SQL = `
24521
24521
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
24522
+ -- Covering + partial index for labelFingerprint over the instance labels: the
24523
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
24524
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
24525
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
24526
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
24527
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
24522
24528
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
24523
24529
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
24524
24530
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -24589,6 +24595,7 @@ function rowToEdge(r) {
24589
24595
  attrs: JSON.parse(r.attrs_json)
24590
24596
  };
24591
24597
  }
24598
+ var FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
24592
24599
  var SqliteGraphStore = class {
24593
24600
  db;
24594
24601
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -25065,6 +25072,32 @@ var SqliteGraphStore = class {
25065
25072
  attrs: JSON.parse(r.attrs_json)
25066
25073
  }));
25067
25074
  }
25075
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
25076
+ const page = this.db.prepare(
25077
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
25078
+ );
25079
+ let after = 0;
25080
+ for (; ; ) {
25081
+ const rows = page.all(after, label, pageSize);
25082
+ for (const r of rows) {
25083
+ yield {
25084
+ id: r.id,
25085
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
25086
+ qname: typeof r.qname === "string" ? r.qname : null
25087
+ };
25088
+ }
25089
+ if (rows.length < pageSize) return;
25090
+ after = rows[rows.length - 1].rid;
25091
+ }
25092
+ }
25093
+ recentNodeIds(label, limit) {
25094
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
25095
+ return rows.map((r) => r.id);
25096
+ }
25097
+ hasLiveNodes() {
25098
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
25099
+ return r.e === 1;
25100
+ }
25068
25101
  findNodeIdentitiesByLabel(label) {
25069
25102
  const rows = this.stmts.identitiesByLabel.all(label);
25070
25103
  return rows.map((r) => ({
@@ -25212,6 +25245,15 @@ var SqliteGraphStore = class {
25212
25245
  }
25213
25246
  labelFingerprint(labels) {
25214
25247
  if (labels.length === 0) return "0";
25248
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
25249
+ const r2 = this.db.prepare(
25250
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
25251
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
25252
+ FROM nodes INDEXED BY nodes_instance_fingerprint
25253
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
25254
+ ).get();
25255
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
25256
+ }
25215
25257
  const r = this.db.prepare(
25216
25258
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
25217
25259
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s