sphica 0.6.11 → 0.6.13

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/dist/deliver.js CHANGED
@@ -15,9 +15,9 @@ var __export = (target, all) => {
15
15
  };
16
16
 
17
17
  // server/src/deliver.ts
18
- import fs6 from "node:fs";
18
+ import fs7 from "node:fs";
19
19
  import os2 from "node:os";
20
- import path7 from "node:path";
20
+ import path8 from "node:path";
21
21
 
22
22
  // server/src/anchors.ts
23
23
  import path from "node:path";
@@ -279,8 +279,8 @@ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path.sep}`) || path.is
279
279
 
280
280
  // server/src/capture.ts
281
281
  import { spawn } from "node:child_process";
282
- import fs4 from "node:fs";
283
- import path5 from "node:path";
282
+ import fs5 from "node:fs";
283
+ import path6 from "node:path";
284
284
 
285
285
  // server/node_modules/kysely/dist/util/object-utils.js
286
286
  function isUndefined(obj) {
@@ -11085,21 +11085,65 @@ function adapt(raw) {
11085
11085
  }
11086
11086
 
11087
11087
  // server/src/sqlite.ts
11088
- import fs from "node:fs";
11088
+ import fs2 from "node:fs";
11089
11089
  import os from "node:os";
11090
- import path2 from "node:path";
11090
+ import path3 from "node:path";
11091
11091
  import { constants as C, DatabaseSync } from "node:sqlite";
11092
+
11093
+ // server/src/plugin.ts
11094
+ import fs from "node:fs";
11095
+ import path2 from "node:path";
11096
+ import { fileURLToPath } from "node:url";
11097
+
11098
+ // server/src/panel.ts
11099
+ import { stripVTControlCharacters, styleText } from "node:util";
11100
+ var title = (text) => `✦ ${text}`;
11101
+ var rule = (text) => text.split(`
11102
+ `).map((line) => line ? `│ ${line}` : "│").join(`
11103
+ `);
11104
+ var foot = (text) => `╰─ ${text}`;
11105
+ var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
11106
+ `);
11107
+ var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
11108
+ var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
11109
+ `).replace(/(?![\t\n])\p{Cc}/gu, ""));
11110
+ var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
11111
+
11112
+ // server/src/plugin.ts
11113
+ var MANIFEST = path2.join(".claude-plugin", "plugin.json");
11114
+ var PACKAGE = "package.json";
11115
+ function versionAt(root) {
11116
+ try {
11117
+ const m = JSON.parse(fs.readFileSync(path2.join(root, MANIFEST), "utf8"));
11118
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
11119
+ } catch {
11120
+ return null;
11121
+ }
11122
+ }
11123
+ function packageVersionAt(root) {
11124
+ try {
11125
+ const m = JSON.parse(fs.readFileSync(path2.join(root, PACKAGE), "utf8"));
11126
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
11127
+ } catch {
11128
+ return null;
11129
+ }
11130
+ }
11131
+ var here = path2.dirname(fileURLToPath(import.meta.url));
11132
+ var ROOT = [path2.join(here, ".."), path2.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path2.join(here, "..");
11133
+ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11134
+
11135
+ // server/src/sqlite.ts
11092
11136
  var SCHEMA_GENERATION = 2;
11093
11137
  var SCHEMA_REVISION = 4;
11094
- var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
11095
- var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
11138
+ var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os.homedir(), ".sphica");
11139
+ var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
11096
11140
  function requireRuntime() {
11097
11141
  const proto = DatabaseSync.prototype;
11098
11142
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
11099
11143
  throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
11100
11144
  }
11101
11145
  function requireFile(file) {
11102
- if (!fs.existsSync(file))
11146
+ if (!fs2.existsSync(file))
11103
11147
  throw new Error(`No database at ${file}. Create it with \`sphica init\`.`);
11104
11148
  }
11105
11149
  function prepare(raw, check) {
@@ -11114,7 +11158,13 @@ function prepare(raw, check) {
11114
11158
  const got = raw.prepare("pragma user_version").get()?.user_version;
11115
11159
  if (got === SCHEMA_REVISION)
11116
11160
  return;
11117
- throw new Error(`The database schema is revision ${got}, but this Sphica expects revision ${SCHEMA_REVISION}. ` + ((got ?? 0) < SCHEMA_REVISION ? "Update the sphica CLI (`npm i -g sphica`), then run `sphica init` to migrate it (records are kept)." : "Update sphica."));
11161
+ throw new RevisionMismatch(got ?? 0);
11162
+ }
11163
+
11164
+ class RevisionMismatch extends Error {
11165
+ constructor(got) {
11166
+ super(`The database schema is revision ${got}, but this Sphica expects revision ${SCHEMA_REVISION}. ` + (got < SCHEMA_REVISION ? `Run \`npm i -g sphica@${packageVersionAt(ROOT) ?? "latest"}\`, then \`sphica init\` to migrate it (records are kept, and a backup is made first).` : "Update sphica."));
11167
+ }
11118
11168
  }
11119
11169
  function generationOf(raw) {
11120
11170
  const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
@@ -11351,7 +11401,7 @@ function connectWriter(role, file = dbFile(), create = false) {
11351
11401
  raw.setAuthorizer((action, p1, p2, _db, triggerOrView) => captureAuthorizer(own, action, p1, p2, triggerOrView));
11352
11402
  const prepare2 = raw.prepare.bind(raw);
11353
11403
  const exec = raw.exec.bind(raw);
11354
- const mark = (f) => (...a) => {
11404
+ const mark2 = (f) => (...a) => {
11355
11405
  own = true;
11356
11406
  try {
11357
11407
  return f(...a);
@@ -11359,8 +11409,8 @@ function connectWriter(role, file = dbFile(), create = false) {
11359
11409
  own = false;
11360
11410
  }
11361
11411
  };
11362
- raw.prepare = mark(prepare2);
11363
- raw.exec = mark(exec);
11412
+ raw.prepare = mark2(prepare2);
11413
+ raw.exec = mark2(exec);
11364
11414
  }
11365
11415
  return raw;
11366
11416
  }
@@ -11371,25 +11421,11 @@ function openWriter(role, file = dbFile()) {
11371
11421
  // server/src/knowledge.ts
11372
11422
  var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), host, externalId);
11373
11423
 
11374
- // server/src/panel.ts
11375
- import { stripVTControlCharacters, styleText } from "node:util";
11376
- var title = (text) => `✦ ${text}`;
11377
- var rule = (text) => text.split(`
11378
- `).map((line) => line ? `│ ${line}` : "│").join(`
11379
- `);
11380
- var foot = (text) => `╰─ ${text}`;
11381
- var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
11382
- `);
11383
- var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
11384
- var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
11385
- `).replace(/(?![\t\n])\p{Cc}/gu, ""));
11386
- var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
11387
-
11388
11424
  // server/src/project.ts
11389
11425
  import { execFileSync } from "node:child_process";
11390
- import fs2 from "node:fs";
11391
- import path3 from "node:path";
11392
- var localFile = () => path3.join(sphicaHome(), "projects.json");
11426
+ import fs3 from "node:fs";
11427
+ import path4 from "node:path";
11428
+ var localFile = () => path4.join(sphicaHome(), "projects.json");
11393
11429
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
11394
11430
  function normalizeRemote(url) {
11395
11431
  const raw = String(url ?? "").trim();
@@ -11422,7 +11458,7 @@ var git = (dir, ...args) => {
11422
11458
  function localMap() {
11423
11459
  let raw;
11424
11460
  try {
11425
- raw = fs2.readFileSync(localFile(), "utf8");
11461
+ raw = fs3.readFileSync(localFile(), "utf8");
11426
11462
  } catch (e) {
11427
11463
  if (e.code === "ENOENT")
11428
11464
  return {};
@@ -11434,23 +11470,23 @@ function localMap() {
11434
11470
  } catch {
11435
11471
  m = null;
11436
11472
  }
11437
- if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path3.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
11473
+ if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path4.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
11438
11474
  throw new Error(`${localFile()} is not a valid JSON project table. Fix or delete it, then name the project again.`);
11439
11475
  return m;
11440
11476
  }
11441
11477
  function identify(dir) {
11442
- const given = path3.resolve(dir);
11478
+ const given = path4.resolve(dir);
11443
11479
  const top = git(given, "rev-parse", "--show-toplevel");
11444
11480
  const root = top || given;
11445
11481
  const remote = top ? normalizeRemote(git(root, "remote", "get-url", "origin")) : null;
11446
11482
  if (remote)
11447
11483
  return { key: `git:${remote}`, root, name: remote.split("/").slice(1).join("/") || remote };
11448
11484
  const map = localMap();
11449
- for (let d = root;; d = path3.dirname(d)) {
11485
+ for (let d = root;; d = path4.dirname(d)) {
11450
11486
  const local = map[d];
11451
11487
  if (local && LOCAL_KEY.test(local))
11452
11488
  return { key: `local:${local}`, root: d, name: local };
11453
- if (top || path3.dirname(d) === d)
11489
+ if (top || path4.dirname(d) === d)
11454
11490
  return null;
11455
11491
  }
11456
11492
  }
@@ -11459,11 +11495,11 @@ async function projectId(db, key) {
11459
11495
  return r?.id ?? null;
11460
11496
  }
11461
11497
  function relativeTo(root, file, cwd = root) {
11462
- const abs = path3.resolve(cwd, file);
11463
- const rel = path3.relative(root, abs);
11498
+ const abs = path4.resolve(cwd, file);
11499
+ const rel = path4.relative(root, abs);
11464
11500
  if (!rel || leaves(rel))
11465
11501
  return null;
11466
- return rel.split(path3.sep).join("/");
11502
+ return rel.split(path4.sep).join("/");
11467
11503
  }
11468
11504
  function patchPaths(patch) {
11469
11505
  const out = [];
@@ -11478,8 +11514,8 @@ function patchPaths(patch) {
11478
11514
 
11479
11515
  // server/src/worktree.ts
11480
11516
  import { execFileSync as execFileSync2 } from "node:child_process";
11481
- import fs3 from "node:fs";
11482
- import path4 from "node:path";
11517
+ import fs4 from "node:fs";
11518
+ import path5 from "node:path";
11483
11519
  var MAX_PATHS = 200;
11484
11520
  var git2 = (root, args) => {
11485
11521
  try {
@@ -11513,7 +11549,7 @@ function snapshot(root) {
11513
11549
  continue;
11514
11550
  let st;
11515
11551
  try {
11516
- st = fs3.lstatSync(path4.join(root, p), { throwIfNoEntry: false });
11552
+ st = fs4.lstatSync(path5.join(root, p), { throwIfNoEntry: false });
11517
11553
  } catch {
11518
11554
  st = undefined;
11519
11555
  }
@@ -11536,13 +11572,13 @@ function changed(root, before, after) {
11536
11572
  }
11537
11573
 
11538
11574
  // server/src/capture.ts
11539
- var spoolDir = () => path5.join(sphicaHome(), "spool");
11540
- var stateFile = () => path5.join(sphicaHome(), "capture.json");
11541
- var rejectedDir = () => path5.join(spoolDir(), "rejected");
11542
- var unregisteredDir = () => path5.join(spoolDir(), "unregistered");
11575
+ var spoolDir = () => path6.join(sphicaHome(), "spool");
11576
+ var stateFile = () => path6.join(sphicaHome(), "capture.json");
11577
+ var rejectedDir = () => path6.join(spoolDir(), "rejected");
11578
+ var unregisteredDir = () => path6.join(spoolDir(), "unregistered");
11543
11579
  var HOLD_DAYS = 30;
11544
11580
  var HOLD_MAX = 1000;
11545
- var baselineDir = () => path5.join(sphicaHome(), "worktree");
11581
+ var baselineDir = () => path6.join(sphicaHome(), "worktree");
11546
11582
  function current(raw) {
11547
11583
  const r = raw;
11548
11584
  if (r.v === 2)
@@ -11589,7 +11625,7 @@ ${z}`,
11589
11625
  function prune(held) {
11590
11626
  let files;
11591
11627
  try {
11592
- files = fs4.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
11628
+ files = fs5.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
11593
11629
  } catch {
11594
11630
  return;
11595
11631
  }
@@ -11597,21 +11633,21 @@ function prune(held) {
11597
11633
  const stale = files.filter((f) => Number(f.split("-")[0]) < cutoff);
11598
11634
  const over = files.slice(0, Math.max(0, files.length - HOLD_MAX));
11599
11635
  for (const f of new Set([...stale, ...over]))
11600
- fs4.rmSync(path5.join(held, f), { force: true });
11636
+ fs5.rmSync(path6.join(held, f), { force: true });
11601
11637
  }
11602
11638
  function spool(record) {
11603
11639
  const dir = spoolDir();
11604
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
11640
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
11605
11641
  const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}.json`;
11606
- const tmp = path5.join(dir, `.${name}`);
11607
- fs4.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
11608
- fs4.renameSync(tmp, path5.join(dir, name));
11642
+ const tmp = path6.join(dir, `.${name}`);
11643
+ fs5.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
11644
+ fs5.renameSync(tmp, path6.join(dir, name));
11609
11645
  }
11610
11646
  var branchOf = (root) => {
11611
11647
  try {
11612
- const dotgit = path5.join(root, ".git");
11613
- const gitdir = fs4.statSync(dotgit).isFile() ? path5.resolve(root, fs4.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
11614
- const h = fs4.readFileSync(path5.join(gitdir, "HEAD"), "utf8").trim();
11648
+ const dotgit = path6.join(root, ".git");
11649
+ const gitdir = fs5.statSync(dotgit).isFile() ? path6.resolve(root, fs5.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
11650
+ const h = fs5.readFileSync(path6.join(gitdir, "HEAD"), "utf8").trim();
11615
11651
  return h.startsWith("ref: refs/heads/") ? h.slice("ref: refs/heads/".length) : null;
11616
11652
  } catch {
11617
11653
  return null;
@@ -11655,7 +11691,7 @@ Notes: ${notes.trim()}` : "";
11655
11691
  };
11656
11692
  }
11657
11693
  function captureNotice(file = dbFile()) {
11658
- if (!fs4.existsSync(file))
11694
+ if (!fs5.existsSync(file))
11659
11695
  return panel("sphica: no database, so conversations are not recorded", [file], "Create it with sphica init");
11660
11696
  const s = readState();
11661
11697
  if (s.stuck)
@@ -11664,32 +11700,32 @@ function captureNotice(file = dbFile()) {
11664
11700
  return panel(`sphica: the database rejected ${plural(s.rejected, "record")}`, [rejectedDir(), `Move them back to ${spoolDir()} to resend`], "Fix them first, then check with sphica doctor");
11665
11701
  return null;
11666
11702
  }
11667
- var baselineFile = (host, session) => path5.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
11703
+ var baselineFile = (host, session) => path6.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
11668
11704
  function readBaseline(file) {
11669
11705
  try {
11670
- return JSON.parse(fs4.readFileSync(file, "utf8"));
11706
+ return JSON.parse(fs5.readFileSync(file, "utf8"));
11671
11707
  } catch {
11672
11708
  return null;
11673
11709
  }
11674
11710
  }
11675
11711
  function writeBaseline(file, b) {
11676
- fs4.mkdirSync(path5.dirname(file), { recursive: true, mode: 448 });
11712
+ fs5.mkdirSync(path6.dirname(file), { recursive: true, mode: 448 });
11677
11713
  const tmp = `${file}.${process.pid}.tmp`;
11678
- fs4.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
11679
- fs4.renameSync(tmp, file);
11714
+ fs5.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
11715
+ fs5.renameSync(tmp, file);
11680
11716
  }
11681
11717
  function pruneBaselines() {
11682
11718
  const cutoff = Date.now() - HOLD_DAYS * 24 * 60 * 60 * 1000;
11683
11719
  let files;
11684
11720
  try {
11685
- files = fs4.readdirSync(baselineDir());
11721
+ files = fs5.readdirSync(baselineDir());
11686
11722
  } catch {
11687
11723
  return;
11688
11724
  }
11689
11725
  for (const f of files) {
11690
- const file = path5.join(baselineDir(), f);
11691
- if ((fs4.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
11692
- fs4.rmSync(file, { force: true });
11726
+ const file = path6.join(baselineDir(), f);
11727
+ if ((fs5.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
11728
+ fs5.rmSync(file, { force: true });
11693
11729
  }
11694
11730
  }
11695
11731
  function onHook(host, input) {
@@ -11700,7 +11736,7 @@ function onHook(host, input) {
11700
11736
  return { flush: false };
11701
11737
  const file = process.env.CLAUDE_ENV_FILE;
11702
11738
  if (file && input.session_id && /^[A-Za-z0-9_-]+$/.test(input.session_id)) {
11703
- fs4.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
11739
+ fs5.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
11704
11740
  `);
11705
11741
  }
11706
11742
  const place2 = identify(input.cwd ?? process.cwd());
@@ -11786,13 +11822,13 @@ function onHook(host, input) {
11786
11822
  }
11787
11823
  function writeState(s) {
11788
11824
  try {
11789
- fs4.writeFileSync(stateFile(), JSON.stringify(s));
11825
+ fs5.writeFileSync(stateFile(), JSON.stringify(s));
11790
11826
  } catch {}
11791
11827
  }
11792
11828
  function readState() {
11793
11829
  const count = (dir) => {
11794
11830
  try {
11795
- return fs4.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
11831
+ return fs5.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
11796
11832
  } catch {
11797
11833
  return 0;
11798
11834
  }
@@ -11804,7 +11840,7 @@ function readState() {
11804
11840
  };
11805
11841
  let raw = {};
11806
11842
  try {
11807
- const parsed = JSON.parse(fs4.readFileSync(stateFile(), "utf8"));
11843
+ const parsed = JSON.parse(fs5.readFileSync(stateFile(), "utf8"));
11808
11844
  if (parsed && typeof parsed === "object")
11809
11845
  raw = parsed;
11810
11846
  } catch {}
@@ -11818,25 +11854,25 @@ function readState() {
11818
11854
  };
11819
11855
  }
11820
11856
  function lock() {
11821
- const file = path5.join(spoolDir(), ".lock");
11822
- fs4.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
11857
+ const file = path6.join(spoolDir(), ".lock");
11858
+ fs5.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
11823
11859
  for (let attempt = 0;attempt < 2; attempt++) {
11824
11860
  try {
11825
- fs4.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
11861
+ fs5.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
11826
11862
  return () => {
11827
11863
  try {
11828
- if (fs4.readFileSync(file, "utf8") === String(process.pid))
11829
- fs4.rmSync(file, { force: true });
11864
+ if (fs5.readFileSync(file, "utf8") === String(process.pid))
11865
+ fs5.rmSync(file, { force: true });
11830
11866
  } catch {}
11831
11867
  };
11832
11868
  } catch (e) {
11833
11869
  if (e.code !== "EEXIST")
11834
11870
  throw e;
11835
11871
  }
11836
- const st = fs4.statSync(file, { throwIfNoEntry: false });
11872
+ const st = fs5.statSync(file, { throwIfNoEntry: false });
11837
11873
  if (!st)
11838
11874
  continue;
11839
- const holder = Number(fs4.readFileSync(file, "utf8") || 0);
11875
+ const holder = Number(fs5.readFileSync(file, "utf8") || 0);
11840
11876
  const fresh = Date.now() - st.mtimeMs < 5 * 60000;
11841
11877
  const alive = (() => {
11842
11878
  if (holder <= 0)
@@ -11849,7 +11885,7 @@ function lock() {
11849
11885
  })();
11850
11886
  if (alive && fresh)
11851
11887
  return null;
11852
- fs4.rmSync(file, { force: true });
11888
+ fs5.rmSync(file, { force: true });
11853
11889
  }
11854
11890
  return null;
11855
11891
  }
@@ -11932,7 +11968,7 @@ var REJECTED = new Set([18, 19, 20, 25]);
11932
11968
  var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
11933
11969
  var queued = (from) => {
11934
11970
  try {
11935
- return fs4.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
11971
+ return fs5.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
11936
11972
  } catch {
11937
11973
  return [];
11938
11974
  }
@@ -11943,16 +11979,16 @@ async function sendBatch(db, names) {
11943
11979
  for (const { name, from } of names) {
11944
11980
  let r;
11945
11981
  try {
11946
- r = current(JSON.parse(fs4.readFileSync(path5.join(from, name), "utf8")));
11982
+ r = current(JSON.parse(fs5.readFileSync(path6.join(from, name), "utf8")));
11947
11983
  } catch {
11948
- fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11949
- fs4.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
11984
+ fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11985
+ fs5.renameSync(path6.join(from, name), path6.join(rejectedDir(), name));
11950
11986
  continue;
11951
11987
  }
11952
11988
  if (r)
11953
11989
  records.push({ name, from, r });
11954
11990
  else
11955
- fs4.rmSync(path5.join(from, name), { force: true });
11991
+ fs5.rmSync(path6.join(from, name), { force: true });
11956
11992
  }
11957
11993
  const projects = new Map((await db.selectFrom("project").select(["id", "key", "name"]).where("key", "in", [...new Set(records.map((x) => x.r.project))]).execute()).map((p) => [p.key, { id: p.id, name: p.name }]));
11958
11994
  const known = records.filter((x) => projects.has(x.r.project));
@@ -11975,10 +12011,10 @@ async function sendBatch(db, names) {
11975
12011
  }
11976
12012
  }
11977
12013
  if (bad.length) {
11978
- fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
12014
+ fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11979
12015
  for (const x of bad) {
11980
12016
  try {
11981
- fs4.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
12017
+ fs5.renameSync(path6.join(x.from, x.name), path6.join(rejectedDir(), x.name));
11982
12018
  } catch (e) {
11983
12019
  if (e.code !== "ENOENT")
11984
12020
  throw e;
@@ -11986,12 +12022,12 @@ async function sendBatch(db, names) {
11986
12022
  }
11987
12023
  }
11988
12024
  if (strayed.length) {
11989
- fs4.mkdirSync(held, { recursive: true, mode: 448 });
12025
+ fs5.mkdirSync(held, { recursive: true, mode: 448 });
11990
12026
  for (const x of strayed) {
11991
12027
  if (x.from === held)
11992
12028
  continue;
11993
12029
  try {
11994
- fs4.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
12030
+ fs5.renameSync(path6.join(x.from, x.name), path6.join(held, x.name));
11995
12031
  } catch (e) {
11996
12032
  if (e.code !== "ENOENT")
11997
12033
  throw e;
@@ -12001,7 +12037,7 @@ async function sendBatch(db, names) {
12001
12037
  const moved = new Set([...bad, ...strayed].map((x) => x.name));
12002
12038
  for (const x of records)
12003
12039
  if (!moved.has(x.name))
12004
- fs4.rmSync(path5.join(x.from, x.name), { force: true });
12040
+ fs5.rmSync(path6.join(x.from, x.name), { force: true });
12005
12041
  return { sent, rejected: bad.length };
12006
12042
  }
12007
12043
  async function flush(file = dbFile(), budgetMs = FLUSH_BUDGET_MS) {
@@ -12901,10 +12937,10 @@ function mergeDefs(...defs) {
12901
12937
  function cloneDef(schema) {
12902
12938
  return mergeDefs(schema._zod.def);
12903
12939
  }
12904
- function getElementAtPath(obj, path6) {
12905
- if (!path6)
12940
+ function getElementAtPath(obj, path7) {
12941
+ if (!path7)
12906
12942
  return obj;
12907
- return path6.reduce((acc, key) => acc?.[key], obj);
12943
+ return path7.reduce((acc, key) => acc?.[key], obj);
12908
12944
  }
12909
12945
  function promiseAllObject(promisesObj) {
12910
12946
  const keys = Object.keys(promisesObj);
@@ -13240,11 +13276,11 @@ function explicitlyAborted(x, startIndex = 0) {
13240
13276
  }
13241
13277
  return false;
13242
13278
  }
13243
- function prefixIssues(path6, issues) {
13279
+ function prefixIssues(path7, issues) {
13244
13280
  return issues.map((iss) => {
13245
13281
  var _a5;
13246
13282
  (_a5 = iss).path ?? (_a5.path = []);
13247
- iss.path.unshift(path6);
13283
+ iss.path.unshift(path7);
13248
13284
  return iss;
13249
13285
  });
13250
13286
  }
@@ -13694,16 +13730,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
13694
13730
  }
13695
13731
  function formatError(error, mapper = (issue2) => issue2.message) {
13696
13732
  const fieldErrors = { _errors: [] };
13697
- const processError = (error2, path6 = []) => {
13733
+ const processError = (error2, path7 = []) => {
13698
13734
  for (const issue2 of error2.issues) {
13699
13735
  if (issue2.code === "invalid_union" && issue2.errors.length) {
13700
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
13736
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
13701
13737
  } else if (issue2.code === "invalid_key") {
13702
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
13738
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
13703
13739
  } else if (issue2.code === "invalid_element") {
13704
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
13740
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
13705
13741
  } else {
13706
- const fullpath = [...path6, ...issue2.path];
13742
+ const fullpath = [...path7, ...issue2.path];
13707
13743
  if (fullpath.length === 0) {
13708
13744
  fieldErrors._errors.push(mapper(issue2));
13709
13745
  } else {
@@ -13742,17 +13778,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
13742
13778
  }
13743
13779
  function treeifyError(error, mapper = (issue2) => issue2.message) {
13744
13780
  const result = { errors: [] };
13745
- const processError = (error2, path6 = []) => {
13781
+ const processError = (error2, path7 = []) => {
13746
13782
  var _a6;
13747
13783
  for (const issue2 of error2.issues) {
13748
13784
  if (issue2.code === "invalid_union" && issue2.errors.length) {
13749
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
13785
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
13750
13786
  } else if (issue2.code === "invalid_key") {
13751
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
13787
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
13752
13788
  } else if (issue2.code === "invalid_element") {
13753
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
13789
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
13754
13790
  } else {
13755
- const fullpath = [...path6, ...issue2.path];
13791
+ const fullpath = [...path7, ...issue2.path];
13756
13792
  if (fullpath.length === 0) {
13757
13793
  result.errors.push(mapper(issue2));
13758
13794
  continue;
@@ -13791,8 +13827,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
13791
13827
  }
13792
13828
  function toDotPath(_path) {
13793
13829
  const segs = [];
13794
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13795
- for (const seg of path6) {
13830
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13831
+ for (const seg of path7) {
13796
13832
  if (typeof seg === "number")
13797
13833
  segs.push(`[${seg}]`);
13798
13834
  else if (typeof seg === "symbol")
@@ -30613,13 +30649,13 @@ function resolveRef(ref, ctx) {
30613
30649
  if (!ref.startsWith("#")) {
30614
30650
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
30615
30651
  }
30616
- const path6 = ref.slice(1).split("/").filter(Boolean);
30617
- if (path6.length === 0) {
30652
+ const path7 = ref.slice(1).split("/").filter(Boolean);
30653
+ if (path7.length === 0) {
30618
30654
  return ctx.rootSchema;
30619
30655
  }
30620
30656
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
30621
- if (path6[0] === defsKey) {
30622
- const key = path6[1] === undefined ? undefined : decodeJSONPointerSegment(path6[1]);
30657
+ if (path7[0] === defsKey) {
30658
+ const key = path7[1] === undefined ? undefined : decodeJSONPointerSegment(path7[1]);
30623
30659
  if (!key || !ctx.defs[key]) {
30624
30660
  throw new Error(`Reference not found: ${ref}`);
30625
30661
  }
@@ -31486,11 +31522,11 @@ function gitPath(token, prefix) {
31486
31522
  }
31487
31523
  function parseDiff(text) {
31488
31524
  const files = [];
31489
- const at = (path6) => {
31490
- const found = files.find((f2) => f2.path === path6);
31525
+ const at = (path7) => {
31526
+ const found = files.find((f2) => f2.path === path7);
31491
31527
  if (found)
31492
31528
  return found;
31493
- const f = { path: path6, added: [], lines: [] };
31529
+ const f = { path: path7, added: [], lines: [] };
31494
31530
  files.push(f);
31495
31531
  return f;
31496
31532
  };
@@ -31603,8 +31639,8 @@ var Findings = exports_external.array(Finding).max(MAX_FINDINGS);
31603
31639
 
31604
31640
  // server/src/review-bridge.ts
31605
31641
  import { execFileSync as execFileSync3 } from "node:child_process";
31606
- import fs5 from "node:fs";
31607
- import path6 from "node:path";
31642
+ import fs6 from "node:fs";
31643
+ import path7 from "node:path";
31608
31644
  var NAME = /^[\w.:/-]{1,100}$/;
31609
31645
  var MAX_DIFF = 4 * 1024 * 1024;
31610
31646
  var MAX_FILES = 500;
@@ -31684,10 +31720,10 @@ function localChange(root, args) {
31684
31720
  for (const rel of untracked) {
31685
31721
  let text = "";
31686
31722
  try {
31687
- const file2 = path6.join(root, rel);
31688
- const st = fs5.lstatSync(file2);
31723
+ const file2 = path7.join(root, rel);
31724
+ const st = fs6.lstatSync(file2);
31689
31725
  if (st.isFile() && st.size <= MAX_UNTRACKED_BYTES)
31690
- text = fs5.readFileSync(file2, "utf8");
31726
+ text = fs6.readFileSync(file2, "utf8");
31691
31727
  } catch {}
31692
31728
  const added = text ? text.split(/\r?\n/) : [];
31693
31729
  files.push({ path: rel, added, lines: added.map((_, i) => i + 1) });
@@ -31854,15 +31890,15 @@ async function namedInCommand(db, projectId2, root, cwd, command) {
31854
31890
  const edge = `\\s'"=(){}<>|;&,`;
31855
31891
  const esc2 = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31856
31892
  const forms = (p) => {
31857
- const abs = path7.join(root, p);
31858
- const fromCwd = path7.relative(cwd, abs);
31893
+ const abs = path8.join(root, p);
31894
+ const fromCwd = path8.relative(cwd, abs);
31859
31895
  const out = new Set;
31860
31896
  for (const f of [p, abs, leaves(fromCwd) ? "" : fromCwd])
31861
31897
  if (f)
31862
31898
  for (const sep of ["/", "\\"]) {
31863
31899
  const t = f.split(/[\\/]/).join(sep);
31864
31900
  out.add(t);
31865
- if (!path7.isAbsolute(f))
31901
+ if (!path8.isAbsolute(f))
31866
31902
  out.add(`.${sep}${t}`);
31867
31903
  }
31868
31904
  return [...out];
@@ -31916,7 +31952,7 @@ async function waiting(db, projectId2, place) {
31916
31952
  if (!n)
31917
31953
  return "";
31918
31954
  const day = new Date().toLocaleDateString("sv-SE");
31919
- if (!markOnce("pending", `${path7.resolve(place.file)}\x00${place.key}\x00${day}`))
31955
+ if (!markOnce("pending", `${path8.resolve(place.file)}\x00${place.key}\x00${day}`))
31920
31956
  return "";
31921
31957
  const trace = place.host === "codex" ? "$sphica:trace" : "/sphica:trace";
31922
31958
  return `- ${n} session${n === 1 ? "" : "s"} waiting to be traced: run ${trace} pending.`;
@@ -31999,10 +32035,10 @@ function markOnce(kind, key) {
31999
32035
  return String(process.getuid?.() ?? "user");
32000
32036
  }
32001
32037
  })();
32002
- const dir = path7.join(os2.tmpdir(), `sphica-${sha256(user).toString("hex").slice(0, 12)}`, kind);
32038
+ const dir = path8.join(os2.tmpdir(), `sphica-${sha256(user).toString("hex").slice(0, 12)}`, kind);
32003
32039
  try {
32004
- fs6.mkdirSync(dir, { recursive: true, mode: 448 });
32005
- fs6.writeFileSync(path7.join(dir, sha256(key).toString("hex").slice(0, 24)), "", { flag: "wx" });
32040
+ fs7.mkdirSync(dir, { recursive: true, mode: 448 });
32041
+ fs7.writeFileSync(path8.join(dir, sha256(key).toString("hex").slice(0, 24)), "", { flag: "wx" });
32006
32042
  return true;
32007
32043
  } catch (e) {
32008
32044
  return e.code !== "EEXIST";
@@ -32039,7 +32075,7 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
32039
32075
  const event = call ? "review" : name === "SessionStart" ? "session_start" : name === "UserPromptSubmit" ? "prompt" : name === "PreToolUse" ? input2.tool_name === "Read" || input2.tool_name === "Bash" && !(host === "codex" && shellPatch(input2)) ? "pre_read" : "pre_edit" : null;
32040
32076
  if (!event || !input2.session_id)
32041
32077
  return "";
32042
- if (event === "prompt" && !isOwnerTurn(input2))
32078
+ if (event === "prompt" && !isOwnerTurn(input2, undefined, undefined, host === "codex" ? process.env.CODEX_THREAD_ID : undefined))
32043
32079
  return "";
32044
32080
  if (event === "review" && input2.agent_id)
32045
32081
  return "";
@@ -32053,12 +32089,12 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
32053
32089
  const place = identify(input2.cwd ?? process.cwd());
32054
32090
  if (!place)
32055
32091
  return "";
32056
- let rels = targets.map((t) => path7.relative(place.root, path7.resolve(input2.cwd ?? place.root, t))).filter((r) => r && !leaves(r)).map((r) => r.split(path7.sep).join("/"));
32092
+ let rels = targets.map((t) => path8.relative(place.root, path8.resolve(input2.cwd ?? place.root, t))).filter((r) => r && !leaves(r)).map((r) => r.split(path8.sep).join("/"));
32057
32093
  if (onPath && !shell && !rels.length)
32058
32094
  return "";
32059
32095
  let db = null;
32060
32096
  try {
32061
- if (!fs6.existsSync(file2))
32097
+ if (!fs7.existsSync(file2))
32062
32098
  throw new Error(`no database at ${file2}`);
32063
32099
  db = openReader(file2);
32064
32100
  const pid = await projectId(db, place.key);
@@ -32085,6 +32121,8 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
32085
32121
  await log(file2, pid, host, input2.session_id, event, plan, plan.text ? "emitted" : "nothing", branchOf(place.root)).catch(() => {});
32086
32122
  return plan.text;
32087
32123
  } catch (e) {
32124
+ if (e instanceof RevisionMismatch)
32125
+ return shell || !markOnce("revision", `${host}\x00${input2.session_id}`) ? "" : `Sphica unavailable: ${head(inline(e.message), 400)}`;
32088
32126
  if (event === "prompt" || shell || !onceUnavailable(`${host}\x00${input2.session_id}`))
32089
32127
  return "";
32090
32128
  return `Sphica unavailable: ${head(inline(reason(e)), 200)}. Past decisions for ${onPath ? named(rels) : "this project"} could not be checked.`;