@inerrata-corporation/errata 2.0.0-dev.46 → 2.0.0-dev.48

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 +259 -48
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -20936,6 +20936,30 @@ var init_client = __esm({
20936
20936
  async resolveProject(locator, name2) {
20937
20937
  return this.json("POST", "/api/gate/projects/resolve", name2 ? { locator, name: name2 } : { locator });
20938
20938
  }
20939
+ /** Attach this repo's locator to an EXISTING project (fork adopt / alias
20940
+ * accrual) — `errata link --project`. Org-scoped; foreign ids 404. */
20941
+ async attachProject(projectId, locator) {
20942
+ return this.json("POST", "/api/gate/projects/attach", { projectId, locator });
20943
+ }
20944
+ /** Create a locator-less project — `errata link --name` for repos without a
20945
+ * usable remote. Discovery can't find it; the caller stamps the id locally. */
20946
+ async createProject(name2) {
20947
+ return this.json("POST", "/api/gate/projects/create", { name: name2 });
20948
+ }
20949
+ /** Read-only discovery (PJ-attach clone tripwire): the org's claim on a
20950
+ * locator, or null. NEVER creates — 404 means the org hasn't linked it. */
20951
+ async lookupProject(locator) {
20952
+ try {
20953
+ const res = await this.json(
20954
+ "GET",
20955
+ `/api/gate/projects/lookup?locator=${encodeURIComponent(locator)}`
20956
+ );
20957
+ return res.project;
20958
+ } catch (err2) {
20959
+ if (err2 instanceof CloudError && err2.status === 404) return null;
20960
+ throw err2;
20961
+ }
20962
+ }
20939
20963
  // ─── ingest ────────────────────────────────────────────────────────
20940
20964
  /** Upload one outbox batch (projected onto the reconciled wire) and fold
20941
20965
  * the per-decision response into flush accounting.
@@ -38259,8 +38283,8 @@ var init_report_render = __esm({
38259
38283
 
38260
38284
  // src/cli.ts
38261
38285
  init_src5();
38262
- import { closeSync as closeSync2, existsSync as existsSync21, openSync as openSync2, readFileSync as readFileSync20, renameSync as renameSync3, statSync as statSync6 } from "node:fs";
38263
- import { join as join24 } from "node:path";
38286
+ import { closeSync as closeSync2, existsSync as existsSync22, openSync as openSync2, readFileSync as readFileSync20, renameSync as renameSync3, statSync as statSync6 } from "node:fs";
38287
+ import { join as join25 } from "node:path";
38264
38288
  import { spawn as spawn3 } from "node:child_process";
38265
38289
 
38266
38290
  // src/daemon.ts
@@ -39576,9 +39600,9 @@ var NodeFsHandler = class {
39576
39600
  if (this.fsw.closed) {
39577
39601
  return;
39578
39602
  }
39579
- const dirname8 = sysPath.dirname(file2);
39603
+ const dirname9 = sysPath.dirname(file2);
39580
39604
  const basename5 = sysPath.basename(file2);
39581
- const parent = this.fsw._getWatchedDir(dirname8);
39605
+ const parent = this.fsw._getWatchedDir(dirname9);
39582
39606
  let prevStats = stats;
39583
39607
  if (parent.has(basename5))
39584
39608
  return;
@@ -39605,7 +39629,7 @@ var NodeFsHandler = class {
39605
39629
  prevStats = newStats2;
39606
39630
  }
39607
39631
  } catch (error48) {
39608
- this.fsw._remove(dirname8, basename5);
39632
+ this.fsw._remove(dirname9, basename5);
39609
39633
  }
39610
39634
  } else if (parent.has(basename5)) {
39611
39635
  const at = newStats.atimeMs;
@@ -42430,10 +42454,10 @@ function readRemotes(root) {
42430
42454
  }
42431
42455
  return out2;
42432
42456
  }
42433
- function detectRepoLocator(root) {
42457
+ function detectRepoLocator(root, remote) {
42434
42458
  const remotes = readRemotes(root);
42435
42459
  if (remotes.length === 0) return null;
42436
- const pick2 = remotes.find((r) => r.name === "origin") ?? (remotes.length === 1 ? remotes[0] : null);
42460
+ const pick2 = remote ? remotes.find((r) => r.name === remote) ?? null : remotes.find((r) => r.name === "origin") ?? (remotes.length === 1 ? remotes[0] : null);
42437
42461
  return pick2 ? normalizeRepoLocator(pick2.url) : null;
42438
42462
  }
42439
42463
 
@@ -42442,7 +42466,7 @@ function workspaceId(root) {
42442
42466
  return "wp_" + createHash12("sha256").update(root).digest("hex").slice(0, 12);
42443
42467
  }
42444
42468
  function refreshRepoLocator(root, profile) {
42445
- const detected = detectRepoLocator(root);
42469
+ const detected = detectRepoLocator(root, profile.repoRemote);
42446
42470
  if (!detected || detected === profile.repoLocator) return false;
42447
42471
  profile.repoLocator = detected;
42448
42472
  saveProfile(root, profile);
@@ -42669,7 +42693,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
42669
42693
  }
42670
42694
 
42671
42695
  // src/engine.ts
42672
- var DAEMON_VERSION = true ? "2.0.0-dev.46" : "2.0.0-alpha.0";
42696
+ var DAEMON_VERSION = true ? "2.0.0-dev.48" : "2.0.0-alpha.0";
42673
42697
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
42674
42698
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
42675
42699
  var GIT_OP_MUTE_MS = 4e3;
@@ -44730,6 +44754,7 @@ init_cloud_auth();
44730
44754
 
44731
44755
  // src/project-link.ts
44732
44756
  async function ensureProjectLink(root, profile, client) {
44757
+ if (profile.projectLinkDisabled) return { linked: false, reason: "disabled" };
44733
44758
  const locator = profile.repoLocator;
44734
44759
  if (!locator) return { linked: false, reason: "no-locator" };
44735
44760
  if (profile.projectId && profile.projectLocator === locator) {
@@ -44750,6 +44775,48 @@ async function ensureProjectLink(root, profile, client) {
44750
44775
  }
44751
44776
  }
44752
44777
 
44778
+ // src/adopt.ts
44779
+ import { existsSync as existsSync21 } from "node:fs";
44780
+ import { dirname as dirname8, join as join24 } from "node:path";
44781
+ function findGitRoot(absPath) {
44782
+ let dir = absPath;
44783
+ for (let depth = 0; depth < 64; depth++) {
44784
+ if (existsSync21(join24(dir, ".git"))) return dir;
44785
+ const parent = dirname8(dir);
44786
+ if (parent === dir) return null;
44787
+ dir = parent;
44788
+ }
44789
+ return null;
44790
+ }
44791
+ var RETRY_COOLDOWN_MS = 15 * 6e4;
44792
+ function createRootAdopter(deps) {
44793
+ const attempted = /* @__PURE__ */ new Map();
44794
+ let chain = Promise.resolve();
44795
+ const tryAdopt = async (path2) => {
44796
+ if (!deps.linkingAllowed()) return;
44797
+ const root = findGitRoot(path2);
44798
+ if (!root || deps.isKnownRoot(root)) return;
44799
+ const last = attempted.get(root);
44800
+ const now = Date.now();
44801
+ if (last !== void 0 && now - last < RETRY_COOLDOWN_MS) return;
44802
+ attempted.set(root, now);
44803
+ if (!resolveGitDir(root)) return;
44804
+ const locator = detectRepoLocator(root);
44805
+ if (!locator) return;
44806
+ const claimed = await deps.lookup(locator);
44807
+ if (!claimed) return;
44808
+ await deps.adopt(root);
44809
+ deps.log(`[errata] adopted ${root} \u2014 ${locator} is claimed by your org (project ${claimed.id})`);
44810
+ };
44811
+ return {
44812
+ noteMiss(path2) {
44813
+ if (!path2) return;
44814
+ chain = chain.then(() => tryAdopt(path2)).catch(() => void 0);
44815
+ },
44816
+ idle: () => chain
44817
+ };
44818
+ }
44819
+
44753
44820
  // src/multi.ts
44754
44821
  function normPath(p) {
44755
44822
  return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -45012,7 +45079,10 @@ async function startMultiDaemon(opts = {}) {
45012
45079
  return c.json({ error: "bad-json" }, 400);
45013
45080
  }
45014
45081
  const owner = ownerOf(records, pathInBody(body2));
45015
- if (!owner) return c.json({ error: "no-matching-project" }, 404);
45082
+ if (!owner) {
45083
+ rootAdopter.noteMiss(pathInBody(body2));
45084
+ return c.json({ error: "no-matching-project" }, 404);
45085
+ }
45016
45086
  const sub = c.req.path;
45017
45087
  const done = markPass(`${sub}:${owner.entry.name}`);
45018
45088
  try {
@@ -45105,6 +45175,25 @@ async function startMultiDaemon(opts = {}) {
45105
45175
  if (opts.reindexOnStart !== false && records.length > 0) {
45106
45176
  void reindexAll({ skipEmbed: opts.skipEmbed ?? true });
45107
45177
  }
45178
+ const rootAdopter = createRootAdopter({
45179
+ isKnownRoot: (root) => pickOwnerRoot(records.map((r) => r.root), root) !== null,
45180
+ linkingAllowed: () => {
45181
+ const c = loadConfig();
45182
+ return c.consent.sync && hasCloudCredential(c);
45183
+ },
45184
+ lookup: async (locator) => cloudNow().lookupProject(locator),
45185
+ adopt: async (root) => {
45186
+ let profile = loadProfile(root);
45187
+ if (!profile) {
45188
+ profile = autodetectProfile(root);
45189
+ saveProfile(root, profile);
45190
+ }
45191
+ registerWorkspace(profile, root);
45192
+ attachWorkspace(root);
45193
+ },
45194
+ // eslint-disable-next-line no-console
45195
+ log: (line) => console.log(line)
45196
+ });
45108
45197
  const ambientLinkAll = async () => {
45109
45198
  const c = loadConfig();
45110
45199
  if (!c.consent.sync || !hasCloudCredential(c)) return;
@@ -45807,6 +45896,10 @@ async function main() {
45807
45896
  return cmdLogin();
45808
45897
  case "logout":
45809
45898
  return cmdLogout();
45899
+ case "link":
45900
+ return cmdLink(rest);
45901
+ case "unlink":
45902
+ return cmdUnlink();
45810
45903
  case "use":
45811
45904
  return cmdUse(rest);
45812
45905
  case "review":
@@ -45933,6 +46026,11 @@ Commands:
45933
46026
  surface (navigation, problems, claims, burst, health)
45934
46027
  login Sign in with the cloud (OAuth by default; --device for legacy device-code)
45935
46028
  logout Clear local cloud credentials
46029
+ link Corrective project link (ambient linking covers the happy path).
46030
+ Flags: --project <id> adopt an existing project (fork\u2192upstream);
46031
+ --remote <name> derive the locator from a non-origin remote;
46032
+ --name <name> mint a locator-less project; no flags = re-resolve now
46033
+ unlink Detach from the cloud project + disable ambient re-linking
45936
46034
  use [<handle>] Set the sticky session active agent the daemon acts as.
45937
46035
  No arg lists the agents you can act as + the current one;
45938
46036
  --none (or --clear) reverts to your default identity.
@@ -45989,21 +46087,21 @@ async function cmdInit() {
45989
46087
  if (!skipHooks) {
45990
46088
  console.log("");
45991
46089
  console.log("installing harness hooks...");
45992
- const { existsSync: existsSync22 } = await import("node:fs");
45993
- const { join: join25 } = await import("node:path");
46090
+ const { existsSync: existsSync23 } = await import("node:fs");
46091
+ const { join: join26 } = await import("node:path");
45994
46092
  try {
45995
46093
  await installClaudeHooks(port);
45996
46094
  } catch (err2) {
45997
46095
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
45998
46096
  }
45999
- if (existsSync22(join25(ROOT, ".cursor"))) {
46097
+ if (existsSync23(join26(ROOT, ".cursor"))) {
46000
46098
  try {
46001
46099
  await installCursorMcpConfig();
46002
46100
  } catch (err2) {
46003
46101
  console.warn(` \u26A0\uFE0F Cursor MCP config failed: ${err2 instanceof Error ? err2.message : err2}`);
46004
46102
  }
46005
46103
  }
46006
- if (existsSync22(join25(ROOT, ".codex"))) {
46104
+ if (existsSync23(join26(ROOT, ".codex"))) {
46007
46105
  try {
46008
46106
  await installCodexHooks(port);
46009
46107
  } catch (err2) {
@@ -46033,7 +46131,25 @@ async function cmdInit() {
46033
46131
  console.log("");
46034
46132
  console.log("Notifications: desktop toasts (flagged/resolved problems, learned skills)");
46035
46133
  console.log("are ON \u2014 silence with `errata notifications off`; verify with `errata notify-test`.");
46036
- const cfgNow = loadConfig();
46134
+ let cfgNow = loadConfig();
46135
+ if (!hasCloudCredential(cfgNow) && !rest.includes("--no-login")) {
46136
+ if (!process.stdout.isTTY) {
46137
+ console.error("");
46138
+ console.error("errata init requires login (or pass --no-login for a headless, unlinked init).");
46139
+ console.error(" interactive: errata init headless: errata init --no-login && errata login --token <key>");
46140
+ process.exitCode = 1;
46141
+ return;
46142
+ }
46143
+ console.log("");
46144
+ console.log("connecting your cloud account (init links this repo to its project)...");
46145
+ await cmdLoginOAuth(cfgNow);
46146
+ cfgNow = loadConfig();
46147
+ if (!hasCloudCredential(cfgNow)) {
46148
+ console.error("init requires login \u2014 re-run `errata init`, or `errata init --no-login` to stay unlinked.");
46149
+ process.exitCode = 1;
46150
+ return;
46151
+ }
46152
+ }
46037
46153
  console.log("");
46038
46154
  console.log("Next:");
46039
46155
  console.log(" errata start # observe this repo");
@@ -46059,6 +46175,23 @@ async function cmdNotifyTest() {
46059
46175
  }
46060
46176
  }
46061
46177
  async function cmdStart() {
46178
+ if (!loadProfile(ROOT)) {
46179
+ const cfg = loadConfig();
46180
+ const locator = detectRepoLocator(ROOT);
46181
+ if (locator && cfg.consent.sync && hasCloudCredential(cfg)) {
46182
+ try {
46183
+ const claimed = await authedCloudClient(cfg).lookupProject(locator);
46184
+ if (claimed) {
46185
+ const adopted = autodetectProfile(ROOT);
46186
+ saveProfile(ROOT, adopted);
46187
+ console.log(
46188
+ `adopted: ${locator} is claimed by your org (project ${claimed.id}) \u2014 workspace initialized`
46189
+ );
46190
+ }
46191
+ } catch {
46192
+ }
46193
+ }
46194
+ }
46062
46195
  await ensureProfile();
46063
46196
  const profile = loadProfile(ROOT);
46064
46197
  if (profile) registerWorkspace(profile, ROOT);
@@ -46122,8 +46255,8 @@ async function cmdStatus() {
46122
46255
  console.log(` stack: ${profile.stack.join(", ") || "(none)"}`);
46123
46256
  console.log(` languages: ${profile.languages.join(", ") || "(none)"}`);
46124
46257
  }
46125
- console.log(` graph db: ${existsSync21(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
46126
- console.log(` event log: ${existsSync21(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
46258
+ console.log(` graph db: ${existsSync22(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
46259
+ console.log(` event log: ${existsSync22(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
46127
46260
  const lockPath = globalDaemonLock();
46128
46261
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
46129
46262
  console.log(
@@ -46347,6 +46480,84 @@ async function linkRegisteredWorkspaces(cfg) {
46347
46480
  }
46348
46481
  }
46349
46482
  }
46483
+ async function cmdLink(args2) {
46484
+ const flag = (name2) => {
46485
+ const i2 = args2.indexOf(name2);
46486
+ return i2 >= 0 ? args2[i2 + 1] : void 0;
46487
+ };
46488
+ const profile = loadProfile(ROOT);
46489
+ if (!profile) {
46490
+ console.error("not an errata workspace \u2014 run `errata init` first");
46491
+ process.exitCode = 1;
46492
+ return;
46493
+ }
46494
+ const cfg = loadConfig();
46495
+ if (!hasCloudCredential(cfg)) {
46496
+ console.error("not logged in \u2014 run `errata login` first (linking needs your cloud identity)");
46497
+ process.exitCode = 1;
46498
+ return;
46499
+ }
46500
+ const client = authedCloudClient(cfg);
46501
+ delete profile.projectLinkDisabled;
46502
+ const remote = flag("--remote");
46503
+ if (remote) {
46504
+ const loc = detectRepoLocator(ROOT, remote);
46505
+ if (!loc) {
46506
+ console.error(`remote '${remote}' not found or its URL isn't linkable`);
46507
+ process.exitCode = 1;
46508
+ return;
46509
+ }
46510
+ profile.repoRemote = remote;
46511
+ profile.repoLocator = loc;
46512
+ }
46513
+ const adoptId = flag("--project");
46514
+ if (adoptId) {
46515
+ if (!profile.repoLocator) {
46516
+ console.error("this workspace has no repo locator to attach \u2014 use --remote or --name");
46517
+ process.exitCode = 1;
46518
+ return;
46519
+ }
46520
+ const res = await client.attachProject(adoptId, profile.repoLocator);
46521
+ profile.projectId = res.project.id;
46522
+ profile.projectLocator = profile.repoLocator;
46523
+ saveProfile(ROOT, profile);
46524
+ console.log(`linked (adopted): ${profile.repoLocator} \u2192 project ${res.project.id} (${res.project.name})`);
46525
+ return;
46526
+ }
46527
+ const bareName = flag("--name");
46528
+ if (bareName) {
46529
+ const res = await client.createProject(bareName);
46530
+ profile.projectId = res.project.id;
46531
+ delete profile.projectLocator;
46532
+ saveProfile(ROOT, profile);
46533
+ console.log(`linked (locator-less): project ${res.project.id} (${res.project.name})`);
46534
+ return;
46535
+ }
46536
+ saveProfile(ROOT, profile);
46537
+ delete profile.projectId;
46538
+ delete profile.projectLocator;
46539
+ const out2 = await ensureProjectLink(ROOT, profile, client);
46540
+ if (out2.linked) {
46541
+ console.log(`linked: ${profile.repoLocator} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""}`);
46542
+ } else {
46543
+ console.error(`link failed (${out2.reason})${out2.detail ? `: ${out2.detail}` : ""}`);
46544
+ process.exitCode = 1;
46545
+ }
46546
+ }
46547
+ async function cmdUnlink() {
46548
+ const profile = loadProfile(ROOT);
46549
+ if (!profile) {
46550
+ console.error("not an errata workspace \u2014 run `errata init` first");
46551
+ process.exitCode = 1;
46552
+ return;
46553
+ }
46554
+ const had = profile.projectId;
46555
+ delete profile.projectId;
46556
+ delete profile.projectLocator;
46557
+ profile.projectLinkDisabled = true;
46558
+ saveProfile(ROOT, profile);
46559
+ console.log(had ? `unlinked from project ${had} (ambient linking disabled \u2014 re-enable with \`errata link\`)` : "already unlinked (ambient linking disabled)");
46560
+ }
46350
46561
  async function cmdLogin() {
46351
46562
  const cfg = loadConfig();
46352
46563
  const flags2 = parseFlags(rest);
@@ -46460,7 +46671,7 @@ async function cmdUse(args2) {
46460
46671
  }
46461
46672
  async function cmdReview() {
46462
46673
  const paths = workspacePaths(ROOT);
46463
- if (!existsSync21(paths.reviewQueue)) {
46674
+ if (!existsSync22(paths.reviewQueue)) {
46464
46675
  console.log("(review queue empty)");
46465
46676
  return;
46466
46677
  }
@@ -47094,7 +47305,7 @@ async function gatherRepo(store, ws) {
47094
47305
  };
47095
47306
  }
47096
47307
  async function gatherReportData(generatedAt) {
47097
- const { existsSync: existsSync22 } = await import("node:fs");
47308
+ const { existsSync: existsSync23 } = await import("node:fs");
47098
47309
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src4(), src_exports2));
47099
47310
  const cfg = loadConfig();
47100
47311
  const outbound = cfg.consent.sync ? "auto" : "off";
@@ -47102,7 +47313,7 @@ async function gatherReportData(generatedAt) {
47102
47313
  for (const ws of listWorkspaces()) {
47103
47314
  if (ws.missing) continue;
47104
47315
  const dbPath = workspacePaths(ws.path).castalia;
47105
- if (!existsSync22(dbPath)) continue;
47316
+ if (!existsSync23(dbPath)) continue;
47106
47317
  let store = null;
47107
47318
  try {
47108
47319
  store = openGraphStore2({ path: dbPath });
@@ -47146,8 +47357,8 @@ async function cmdReport(args2) {
47146
47357
  const outDir = workspacePaths(ROOT).configDir;
47147
47358
  mkdirSync6(outDir, { recursive: true });
47148
47359
  const files = renderReport2(data, { includeFutureVerbs });
47149
- for (const f of files) writeFileSync16(join24(outDir, f.name), f.html, "utf8");
47150
- const indexPath = join24(outDir, "report.html");
47360
+ for (const f of files) writeFileSync16(join25(outDir, f.name), f.html, "utf8");
47361
+ const indexPath = join25(outDir, "report.html");
47151
47362
  console.log(`report \u2192 ${indexPath}`);
47152
47363
  console.log(` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`);
47153
47364
  console.log(` open: file://${indexPath.replace(/\\/g, "/")}`);
@@ -47267,13 +47478,13 @@ function hookRelayCommand(port, path2) {
47267
47478
  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 '{}'`;
47268
47479
  }
47269
47480
  async function installClaudeHooks(port) {
47270
- const { mkdirSync: mkdirSync6, existsSync: existsSync22, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47271
- const { join: join25 } = await import("node:path");
47272
- const dir = join25(ROOT, ".claude");
47273
- if (!existsSync22(dir)) mkdirSync6(dir, { recursive: true });
47274
- const file2 = join25(dir, "settings.json");
47481
+ const { mkdirSync: mkdirSync6, existsSync: existsSync23, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47482
+ const { join: join26 } = await import("node:path");
47483
+ const dir = join26(ROOT, ".claude");
47484
+ if (!existsSync23(dir)) mkdirSync6(dir, { recursive: true });
47485
+ const file2 = join26(dir, "settings.json");
47275
47486
  let settings = {};
47276
- if (existsSync22(file2)) {
47487
+ if (existsSync23(file2)) {
47277
47488
  try {
47278
47489
  settings = JSON.parse(readFileSync21(file2, "utf8"));
47279
47490
  } catch {
@@ -47324,7 +47535,7 @@ async function installClaudeHooks(port) {
47324
47535
  writeFileSync16(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
47325
47536
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
47326
47537
  await installClaudeMcpConfig();
47327
- const claudeMd = join25(ROOT, "CLAUDE.md");
47538
+ const claudeMd = join26(ROOT, "CLAUDE.md");
47328
47539
  const recall = writeManagedBlock(claudeMd, { body: RECALL_FIRST_BLOCK });
47329
47540
  if (recall.kind === "collision") {
47330
47541
  console.warn(
@@ -47336,13 +47547,13 @@ async function installClaudeHooks(port) {
47336
47547
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
47337
47548
  }
47338
47549
  async function installClaudeMcpConfig() {
47339
- const { mkdirSync: mkdirSync6, existsSync: existsSync22, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47340
- const { join: join25, dirname: dirname8 } = await import("node:path");
47341
- const file2 = join25(ROOT, ".mcp.json");
47342
- const dir = dirname8(file2);
47343
- if (!existsSync22(dir)) mkdirSync6(dir, { recursive: true });
47550
+ const { mkdirSync: mkdirSync6, existsSync: existsSync23, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47551
+ const { join: join26, dirname: dirname9 } = await import("node:path");
47552
+ const file2 = join26(ROOT, ".mcp.json");
47553
+ const dir = dirname9(file2);
47554
+ if (!existsSync23(dir)) mkdirSync6(dir, { recursive: true });
47344
47555
  let cfg = {};
47345
- if (existsSync22(file2)) {
47556
+ if (existsSync23(file2)) {
47346
47557
  try {
47347
47558
  cfg = JSON.parse(readFileSync21(file2, "utf8"));
47348
47559
  } catch {
@@ -47358,13 +47569,13 @@ async function installClaudeMcpConfig() {
47358
47569
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
47359
47570
  }
47360
47571
  async function installCursorMcpConfig() {
47361
- const { mkdirSync: mkdirSync6, existsSync: existsSync22, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47362
- const { join: join25 } = await import("node:path");
47363
- const dir = join25(ROOT, ".cursor");
47364
- if (!existsSync22(dir)) mkdirSync6(dir, { recursive: true });
47365
- const file2 = join25(dir, "mcp.json");
47572
+ const { mkdirSync: mkdirSync6, existsSync: existsSync23, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47573
+ const { join: join26 } = await import("node:path");
47574
+ const dir = join26(ROOT, ".cursor");
47575
+ if (!existsSync23(dir)) mkdirSync6(dir, { recursive: true });
47576
+ const file2 = join26(dir, "mcp.json");
47366
47577
  let cfg = {};
47367
- if (existsSync22(file2)) {
47578
+ if (existsSync23(file2)) {
47368
47579
  try {
47369
47580
  cfg = JSON.parse(readFileSync21(file2, "utf8"));
47370
47581
  } catch {
@@ -47382,15 +47593,15 @@ async function installCursorMcpConfig() {
47382
47593
  console.log(` \u26A0\uFE0F reload Cursor (Cmd/Ctrl-Shift-P \u2192 "Reload Window") to pick it up.`);
47383
47594
  }
47384
47595
  async function installCodexHooks(port) {
47385
- const { mkdirSync: mkdirSync6, existsSync: existsSync22, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47386
- const { join: join25 } = await import("node:path");
47387
- const dir = join25(ROOT, ".codex");
47388
- if (!existsSync22(dir)) mkdirSync6(dir, { recursive: true });
47389
- const file2 = join25(dir, "config.toml");
47596
+ const { mkdirSync: mkdirSync6, existsSync: existsSync23, readFileSync: readFileSync21, writeFileSync: writeFileSync16 } = await import("node:fs");
47597
+ const { join: join26 } = await import("node:path");
47598
+ const dir = join26(ROOT, ".codex");
47599
+ if (!existsSync23(dir)) mkdirSync6(dir, { recursive: true });
47600
+ const file2 = join26(dir, "config.toml");
47390
47601
  const BEGIN = `# >>> errata hooks (errata-managed)`;
47391
47602
  const END = `# <<< errata hooks`;
47392
47603
  let existing = "";
47393
- if (existsSync22(file2)) {
47604
+ if (existsSync23(file2)) {
47394
47605
  existing = readFileSync21(file2, "utf8");
47395
47606
  const beginIdx = existing.indexOf(BEGIN);
47396
47607
  const endIdx = existing.indexOf(END);
@@ -47683,7 +47894,7 @@ async function cmdDash(args2) {
47683
47894
  await yieldToLoop2();
47684
47895
  try {
47685
47896
  const items = selectDurableMemory(handle2.sharedStore, r.engine.profile);
47686
- const res = bleedRules(join24(r.root, ".claude", "rules"), items);
47897
+ const res = bleedRules(join25(r.root, ".claude", "rules"), items);
47687
47898
  if (res.written || res.pruned) {
47688
47899
  console.log(
47689
47900
  `[rules:${r.entry.name}] ${res.written} collective principle(s) \u2192 .claude/rules` + (res.pruned ? `, ${res.pruned} pruned` : "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.0-dev.46",
3
+ "version": "2.0.0-dev.48",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {