@inerrata-corporation/errata 2.0.8 → 2.0.9
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/consolidate-worker.mjs +42 -0
- package/errata.mjs +162 -60
- package/package.json +1 -1
- package/pass-worker.mjs +42 -0
package/consolidate-worker.mjs
CHANGED
|
@@ -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
|
|
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
|
|
72767
|
+
const dirname14 = sysPath.dirname(file2);
|
|
72719
72768
|
const basename8 = sysPath.basename(file2);
|
|
72720
|
-
const parent = this.fsw._getWatchedDir(
|
|
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(
|
|
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.
|
|
80270
|
+
var DAEMON_VERSION = true ? "2.0.9" : "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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
88571
|
-
|
|
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
|
|
88598
|
-
if (
|
|
88599
|
-
|
|
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:
|
|
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 (
|
|
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: ${
|
|
89209
|
-
console.log(` event log: ${
|
|
89210
|
-
if (
|
|
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 (!
|
|
90282
|
+
if (!existsSync35(paths.reviewQueue)) {
|
|
90180
90283
|
console.log("(review queue empty)");
|
|
90181
90284
|
return;
|
|
90182
90285
|
}
|
|
90183
|
-
const queue = JSON.parse(
|
|
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:
|
|
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 (!
|
|
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:
|
|
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 (!
|
|
91133
|
+
if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
|
|
91031
91134
|
const file2 = join44(dir, "inerrata.js");
|
|
91032
|
-
if (
|
|
91033
|
-
const head2 =
|
|
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 (
|
|
91145
|
+
if (existsSync36(cfgFile)) {
|
|
91043
91146
|
try {
|
|
91044
|
-
cfg = JSON.parse(
|
|
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:
|
|
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 (!
|
|
91188
|
+
if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
|
|
91079
91189
|
const file2 = join44(dir, "settings.json");
|
|
91080
91190
|
let settings = {};
|
|
91081
|
-
if (
|
|
91191
|
+
if (existsSync36(file2)) {
|
|
91082
91192
|
try {
|
|
91083
|
-
settings = JSON.parse(
|
|
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
|
-
|
|
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:
|
|
91156
|
-
const { join: join44, dirname:
|
|
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 =
|
|
91159
|
-
if (!
|
|
91260
|
+
const dir = dirname14(file2);
|
|
91261
|
+
if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
|
|
91160
91262
|
let cfg = {};
|
|
91161
|
-
if (
|
|
91263
|
+
if (existsSync36(file2)) {
|
|
91162
91264
|
try {
|
|
91163
|
-
cfg = JSON.parse(
|
|
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:
|
|
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 (!
|
|
91282
|
+
if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
|
|
91181
91283
|
const file2 = join44(dir, "mcp.json");
|
|
91182
91284
|
let cfg = {};
|
|
91183
|
-
if (
|
|
91285
|
+
if (existsSync36(file2)) {
|
|
91184
91286
|
try {
|
|
91185
|
-
cfg = JSON.parse(
|
|
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
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
|