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.
@@ -45522,109 +45522,18 @@ function adapt(raw) {
45522
45522
  }
45523
45523
 
45524
45524
  // server/src/sqlite.ts
45525
- import fs from "node:fs";
45525
+ import fs2 from "node:fs";
45526
45526
  import os from "node:os";
45527
- import path from "node:path";
45527
+ import path2 from "node:path";
45528
45528
  import { constants as C, DatabaseSync } from "node:sqlite";
45529
- var SCHEMA_GENERATION = 2;
45530
- var SCHEMA_REVISION = 4;
45531
- var sphicaHome = () => process.env.SPHICA_HOME || path.join(os.homedir(), ".sphica");
45532
- var dbFile = () => process.env.SPHICA_DB || path.join(sphicaHome(), "sphica.db");
45533
- function requireRuntime() {
45534
- const proto = DatabaseSync.prototype;
45535
- if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
45536
- throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
45537
- }
45538
- function requireFile(file2) {
45539
- if (!fs.existsSync(file2))
45540
- throw new Error(`No database at ${file2}. Create it with \`sphica init\`.`);
45541
- }
45542
- function prepare(raw, check2) {
45543
- raw.enableDefensive(true);
45544
- raw.exec("pragma foreign_keys = on");
45545
- raw.exec("pragma busy_timeout = 5000");
45546
- if (check2 === "none")
45547
- return;
45548
- checkGeneration(raw);
45549
- if (check2 === "generation")
45550
- return;
45551
- const got = raw.prepare("pragma user_version").get()?.user_version;
45552
- if (got === SCHEMA_REVISION)
45553
- return;
45554
- 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."));
45555
- }
45556
- function generationOf(raw) {
45557
- const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
45558
- if (!has) {
45559
- const any3 = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'project'").get();
45560
- return any3 ? 1 : null;
45561
- }
45562
- return raw.prepare("select generation from sphica_generation").get()?.generation ?? null;
45563
- }
45564
- function checkGeneration(raw) {
45565
- const got = generationOf(raw);
45566
- if (got === SCHEMA_GENERATION)
45567
- return;
45568
- if (got === null)
45569
- throw new Error("The database has no sphica schema. Create it with `sphica init`.");
45570
- throw new Error(got < SCHEMA_GENERATION ? "The database was made by Sphica 0.4 or earlier, and this Sphica cannot read it. Move it aside (it is left unchanged), then run `sphica init`." : "The database was made by a newer Sphica. Update sphica.");
45571
- }
45572
- var READER_FUNCTIONS = new Set([
45573
- "bm25",
45574
- "coalesce",
45575
- "count",
45576
- "instr",
45577
- "json_array_length",
45578
- "json_extract",
45579
- "json_group_array",
45580
- "json_object",
45581
- "length",
45582
- "lower",
45583
- "match",
45584
- "max",
45585
- "min",
45586
- "substr"
45587
- ]);
45588
- var SHADOW = /^(unit|source)_fts_(data|idx|docsize|config)$/;
45589
45529
 
45590
- // server/src/db.ts
45591
- var JSON_COLUMNS = new Set(["next"]);
45592
- var TOP_LEVEL = /^\$\[\d+\]\."([^"]+)"$/;
45593
- var parseJson = new ParseJSONResultsPlugin({
45594
- shouldParse: (_value, jsonPath) => JSON_COLUMNS.has(jsonPath.match(TOP_LEVEL)?.[1] ?? "")
45595
- });
45596
- function kyselyOn(connect) {
45597
- return new Kysely({
45598
- dialect: new SqliteDialect({ database: async () => adapt(connect()) }),
45599
- plugins: [parseJson]
45600
- });
45601
- }
45602
- async function inTransaction(db, fn) {
45603
- return db.connection().execute(async (c) => {
45604
- await sql`begin immediate`.execute(c);
45605
- try {
45606
- const out = await fn(c);
45607
- await sql`commit`.execute(c);
45608
- return out;
45609
- } catch (e) {
45610
- await sql`rollback`.execute(c).catch(() => {});
45611
- throw e;
45612
- }
45613
- });
45614
- }
45615
- var iso = (d) => {
45616
- const t = new Date(d);
45617
- if (Number.isNaN(t.getTime()))
45618
- throw new RangeError(`Not a readable time: ${String(d)}`);
45619
- return t.toISOString();
45620
- };
45621
- function sqliteCode(e) {
45622
- const x = e;
45623
- return x?.code === "ERR_SQLITE_ERROR" && typeof x.errcode === "number" ? x.errcode & 255 : null;
45624
- }
45530
+ // server/src/plugin.ts
45531
+ import fs from "node:fs";
45532
+ import path from "node:path";
45533
+ import { fileURLToPath } from "node:url";
45625
45534
 
45626
- // server/src/db-write.ts
45627
- import { constants as C2, DatabaseSync as DatabaseSync2 } from "node:sqlite";
45535
+ // server/src/panel.ts
45536
+ import { stripVTControlCharacters, styleText } from "node:util";
45628
45537
 
45629
45538
  // server/src/text.ts
45630
45539
  import crypto from "node:crypto";
@@ -45922,7 +45831,148 @@ function explain(e, depth) {
45922
45831
  return own2 && inner ? `${own2} (${inner})` : own2 || inner;
45923
45832
  }
45924
45833
 
45834
+ // server/src/panel.ts
45835
+ var title = (text) => `✦ ${text}`;
45836
+ var rule = (text) => text.split(`
45837
+ `).map((line) => line ? `│ ${line}` : "│").join(`
45838
+ `);
45839
+ var foot = (text) => `╰─ ${text}`;
45840
+ var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
45841
+ `);
45842
+ var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
45843
+ var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
45844
+ `).replace(/(?![\t\n])\p{Cc}/gu, ""));
45845
+ var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
45846
+
45847
+ // server/src/plugin.ts
45848
+ var MANIFEST = path.join(".claude-plugin", "plugin.json");
45849
+ var PACKAGE = "package.json";
45850
+ function versionAt(root) {
45851
+ try {
45852
+ const m = JSON.parse(fs.readFileSync(path.join(root, MANIFEST), "utf8"));
45853
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
45854
+ } catch {
45855
+ return null;
45856
+ }
45857
+ }
45858
+ function packageVersionAt(root) {
45859
+ try {
45860
+ const m = JSON.parse(fs.readFileSync(path.join(root, PACKAGE), "utf8"));
45861
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
45862
+ } catch {
45863
+ return null;
45864
+ }
45865
+ }
45866
+ var here = path.dirname(fileURLToPath(import.meta.url));
45867
+ var ROOT = [path.join(here, ".."), path.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path.join(here, "..");
45868
+ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
45869
+
45870
+ // server/src/sqlite.ts
45871
+ var SCHEMA_GENERATION = 2;
45872
+ var SCHEMA_REVISION = 4;
45873
+ var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
45874
+ var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
45875
+ function requireRuntime() {
45876
+ const proto = DatabaseSync.prototype;
45877
+ if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
45878
+ throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
45879
+ }
45880
+ function requireFile(file2) {
45881
+ if (!fs2.existsSync(file2))
45882
+ throw new Error(`No database at ${file2}. Create it with \`sphica init\`.`);
45883
+ }
45884
+ function prepare(raw, check2) {
45885
+ raw.enableDefensive(true);
45886
+ raw.exec("pragma foreign_keys = on");
45887
+ raw.exec("pragma busy_timeout = 5000");
45888
+ if (check2 === "none")
45889
+ return;
45890
+ checkGeneration(raw);
45891
+ if (check2 === "generation")
45892
+ return;
45893
+ const got = raw.prepare("pragma user_version").get()?.user_version;
45894
+ if (got === SCHEMA_REVISION)
45895
+ return;
45896
+ throw new RevisionMismatch(got ?? 0);
45897
+ }
45898
+
45899
+ class RevisionMismatch extends Error {
45900
+ constructor(got) {
45901
+ 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."));
45902
+ }
45903
+ }
45904
+ function generationOf(raw) {
45905
+ const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
45906
+ if (!has) {
45907
+ const any3 = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'project'").get();
45908
+ return any3 ? 1 : null;
45909
+ }
45910
+ return raw.prepare("select generation from sphica_generation").get()?.generation ?? null;
45911
+ }
45912
+ function checkGeneration(raw) {
45913
+ const got = generationOf(raw);
45914
+ if (got === SCHEMA_GENERATION)
45915
+ return;
45916
+ if (got === null)
45917
+ throw new Error("The database has no sphica schema. Create it with `sphica init`.");
45918
+ throw new Error(got < SCHEMA_GENERATION ? "The database was made by Sphica 0.4 or earlier, and this Sphica cannot read it. Move it aside (it is left unchanged), then run `sphica init`." : "The database was made by a newer Sphica. Update sphica.");
45919
+ }
45920
+ var READER_FUNCTIONS = new Set([
45921
+ "bm25",
45922
+ "coalesce",
45923
+ "count",
45924
+ "instr",
45925
+ "json_array_length",
45926
+ "json_extract",
45927
+ "json_group_array",
45928
+ "json_object",
45929
+ "length",
45930
+ "lower",
45931
+ "match",
45932
+ "max",
45933
+ "min",
45934
+ "substr"
45935
+ ]);
45936
+ var SHADOW = /^(unit|source)_fts_(data|idx|docsize|config)$/;
45937
+
45938
+ // server/src/db.ts
45939
+ var JSON_COLUMNS = new Set(["next"]);
45940
+ var TOP_LEVEL = /^\$\[\d+\]\."([^"]+)"$/;
45941
+ var parseJson = new ParseJSONResultsPlugin({
45942
+ shouldParse: (_value, jsonPath) => JSON_COLUMNS.has(jsonPath.match(TOP_LEVEL)?.[1] ?? "")
45943
+ });
45944
+ function kyselyOn(connect) {
45945
+ return new Kysely({
45946
+ dialect: new SqliteDialect({ database: async () => adapt(connect()) }),
45947
+ plugins: [parseJson]
45948
+ });
45949
+ }
45950
+ async function inTransaction(db, fn) {
45951
+ return db.connection().execute(async (c) => {
45952
+ await sql`begin immediate`.execute(c);
45953
+ try {
45954
+ const out = await fn(c);
45955
+ await sql`commit`.execute(c);
45956
+ return out;
45957
+ } catch (e) {
45958
+ await sql`rollback`.execute(c).catch(() => {});
45959
+ throw e;
45960
+ }
45961
+ });
45962
+ }
45963
+ var iso = (d) => {
45964
+ const t = new Date(d);
45965
+ if (Number.isNaN(t.getTime()))
45966
+ throw new RangeError(`Not a readable time: ${String(d)}`);
45967
+ return t.toISOString();
45968
+ };
45969
+ function sqliteCode(e) {
45970
+ const x = e;
45971
+ return x?.code === "ERR_SQLITE_ERROR" && typeof x.errcode === "number" ? x.errcode & 255 : null;
45972
+ }
45973
+
45925
45974
  // server/src/db-write.ts
45975
+ import { constants as C2, DatabaseSync as DatabaseSync2 } from "node:sqlite";
45926
45976
  var ddl = null;
45927
45977
  var DDL = () => ddl ??= new Set([
45928
45978
  "SQLITE_ALTER_TABLE",
@@ -46062,7 +46112,7 @@ function connectWriter(role, file2 = dbFile(), create = false) {
46062
46112
  raw.setAuthorizer((action, p1, p2, _db, triggerOrView) => captureAuthorizer(own2, action, p1, p2, triggerOrView));
46063
46113
  const prepare2 = raw.prepare.bind(raw);
46064
46114
  const exec = raw.exec.bind(raw);
46065
- const mark = (f) => (...a) => {
46115
+ const mark2 = (f) => (...a) => {
46066
46116
  own2 = true;
46067
46117
  try {
46068
46118
  return f(...a);
@@ -46070,8 +46120,8 @@ function connectWriter(role, file2 = dbFile(), create = false) {
46070
46120
  own2 = false;
46071
46121
  }
46072
46122
  };
46073
- raw.prepare = mark(prepare2);
46074
- raw.exec = mark(exec);
46123
+ raw.prepare = mark2(prepare2);
46124
+ raw.exec = mark2(exec);
46075
46125
  }
46076
46126
  return raw;
46077
46127
  }
@@ -46084,8 +46134,8 @@ import crypto3 from "node:crypto";
46084
46134
 
46085
46135
  // server/src/capture.ts
46086
46136
  import { spawn } from "node:child_process";
46087
- import fs5 from "node:fs";
46088
- import path5 from "node:path";
46137
+ import fs6 from "node:fs";
46138
+ import path6 from "node:path";
46089
46139
 
46090
46140
  // server/src/knowledge.ts
46091
46141
  var UNIT_KINDS = [
@@ -46111,47 +46161,33 @@ var FIELD_TYPES = ["text", "enum", "integer", "date"];
46111
46161
  var HOSTS = ["claude-code", "codex"];
46112
46162
  var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), host, externalId);
46113
46163
 
46114
- // server/src/panel.ts
46115
- import { stripVTControlCharacters, styleText } from "node:util";
46116
- var title = (text) => `✦ ${text}`;
46117
- var rule = (text) => text.split(`
46118
- `).map((line) => line ? `│ ${line}` : "│").join(`
46119
- `);
46120
- var foot = (text) => `╰─ ${text}`;
46121
- var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
46122
- `);
46123
- var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
46124
- var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
46125
- `).replace(/(?![\t\n])\p{Cc}/gu, ""));
46126
- var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
46127
-
46128
46164
  // server/src/project.ts
46129
46165
  import { execFileSync } from "node:child_process";
46130
- import fs3 from "node:fs";
46131
- import path3 from "node:path";
46132
- import { fileURLToPath } from "node:url";
46166
+ import fs4 from "node:fs";
46167
+ import path4 from "node:path";
46168
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
46133
46169
 
46134
46170
  // server/src/anchors.ts
46135
46171
  import crypto2 from "node:crypto";
46136
- import fs2 from "node:fs";
46137
- import path2 from "node:path";
46172
+ import fs3 from "node:fs";
46173
+ import path3 from "node:path";
46138
46174
  var MAX_BYTES = 2 * 1024 * 1024;
46139
- var leaves = (rel) => rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
46175
+ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel);
46140
46176
  var literal3 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46141
46177
  function readBytes(root, rel) {
46142
- const abs = path2.join(root, rel);
46143
- if (leaves(path2.relative(root, abs)))
46178
+ const abs = path3.join(root, rel);
46179
+ if (leaves(path3.relative(root, abs)))
46144
46180
  return;
46145
- const st = fs2.lstatSync(abs, { throwIfNoEntry: false });
46181
+ const st = fs3.lstatSync(abs, { throwIfNoEntry: false });
46146
46182
  if (!st)
46147
46183
  return null;
46148
46184
  if (!st.isFile() || st.size > MAX_BYTES)
46149
46185
  return;
46150
46186
  try {
46151
- const inside = path2.relative(fs2.realpathSync(root), fs2.realpathSync(abs));
46187
+ const inside = path3.relative(fs3.realpathSync(root), fs3.realpathSync(abs));
46152
46188
  if (leaves(inside))
46153
46189
  return;
46154
- const buf = fs2.readFileSync(abs);
46190
+ const buf = fs3.readFileSync(abs);
46155
46191
  return buf.includes(0) ? undefined : buf;
46156
46192
  } catch {
46157
46193
  return;
@@ -46220,7 +46256,7 @@ ${l}`).join("");
46220
46256
  }
46221
46257
 
46222
46258
  // server/src/project.ts
46223
- var localFile = () => path3.join(sphicaHome(), "projects.json");
46259
+ var localFile = () => path4.join(sphicaHome(), "projects.json");
46224
46260
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
46225
46261
  function normalizeRemote(url2) {
46226
46262
  const raw = String(url2 ?? "").trim();
@@ -46253,7 +46289,7 @@ var git = (dir, ...args) => {
46253
46289
  function localMap() {
46254
46290
  let raw;
46255
46291
  try {
46256
- raw = fs3.readFileSync(localFile(), "utf8");
46292
+ raw = fs4.readFileSync(localFile(), "utf8");
46257
46293
  } catch (e) {
46258
46294
  if (e.code === "ENOENT")
46259
46295
  return {};
@@ -46265,23 +46301,23 @@ function localMap() {
46265
46301
  } catch {
46266
46302
  m = null;
46267
46303
  }
46268
- if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path3.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
46304
+ if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path4.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
46269
46305
  throw new Error(`${localFile()} is not a valid JSON project table. Fix or delete it, then name the project again.`);
46270
46306
  return m;
46271
46307
  }
46272
46308
  function identify(dir) {
46273
- const given = path3.resolve(dir);
46309
+ const given = path4.resolve(dir);
46274
46310
  const top = git(given, "rev-parse", "--show-toplevel");
46275
46311
  const root = top || given;
46276
46312
  const remote = top ? normalizeRemote(git(root, "remote", "get-url", "origin")) : null;
46277
46313
  if (remote)
46278
46314
  return { key: `git:${remote}`, root, name: remote.split("/").slice(1).join("/") || remote };
46279
46315
  const map3 = localMap();
46280
- for (let d = root;; d = path3.dirname(d)) {
46316
+ for (let d = root;; d = path4.dirname(d)) {
46281
46317
  const local = map3[d];
46282
46318
  if (local && LOCAL_KEY.test(local))
46283
46319
  return { key: `local:${local}`, root: d, name: local };
46284
- if (top || path3.dirname(d) === d)
46320
+ if (top || path4.dirname(d) === d)
46285
46321
  return null;
46286
46322
  }
46287
46323
  }
@@ -46291,30 +46327,30 @@ function hostWorkspace(meta3) {
46291
46327
  if (typeof cwd !== "string" || !cwd.startsWith("file:"))
46292
46328
  return null;
46293
46329
  try {
46294
- return fileURLToPath(cwd);
46330
+ return fileURLToPath2(cwd);
46295
46331
  } catch {
46296
46332
  return null;
46297
46333
  }
46298
46334
  }
46299
46335
  function writePlace(workspace, cwd) {
46300
- const here = identify(workspace);
46301
- if (!here)
46336
+ const here2 = identify(workspace);
46337
+ if (!here2)
46302
46338
  return null;
46303
46339
  const asked = cwd ? identify(cwd) : null;
46304
- if (asked && asked.key !== here.key)
46305
- throw new Error(`${asked.name} is not the workspace this session writes to (${here.name})`);
46306
- return here;
46340
+ if (asked && asked.key !== here2.key)
46341
+ throw new Error(`${asked.name} is not the workspace this session writes to (${here2.name})`);
46342
+ return here2;
46307
46343
  }
46308
46344
  async function projectId(db, key) {
46309
46345
  const r = await db.selectFrom("project").select("id").where("key", "=", key).executeTakeFirst();
46310
46346
  return r?.id ?? null;
46311
46347
  }
46312
46348
  function relativeTo(root, file2, cwd = root) {
46313
- const abs = path3.resolve(cwd, file2);
46314
- const rel = path3.relative(root, abs);
46349
+ const abs = path4.resolve(cwd, file2);
46350
+ const rel = path4.relative(root, abs);
46315
46351
  if (!rel || leaves(rel))
46316
46352
  return null;
46317
- return rel.split(path3.sep).join("/");
46353
+ return rel.split(path4.sep).join("/");
46318
46354
  }
46319
46355
  function patchPaths(patch) {
46320
46356
  const out = [];
@@ -46329,8 +46365,8 @@ function patchPaths(patch) {
46329
46365
 
46330
46366
  // server/src/worktree.ts
46331
46367
  import { execFileSync as execFileSync2 } from "node:child_process";
46332
- import fs4 from "node:fs";
46333
- import path4 from "node:path";
46368
+ import fs5 from "node:fs";
46369
+ import path5 from "node:path";
46334
46370
  var MAX_PATHS = 200;
46335
46371
  var git2 = (root, args) => {
46336
46372
  try {
@@ -46364,7 +46400,7 @@ function snapshot(root) {
46364
46400
  continue;
46365
46401
  let st;
46366
46402
  try {
46367
- st = fs4.lstatSync(path4.join(root, p), { throwIfNoEntry: false });
46403
+ st = fs5.lstatSync(path5.join(root, p), { throwIfNoEntry: false });
46368
46404
  } catch {
46369
46405
  st = undefined;
46370
46406
  }
@@ -46387,13 +46423,13 @@ function changed(root, before, after) {
46387
46423
  }
46388
46424
 
46389
46425
  // server/src/capture.ts
46390
- var spoolDir = () => path5.join(sphicaHome(), "spool");
46391
- var stateFile = () => path5.join(sphicaHome(), "capture.json");
46392
- var rejectedDir = () => path5.join(spoolDir(), "rejected");
46393
- var unregisteredDir = () => path5.join(spoolDir(), "unregistered");
46426
+ var spoolDir = () => path6.join(sphicaHome(), "spool");
46427
+ var stateFile = () => path6.join(sphicaHome(), "capture.json");
46428
+ var rejectedDir = () => path6.join(spoolDir(), "rejected");
46429
+ var unregisteredDir = () => path6.join(spoolDir(), "unregistered");
46394
46430
  var HOLD_DAYS = 30;
46395
46431
  var HOLD_MAX = 1000;
46396
- var baselineDir = () => path5.join(sphicaHome(), "worktree");
46432
+ var baselineDir = () => path6.join(sphicaHome(), "worktree");
46397
46433
  function current(raw) {
46398
46434
  const r = raw;
46399
46435
  if (r.v === 2)
@@ -46440,7 +46476,7 @@ ${z2}`,
46440
46476
  function prune(held) {
46441
46477
  let files;
46442
46478
  try {
46443
- files = fs5.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
46479
+ files = fs6.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
46444
46480
  } catch {
46445
46481
  return;
46446
46482
  }
@@ -46448,21 +46484,21 @@ function prune(held) {
46448
46484
  const stale = files.filter((f) => Number(f.split("-")[0]) < cutoff);
46449
46485
  const over = files.slice(0, Math.max(0, files.length - HOLD_MAX));
46450
46486
  for (const f of new Set([...stale, ...over]))
46451
- fs5.rmSync(path5.join(held, f), { force: true });
46487
+ fs6.rmSync(path6.join(held, f), { force: true });
46452
46488
  }
46453
46489
  function spool(record3) {
46454
46490
  const dir = spoolDir();
46455
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
46491
+ fs6.mkdirSync(dir, { recursive: true, mode: 448 });
46456
46492
  const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}.json`;
46457
- const tmp = path5.join(dir, `.${name}`);
46458
- fs5.writeFileSync(tmp, JSON.stringify(record3), { mode: 384 });
46459
- fs5.renameSync(tmp, path5.join(dir, name));
46493
+ const tmp = path6.join(dir, `.${name}`);
46494
+ fs6.writeFileSync(tmp, JSON.stringify(record3), { mode: 384 });
46495
+ fs6.renameSync(tmp, path6.join(dir, name));
46460
46496
  }
46461
46497
  var branchOf = (root) => {
46462
46498
  try {
46463
- const dotgit = path5.join(root, ".git");
46464
- const gitdir = fs5.statSync(dotgit).isFile() ? path5.resolve(root, fs5.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
46465
- const h = fs5.readFileSync(path5.join(gitdir, "HEAD"), "utf8").trim();
46499
+ const dotgit = path6.join(root, ".git");
46500
+ const gitdir = fs6.statSync(dotgit).isFile() ? path6.resolve(root, fs6.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
46501
+ const h = fs6.readFileSync(path6.join(gitdir, "HEAD"), "utf8").trim();
46466
46502
  return h.startsWith("ref: refs/heads/") ? h.slice("ref: refs/heads/".length) : null;
46467
46503
  } catch {
46468
46504
  return null;
@@ -46506,7 +46542,7 @@ Notes: ${notes.trim()}` : "";
46506
46542
  };
46507
46543
  }
46508
46544
  function captureNotice(file2 = dbFile()) {
46509
- if (!fs5.existsSync(file2))
46545
+ if (!fs6.existsSync(file2))
46510
46546
  return panel("sphica: no database, so conversations are not recorded", [file2], "Create it with sphica init");
46511
46547
  const s = readState();
46512
46548
  if (s.stuck)
@@ -46515,32 +46551,32 @@ function captureNotice(file2 = dbFile()) {
46515
46551
  return panel(`sphica: the database rejected ${plural2(s.rejected, "record")}`, [rejectedDir(), `Move them back to ${spoolDir()} to resend`], "Fix them first, then check with sphica doctor");
46516
46552
  return null;
46517
46553
  }
46518
- var baselineFile = (host, session) => path5.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
46554
+ var baselineFile = (host, session) => path6.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
46519
46555
  function readBaseline(file2) {
46520
46556
  try {
46521
- return JSON.parse(fs5.readFileSync(file2, "utf8"));
46557
+ return JSON.parse(fs6.readFileSync(file2, "utf8"));
46522
46558
  } catch {
46523
46559
  return null;
46524
46560
  }
46525
46561
  }
46526
46562
  function writeBaseline(file2, b) {
46527
- fs5.mkdirSync(path5.dirname(file2), { recursive: true, mode: 448 });
46563
+ fs6.mkdirSync(path6.dirname(file2), { recursive: true, mode: 448 });
46528
46564
  const tmp = `${file2}.${process.pid}.tmp`;
46529
- fs5.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
46530
- fs5.renameSync(tmp, file2);
46565
+ fs6.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
46566
+ fs6.renameSync(tmp, file2);
46531
46567
  }
46532
46568
  function pruneBaselines() {
46533
46569
  const cutoff = Date.now() - HOLD_DAYS * 24 * 60 * 60 * 1000;
46534
46570
  let files;
46535
46571
  try {
46536
- files = fs5.readdirSync(baselineDir());
46572
+ files = fs6.readdirSync(baselineDir());
46537
46573
  } catch {
46538
46574
  return;
46539
46575
  }
46540
46576
  for (const f of files) {
46541
- const file2 = path5.join(baselineDir(), f);
46542
- if ((fs5.statSync(file2, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
46543
- fs5.rmSync(file2, { force: true });
46577
+ const file2 = path6.join(baselineDir(), f);
46578
+ if ((fs6.statSync(file2, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
46579
+ fs6.rmSync(file2, { force: true });
46544
46580
  }
46545
46581
  }
46546
46582
  function onHook(host, input2) {
@@ -46551,7 +46587,7 @@ function onHook(host, input2) {
46551
46587
  return { flush: false };
46552
46588
  const file2 = process.env.CLAUDE_ENV_FILE;
46553
46589
  if (file2 && input2.session_id && /^[A-Za-z0-9_-]+$/.test(input2.session_id)) {
46554
- fs5.appendFileSync(file2, `export SPHICA_PARENT_SESSION=${input2.session_id}
46590
+ fs6.appendFileSync(file2, `export SPHICA_PARENT_SESSION=${input2.session_id}
46555
46591
  `);
46556
46592
  }
46557
46593
  const place2 = identify(input2.cwd ?? process.cwd());
@@ -46637,13 +46673,13 @@ function onHook(host, input2) {
46637
46673
  }
46638
46674
  function writeState(s) {
46639
46675
  try {
46640
- fs5.writeFileSync(stateFile(), JSON.stringify(s));
46676
+ fs6.writeFileSync(stateFile(), JSON.stringify(s));
46641
46677
  } catch {}
46642
46678
  }
46643
46679
  function readState() {
46644
46680
  const count = (dir) => {
46645
46681
  try {
46646
- return fs5.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
46682
+ return fs6.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
46647
46683
  } catch {
46648
46684
  return 0;
46649
46685
  }
@@ -46655,7 +46691,7 @@ function readState() {
46655
46691
  };
46656
46692
  let raw = {};
46657
46693
  try {
46658
- const parsed = JSON.parse(fs5.readFileSync(stateFile(), "utf8"));
46694
+ const parsed = JSON.parse(fs6.readFileSync(stateFile(), "utf8"));
46659
46695
  if (parsed && typeof parsed === "object")
46660
46696
  raw = parsed;
46661
46697
  } catch {}
@@ -46669,25 +46705,25 @@ function readState() {
46669
46705
  };
46670
46706
  }
46671
46707
  function lock() {
46672
- const file2 = path5.join(spoolDir(), ".lock");
46673
- fs5.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
46708
+ const file2 = path6.join(spoolDir(), ".lock");
46709
+ fs6.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
46674
46710
  for (let attempt = 0;attempt < 2; attempt++) {
46675
46711
  try {
46676
- fs5.writeFileSync(file2, String(process.pid), { flag: "wx", mode: 384 });
46712
+ fs6.writeFileSync(file2, String(process.pid), { flag: "wx", mode: 384 });
46677
46713
  return () => {
46678
46714
  try {
46679
- if (fs5.readFileSync(file2, "utf8") === String(process.pid))
46680
- fs5.rmSync(file2, { force: true });
46715
+ if (fs6.readFileSync(file2, "utf8") === String(process.pid))
46716
+ fs6.rmSync(file2, { force: true });
46681
46717
  } catch {}
46682
46718
  };
46683
46719
  } catch (e) {
46684
46720
  if (e.code !== "EEXIST")
46685
46721
  throw e;
46686
46722
  }
46687
- const st = fs5.statSync(file2, { throwIfNoEntry: false });
46723
+ const st = fs6.statSync(file2, { throwIfNoEntry: false });
46688
46724
  if (!st)
46689
46725
  continue;
46690
- const holder = Number(fs5.readFileSync(file2, "utf8") || 0);
46726
+ const holder = Number(fs6.readFileSync(file2, "utf8") || 0);
46691
46727
  const fresh = Date.now() - st.mtimeMs < 5 * 60000;
46692
46728
  const alive = (() => {
46693
46729
  if (holder <= 0)
@@ -46700,7 +46736,7 @@ function lock() {
46700
46736
  })();
46701
46737
  if (alive && fresh)
46702
46738
  return null;
46703
- fs5.rmSync(file2, { force: true });
46739
+ fs6.rmSync(file2, { force: true });
46704
46740
  }
46705
46741
  return null;
46706
46742
  }
@@ -46784,7 +46820,7 @@ var REJECTED = new Set([18, 19, 20, 25]);
46784
46820
  var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
46785
46821
  var queued = (from) => {
46786
46822
  try {
46787
- return fs5.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
46823
+ return fs6.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
46788
46824
  } catch {
46789
46825
  return [];
46790
46826
  }
@@ -46795,16 +46831,16 @@ async function sendBatch(db, names) {
46795
46831
  for (const { name, from } of names) {
46796
46832
  let r;
46797
46833
  try {
46798
- r = current(JSON.parse(fs5.readFileSync(path5.join(from, name), "utf8")));
46834
+ r = current(JSON.parse(fs6.readFileSync(path6.join(from, name), "utf8")));
46799
46835
  } catch {
46800
- fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46801
- fs5.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
46836
+ fs6.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46837
+ fs6.renameSync(path6.join(from, name), path6.join(rejectedDir(), name));
46802
46838
  continue;
46803
46839
  }
46804
46840
  if (r)
46805
46841
  records.push({ name, from, r });
46806
46842
  else
46807
- fs5.rmSync(path5.join(from, name), { force: true });
46843
+ fs6.rmSync(path6.join(from, name), { force: true });
46808
46844
  }
46809
46845
  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 }]));
46810
46846
  const known = records.filter((x) => projects.has(x.r.project));
@@ -46827,10 +46863,10 @@ async function sendBatch(db, names) {
46827
46863
  }
46828
46864
  }
46829
46865
  if (bad.length) {
46830
- fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46866
+ fs6.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46831
46867
  for (const x of bad) {
46832
46868
  try {
46833
- fs5.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
46869
+ fs6.renameSync(path6.join(x.from, x.name), path6.join(rejectedDir(), x.name));
46834
46870
  } catch (e) {
46835
46871
  if (e.code !== "ENOENT")
46836
46872
  throw e;
@@ -46838,12 +46874,12 @@ async function sendBatch(db, names) {
46838
46874
  }
46839
46875
  }
46840
46876
  if (strayed.length) {
46841
- fs5.mkdirSync(held, { recursive: true, mode: 448 });
46877
+ fs6.mkdirSync(held, { recursive: true, mode: 448 });
46842
46878
  for (const x of strayed) {
46843
46879
  if (x.from === held)
46844
46880
  continue;
46845
46881
  try {
46846
- fs5.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
46882
+ fs6.renameSync(path6.join(x.from, x.name), path6.join(held, x.name));
46847
46883
  } catch (e) {
46848
46884
  if (e.code !== "ENOENT")
46849
46885
  throw e;
@@ -46853,7 +46889,7 @@ async function sendBatch(db, names) {
46853
46889
  const moved = new Set([...bad, ...strayed].map((x) => x.name));
46854
46890
  for (const x of records)
46855
46891
  if (!moved.has(x.name))
46856
- fs5.rmSync(path5.join(x.from, x.name), { force: true });
46892
+ fs6.rmSync(path6.join(x.from, x.name), { force: true });
46857
46893
  return { sent, rejected: bad.length };
46858
46894
  }
46859
46895
  async function flush(file2 = dbFile(), budgetMs = FLUSH_BUDGET_MS) {
@@ -46960,22 +46996,22 @@ function visible2(body) {
46960
46996
  }
46961
46997
  }
46962
46998
  function closingRefs(body, repo) {
46963
- const here = repo?.toLowerCase();
46999
+ const here2 = repo?.toLowerCase();
46964
47000
  return [...visible2(body).matchAll(CLOSES)].flatMap((m) => {
46965
47001
  const named = (m[1] ?? m[2])?.toLowerCase();
46966
- return !named || named === here ? [Number(m[3])] : [];
47002
+ return !named || named === here2 ? [Number(m[3])] : [];
46967
47003
  });
46968
47004
  }
46969
47005
  var repoOf = (key) => /^git:github\.com\/([^/]+\/[^/]+)$/.exec(key)?.[1] ?? null;
46970
47006
  var MAX_RESPONSE = 16 * 1024 * 1024;
46971
47007
  var HOST = ["--hostname", "github.com"];
46972
47008
  var plainEnv = () => ({ ...process.env, CLICOLOR_FORCE: "0" });
46973
- var gh = (repo, timeout = 60000) => async (path6, all = false) => {
46974
- const { stdout } = await exec("gh", ["api", `repos/${repo}/${path6}`, ...HOST, ...all ? ["--paginate", "--slurp"] : []], { encoding: "utf8", maxBuffer: MAX_RESPONSE, timeout, killSignal: "SIGKILL", env: plainEnv() }).catch((e) => {
47009
+ var gh = (repo, timeout = 60000) => async (path7, all = false) => {
47010
+ const { stdout } = await exec("gh", ["api", `repos/${repo}/${path7}`, ...HOST, ...all ? ["--paginate", "--slurp"] : []], { encoding: "utf8", maxBuffer: MAX_RESPONSE, timeout, killSignal: "SIGKILL", env: plainEnv() }).catch((e) => {
46975
47011
  if (e.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER")
46976
- throw new Error(`${path6.split("?")[0]} is too large to read (over ${MAX_RESPONSE / 1024 / 1024} MB)`);
47012
+ throw new Error(`${path7.split("?")[0]} is too large to read (over ${MAX_RESPONSE / 1024 / 1024} MB)`);
46977
47013
  if (e.killed)
46978
- throw new Error(`${path6.split("?")[0]} did not answer within ${timeout / 1000} seconds`);
47014
+ throw new Error(`${path7.split("?")[0]} did not answer within ${timeout / 1000} seconds`);
46979
47015
  throw e;
46980
47016
  });
46981
47017
  const parsed = JSON.parse(stdout);
@@ -48846,6 +48882,20 @@ async function saveText(db, id, projectId2, root, record3, probe) {
48846
48882
  return text2;
48847
48883
  }
48848
48884
 
48885
+ // server/src/backups.ts
48886
+ import fs7 from "node:fs";
48887
+ import path7 from "node:path";
48888
+ var backupDir = (file3 = dbFile()) => path7.join(path7.dirname(file3), "backups");
48889
+ var stem = (file3) => path7.basename(file3).replace(/\.db$/, "");
48890
+ var pattern = (file3) => new RegExp(`^${stem(file3).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.rev\\d+\\.(\\d{8}T\\d{9}Z)\\.\\d+\\.db$`);
48891
+ function backups(file3 = dbFile()) {
48892
+ const dir = backupDir(file3);
48893
+ if (!fs7.existsSync(dir))
48894
+ return [];
48895
+ const named = pattern(file3);
48896
+ return fs7.readdirSync(dir).filter((f) => named.test(f)).sort((a, b) => (named.exec(b)?.[1] ?? "").localeCompare(named.exec(a)?.[1] ?? "") || b.localeCompare(a)).map((f) => path7.join(dir, f));
48897
+ }
48898
+
48849
48899
  // server/src/forget.ts
48850
48900
  class Preview extends Error {
48851
48901
  outcome;
@@ -48987,7 +49037,7 @@ async function applyForget(file3, projectId2, ids, confirmed, signal) {
48987
49037
  await db.destroy();
48988
49038
  }
48989
49039
  }
48990
- function forgetText(o) {
49040
+ function forgetText(o, file3) {
48991
49041
  const lines = o.sources.map((s) => `- s${s.id} ${s.kind} in ${inline(s.artifact).slice(0, 120)} (${plural2(s.bytes, "byte")})`);
48992
49042
  if (o.already.length)
48993
49043
  lines.push(`- already forgotten: ${o.already.map((id) => `s${id}`).join(", ")} (only the cleanup runs again)`);
@@ -49001,7 +49051,13 @@ function forgetText(o) {
49001
49051
  lines.push(`- ${plural2(o.fields.definitions, "field definition")} and ${plural2(o.fields.values, "field value")} go with them`);
49002
49052
  if (o.units.length || o.fields.values)
49003
49053
  lines.push("Records keep their own text: if one repeats the forgotten words, they stay in it.");
49004
- lines.push("Copies outside the database (capture's waiting and set-aside files, backups) are not touched.");
49054
+ let copies;
49055
+ try {
49056
+ copies = backups(file3).length;
49057
+ } catch {
49058
+ copies = null;
49059
+ }
49060
+ lines.push(copies === null ? `Copies outside the database are not touched: capture's waiting and set-aside files. Backups made before migrating in ${backupDir(file3)} could not be listed; look there yourself if the words must go from every copy.` : copies ? `Copies outside the database are not touched: capture's waiting and set-aside files, and ${plural2(copies, "backup")} made before migrating, in ${backupDir(file3)}. Delete those backups yourself if the words must go from them too.` : "Copies outside the database (capture's waiting and set-aside files) are not touched.");
49005
49061
  return lines.join(`
49006
49062
  `);
49007
49063
  }
@@ -49019,23 +49075,6 @@ function framed(body) {
49019
49075
  `);
49020
49076
  }
49021
49077
 
49022
- // server/src/plugin.ts
49023
- import fs6 from "node:fs";
49024
- import path6 from "node:path";
49025
- import { fileURLToPath as fileURLToPath2 } from "node:url";
49026
- var MANIFEST = path6.join(".claude-plugin", "plugin.json");
49027
- function versionAt(root) {
49028
- try {
49029
- const m = JSON.parse(fs6.readFileSync(path6.join(root, MANIFEST), "utf8"));
49030
- return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
49031
- } catch {
49032
- return null;
49033
- }
49034
- }
49035
- var here = path6.dirname(fileURLToPath2(import.meta.url));
49036
- var ROOT = [path6.join(here, ".."), path6.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path6.join(here, "..");
49037
- var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
49038
-
49039
49078
  // server/src/mcp-record.ts
49040
49079
  requireRuntime();
49041
49080
  var db = null;
@@ -49158,7 +49197,7 @@ server.registerTool("forget_preview", {
49158
49197
  annotations: READ
49159
49198
  }, async (a, extra) => tool(async () => {
49160
49199
  const p = await projectOf(a.cwd, extra._meta);
49161
- return forgetText(await previewForget(dbFile(), p.projectId, idsOf(a.sources)));
49200
+ return forgetText(await previewForget(dbFile(), p.projectId, idsOf(a.sources)), dbFile());
49162
49201
  }));
49163
49202
  server.registerTool("forget_apply", {
49164
49203
  title: "Forget sources",
@@ -49179,7 +49218,7 @@ server.registerTool("forget_apply", {
49179
49218
  answer = await server.server.elicitInput({
49180
49219
  mode: "form",
49181
49220
  message: `Forget ${plural2(n, "source")} for good?
49182
- ${forgetText(seen)}`,
49221
+ ${forgetText(seen, dbFile())}`,
49183
49222
  requestedSchema: {
49184
49223
  type: "object",
49185
49224
  properties: {
@@ -49205,7 +49244,7 @@ ${forgetText(seen)}`,
49205
49244
  const done = await applyForget(dbFile(), p.projectId, ids, seen, extra.signal);
49206
49245
  return [
49207
49246
  n ? `Forgot ${plural2(n, "source")}.` : "Nothing new to forget.",
49208
- forgetText(done.outcome),
49247
+ forgetText(done.outcome, dbFile()),
49209
49248
  done.cleanup === "done" ? "The deleted text was cleared from the database file." : "Clearing the deleted text from the database file did not finish (another session may be reading it), so it may stay there until you run forget_apply with the same sources again."
49210
49249
  ].join(`
49211
49250
  `);