sphica 0.6.11 → 0.6.12

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "sphica",
4
- "version": "0.6.11",
4
+ "version": "0.6.12",
5
5
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
6
6
  "author": {
7
7
  "name": "iroha924",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.11",
3
+ "version": "0.6.12",
4
4
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
5
5
  "author": {
6
6
  "name": "iroha924",
package/dist/capture.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // server/src/capture.ts
4
4
  import { spawn } from "node:child_process";
5
- import fs4 from "node:fs";
6
- import path5 from "node:path";
5
+ import fs5 from "node:fs";
6
+ import path6 from "node:path";
7
7
 
8
8
  // server/node_modules/kysely/dist/util/object-utils.js
9
9
  function isUndefined(obj) {
@@ -10808,109 +10808,18 @@ function adapt(raw) {
10808
10808
  }
10809
10809
 
10810
10810
  // server/src/sqlite.ts
10811
- import fs from "node:fs";
10811
+ import fs2 from "node:fs";
10812
10812
  import os from "node:os";
10813
- import path from "node:path";
10813
+ import path2 from "node:path";
10814
10814
  import { constants as C, DatabaseSync } from "node:sqlite";
10815
- var SCHEMA_GENERATION = 2;
10816
- var SCHEMA_REVISION = 4;
10817
- var sphicaHome = () => process.env.SPHICA_HOME || path.join(os.homedir(), ".sphica");
10818
- var dbFile = () => process.env.SPHICA_DB || path.join(sphicaHome(), "sphica.db");
10819
- function requireRuntime() {
10820
- const proto = DatabaseSync.prototype;
10821
- if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
10822
- throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
10823
- }
10824
- function requireFile(file) {
10825
- if (!fs.existsSync(file))
10826
- throw new Error(`No database at ${file}. Create it with \`sphica init\`.`);
10827
- }
10828
- function prepare(raw, check) {
10829
- raw.enableDefensive(true);
10830
- raw.exec("pragma foreign_keys = on");
10831
- raw.exec("pragma busy_timeout = 5000");
10832
- if (check === "none")
10833
- return;
10834
- checkGeneration(raw);
10835
- if (check === "generation")
10836
- return;
10837
- const got = raw.prepare("pragma user_version").get()?.user_version;
10838
- if (got === SCHEMA_REVISION)
10839
- return;
10840
- 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."));
10841
- }
10842
- function generationOf(raw) {
10843
- const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
10844
- if (!has) {
10845
- const any = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'project'").get();
10846
- return any ? 1 : null;
10847
- }
10848
- return raw.prepare("select generation from sphica_generation").get()?.generation ?? null;
10849
- }
10850
- function checkGeneration(raw) {
10851
- const got = generationOf(raw);
10852
- if (got === SCHEMA_GENERATION)
10853
- return;
10854
- if (got === null)
10855
- throw new Error("The database has no sphica schema. Create it with `sphica init`.");
10856
- 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.");
10857
- }
10858
- var READER_FUNCTIONS = new Set([
10859
- "bm25",
10860
- "coalesce",
10861
- "count",
10862
- "instr",
10863
- "json_array_length",
10864
- "json_extract",
10865
- "json_group_array",
10866
- "json_object",
10867
- "length",
10868
- "lower",
10869
- "match",
10870
- "max",
10871
- "min",
10872
- "substr"
10873
- ]);
10874
- var SHADOW = /^(unit|source)_fts_(data|idx|docsize|config)$/;
10875
10815
 
10876
- // server/src/db.ts
10877
- var JSON_COLUMNS = new Set(["next"]);
10878
- var TOP_LEVEL = /^\$\[\d+\]\."([^"]+)"$/;
10879
- var parseJson = new ParseJSONResultsPlugin({
10880
- shouldParse: (_value, jsonPath) => JSON_COLUMNS.has(jsonPath.match(TOP_LEVEL)?.[1] ?? "")
10881
- });
10882
- function kyselyOn(connect) {
10883
- return new Kysely({
10884
- dialect: new SqliteDialect({ database: async () => adapt(connect()) }),
10885
- plugins: [parseJson]
10886
- });
10887
- }
10888
- async function inTransaction(db, fn) {
10889
- return db.connection().execute(async (c) => {
10890
- await sql`begin immediate`.execute(c);
10891
- try {
10892
- const out = await fn(c);
10893
- await sql`commit`.execute(c);
10894
- return out;
10895
- } catch (e) {
10896
- await sql`rollback`.execute(c).catch(() => {});
10897
- throw e;
10898
- }
10899
- });
10900
- }
10901
- var iso = (d) => {
10902
- const t = new Date(d);
10903
- if (Number.isNaN(t.getTime()))
10904
- throw new RangeError(`Not a readable time: ${String(d)}`);
10905
- return t.toISOString();
10906
- };
10907
- function sqliteCode(e) {
10908
- const x = e;
10909
- return x?.code === "ERR_SQLITE_ERROR" && typeof x.errcode === "number" ? x.errcode & 255 : null;
10910
- }
10816
+ // server/src/plugin.ts
10817
+ import fs from "node:fs";
10818
+ import path from "node:path";
10819
+ import { fileURLToPath } from "node:url";
10911
10820
 
10912
- // server/src/db-write.ts
10913
- import { constants as C2, DatabaseSync as DatabaseSync2 } from "node:sqlite";
10821
+ // server/src/panel.ts
10822
+ import { stripVTControlCharacters, styleText } from "node:util";
10914
10823
 
10915
10824
  // server/src/text.ts
10916
10825
  import crypto from "node:crypto";
@@ -11163,7 +11072,147 @@ function explain(e, depth) {
11163
11072
  return own && inner ? `${own} (${inner})` : own || inner;
11164
11073
  }
11165
11074
 
11075
+ // server/src/panel.ts
11076
+ var title = (text) => `✦ ${text}`;
11077
+ var rule = (text) => text.split(`
11078
+ `).map((line) => line ? `│ ${line}` : "│").join(`
11079
+ `);
11080
+ var foot = (text) => `╰─ ${text}`;
11081
+ var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
11082
+ `);
11083
+ var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
11084
+ var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
11085
+ `).replace(/(?![\t\n])\p{Cc}/gu, ""));
11086
+
11087
+ // server/src/plugin.ts
11088
+ var MANIFEST = path.join(".claude-plugin", "plugin.json");
11089
+ var PACKAGE = "package.json";
11090
+ function versionAt(root) {
11091
+ try {
11092
+ const m = JSON.parse(fs.readFileSync(path.join(root, MANIFEST), "utf8"));
11093
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
11094
+ } catch {
11095
+ return null;
11096
+ }
11097
+ }
11098
+ function packageVersionAt(root) {
11099
+ try {
11100
+ const m = JSON.parse(fs.readFileSync(path.join(root, PACKAGE), "utf8"));
11101
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
11102
+ } catch {
11103
+ return null;
11104
+ }
11105
+ }
11106
+ var here = path.dirname(fileURLToPath(import.meta.url));
11107
+ var ROOT = [path.join(here, ".."), path.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path.join(here, "..");
11108
+ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11109
+
11110
+ // server/src/sqlite.ts
11111
+ var SCHEMA_GENERATION = 2;
11112
+ var SCHEMA_REVISION = 4;
11113
+ var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
11114
+ var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
11115
+ function requireRuntime() {
11116
+ const proto = DatabaseSync.prototype;
11117
+ if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
11118
+ throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
11119
+ }
11120
+ function requireFile(file) {
11121
+ if (!fs2.existsSync(file))
11122
+ throw new Error(`No database at ${file}. Create it with \`sphica init\`.`);
11123
+ }
11124
+ function prepare(raw, check) {
11125
+ raw.enableDefensive(true);
11126
+ raw.exec("pragma foreign_keys = on");
11127
+ raw.exec("pragma busy_timeout = 5000");
11128
+ if (check === "none")
11129
+ return;
11130
+ checkGeneration(raw);
11131
+ if (check === "generation")
11132
+ return;
11133
+ const got = raw.prepare("pragma user_version").get()?.user_version;
11134
+ if (got === SCHEMA_REVISION)
11135
+ return;
11136
+ throw new RevisionMismatch(got ?? 0);
11137
+ }
11138
+
11139
+ class RevisionMismatch extends Error {
11140
+ constructor(got) {
11141
+ 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."));
11142
+ }
11143
+ }
11144
+ function generationOf(raw) {
11145
+ const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
11146
+ if (!has) {
11147
+ const any = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'project'").get();
11148
+ return any ? 1 : null;
11149
+ }
11150
+ return raw.prepare("select generation from sphica_generation").get()?.generation ?? null;
11151
+ }
11152
+ function checkGeneration(raw) {
11153
+ const got = generationOf(raw);
11154
+ if (got === SCHEMA_GENERATION)
11155
+ return;
11156
+ if (got === null)
11157
+ throw new Error("The database has no sphica schema. Create it with `sphica init`.");
11158
+ 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.");
11159
+ }
11160
+ var READER_FUNCTIONS = new Set([
11161
+ "bm25",
11162
+ "coalesce",
11163
+ "count",
11164
+ "instr",
11165
+ "json_array_length",
11166
+ "json_extract",
11167
+ "json_group_array",
11168
+ "json_object",
11169
+ "length",
11170
+ "lower",
11171
+ "match",
11172
+ "max",
11173
+ "min",
11174
+ "substr"
11175
+ ]);
11176
+ var SHADOW = /^(unit|source)_fts_(data|idx|docsize|config)$/;
11177
+
11178
+ // server/src/db.ts
11179
+ var JSON_COLUMNS = new Set(["next"]);
11180
+ var TOP_LEVEL = /^\$\[\d+\]\."([^"]+)"$/;
11181
+ var parseJson = new ParseJSONResultsPlugin({
11182
+ shouldParse: (_value, jsonPath) => JSON_COLUMNS.has(jsonPath.match(TOP_LEVEL)?.[1] ?? "")
11183
+ });
11184
+ function kyselyOn(connect) {
11185
+ return new Kysely({
11186
+ dialect: new SqliteDialect({ database: async () => adapt(connect()) }),
11187
+ plugins: [parseJson]
11188
+ });
11189
+ }
11190
+ async function inTransaction(db, fn) {
11191
+ return db.connection().execute(async (c) => {
11192
+ await sql`begin immediate`.execute(c);
11193
+ try {
11194
+ const out = await fn(c);
11195
+ await sql`commit`.execute(c);
11196
+ return out;
11197
+ } catch (e) {
11198
+ await sql`rollback`.execute(c).catch(() => {});
11199
+ throw e;
11200
+ }
11201
+ });
11202
+ }
11203
+ var iso = (d) => {
11204
+ const t = new Date(d);
11205
+ if (Number.isNaN(t.getTime()))
11206
+ throw new RangeError(`Not a readable time: ${String(d)}`);
11207
+ return t.toISOString();
11208
+ };
11209
+ function sqliteCode(e) {
11210
+ const x = e;
11211
+ return x?.code === "ERR_SQLITE_ERROR" && typeof x.errcode === "number" ? x.errcode & 255 : null;
11212
+ }
11213
+
11166
11214
  // server/src/db-write.ts
11215
+ import { constants as C2, DatabaseSync as DatabaseSync2 } from "node:sqlite";
11167
11216
  var ddl = null;
11168
11217
  var DDL = () => ddl ??= new Set([
11169
11218
  "SQLITE_ALTER_TABLE",
@@ -11303,7 +11352,7 @@ function connectWriter(role, file = dbFile(), create = false) {
11303
11352
  raw.setAuthorizer((action, p1, p2, _db, triggerOrView) => captureAuthorizer(own, action, p1, p2, triggerOrView));
11304
11353
  const prepare2 = raw.prepare.bind(raw);
11305
11354
  const exec = raw.exec.bind(raw);
11306
- const mark = (f) => (...a) => {
11355
+ const mark2 = (f) => (...a) => {
11307
11356
  own = true;
11308
11357
  try {
11309
11358
  return f(...a);
@@ -11311,8 +11360,8 @@ function connectWriter(role, file = dbFile(), create = false) {
11311
11360
  own = false;
11312
11361
  }
11313
11362
  };
11314
- raw.prepare = mark(prepare2);
11315
- raw.exec = mark(exec);
11363
+ raw.prepare = mark2(prepare2);
11364
+ raw.exec = mark2(exec);
11316
11365
  }
11317
11366
  return raw;
11318
11367
  }
@@ -11323,31 +11372,18 @@ function openWriter(role, file = dbFile()) {
11323
11372
  // server/src/knowledge.ts
11324
11373
  var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), host, externalId);
11325
11374
 
11326
- // server/src/panel.ts
11327
- import { stripVTControlCharacters, styleText } from "node:util";
11328
- var title = (text) => `✦ ${text}`;
11329
- var rule = (text) => text.split(`
11330
- `).map((line) => line ? `│ ${line}` : "│").join(`
11331
- `);
11332
- var foot = (text) => `╰─ ${text}`;
11333
- var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
11334
- `);
11335
- var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
11336
- var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
11337
- `).replace(/(?![\t\n])\p{Cc}/gu, ""));
11338
-
11339
11375
  // server/src/project.ts
11340
11376
  import { execFileSync } from "node:child_process";
11341
- import fs2 from "node:fs";
11342
- import path3 from "node:path";
11377
+ import fs3 from "node:fs";
11378
+ import path4 from "node:path";
11343
11379
 
11344
11380
  // server/src/anchors.ts
11345
- import path2 from "node:path";
11381
+ import path3 from "node:path";
11346
11382
  var MAX_BYTES = 2 * 1024 * 1024;
11347
- var leaves = (rel) => rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
11383
+ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel);
11348
11384
 
11349
11385
  // server/src/project.ts
11350
- var localFile = () => path3.join(sphicaHome(), "projects.json");
11386
+ var localFile = () => path4.join(sphicaHome(), "projects.json");
11351
11387
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
11352
11388
  function normalizeRemote(url) {
11353
11389
  const raw = String(url ?? "").trim();
@@ -11380,7 +11416,7 @@ var git = (dir, ...args) => {
11380
11416
  function localMap() {
11381
11417
  let raw;
11382
11418
  try {
11383
- raw = fs2.readFileSync(localFile(), "utf8");
11419
+ raw = fs3.readFileSync(localFile(), "utf8");
11384
11420
  } catch (e) {
11385
11421
  if (e.code === "ENOENT")
11386
11422
  return {};
@@ -11392,32 +11428,32 @@ function localMap() {
11392
11428
  } catch {
11393
11429
  m = null;
11394
11430
  }
11395
- if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path3.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
11431
+ if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path4.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
11396
11432
  throw new Error(`${localFile()} is not a valid JSON project table. Fix or delete it, then name the project again.`);
11397
11433
  return m;
11398
11434
  }
11399
11435
  function identify(dir) {
11400
- const given = path3.resolve(dir);
11436
+ const given = path4.resolve(dir);
11401
11437
  const top = git(given, "rev-parse", "--show-toplevel");
11402
11438
  const root = top || given;
11403
11439
  const remote = top ? normalizeRemote(git(root, "remote", "get-url", "origin")) : null;
11404
11440
  if (remote)
11405
11441
  return { key: `git:${remote}`, root, name: remote.split("/").slice(1).join("/") || remote };
11406
11442
  const map = localMap();
11407
- for (let d = root;; d = path3.dirname(d)) {
11443
+ for (let d = root;; d = path4.dirname(d)) {
11408
11444
  const local = map[d];
11409
11445
  if (local && LOCAL_KEY.test(local))
11410
11446
  return { key: `local:${local}`, root: d, name: local };
11411
- if (top || path3.dirname(d) === d)
11447
+ if (top || path4.dirname(d) === d)
11412
11448
  return null;
11413
11449
  }
11414
11450
  }
11415
11451
  function relativeTo(root, file, cwd = root) {
11416
- const abs = path3.resolve(cwd, file);
11417
- const rel = path3.relative(root, abs);
11452
+ const abs = path4.resolve(cwd, file);
11453
+ const rel = path4.relative(root, abs);
11418
11454
  if (!rel || leaves(rel))
11419
11455
  return null;
11420
- return rel.split(path3.sep).join("/");
11456
+ return rel.split(path4.sep).join("/");
11421
11457
  }
11422
11458
  function patchPaths(patch) {
11423
11459
  const out = [];
@@ -11432,8 +11468,8 @@ function patchPaths(patch) {
11432
11468
 
11433
11469
  // server/src/worktree.ts
11434
11470
  import { execFileSync as execFileSync2 } from "node:child_process";
11435
- import fs3 from "node:fs";
11436
- import path4 from "node:path";
11471
+ import fs4 from "node:fs";
11472
+ import path5 from "node:path";
11437
11473
  var MAX_PATHS = 200;
11438
11474
  var git2 = (root, args) => {
11439
11475
  try {
@@ -11467,7 +11503,7 @@ function snapshot(root) {
11467
11503
  continue;
11468
11504
  let st;
11469
11505
  try {
11470
- st = fs3.lstatSync(path4.join(root, p), { throwIfNoEntry: false });
11506
+ st = fs4.lstatSync(path5.join(root, p), { throwIfNoEntry: false });
11471
11507
  } catch {
11472
11508
  st = undefined;
11473
11509
  }
@@ -11490,13 +11526,13 @@ function changed(root, before, after) {
11490
11526
  }
11491
11527
 
11492
11528
  // server/src/capture.ts
11493
- var spoolDir = () => path5.join(sphicaHome(), "spool");
11494
- var stateFile = () => path5.join(sphicaHome(), "capture.json");
11495
- var rejectedDir = () => path5.join(spoolDir(), "rejected");
11496
- var unregisteredDir = () => path5.join(spoolDir(), "unregistered");
11529
+ var spoolDir = () => path6.join(sphicaHome(), "spool");
11530
+ var stateFile = () => path6.join(sphicaHome(), "capture.json");
11531
+ var rejectedDir = () => path6.join(spoolDir(), "rejected");
11532
+ var unregisteredDir = () => path6.join(spoolDir(), "unregistered");
11497
11533
  var HOLD_DAYS = 30;
11498
11534
  var HOLD_MAX = 1000;
11499
- var baselineDir = () => path5.join(sphicaHome(), "worktree");
11535
+ var baselineDir = () => path6.join(sphicaHome(), "worktree");
11500
11536
  function current(raw) {
11501
11537
  const r = raw;
11502
11538
  if (r.v === 2)
@@ -11543,7 +11579,7 @@ ${z}`,
11543
11579
  function prune(held) {
11544
11580
  let files;
11545
11581
  try {
11546
- files = fs4.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
11582
+ files = fs5.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
11547
11583
  } catch {
11548
11584
  return;
11549
11585
  }
@@ -11551,21 +11587,21 @@ function prune(held) {
11551
11587
  const stale = files.filter((f) => Number(f.split("-")[0]) < cutoff);
11552
11588
  const over = files.slice(0, Math.max(0, files.length - HOLD_MAX));
11553
11589
  for (const f of new Set([...stale, ...over]))
11554
- fs4.rmSync(path5.join(held, f), { force: true });
11590
+ fs5.rmSync(path6.join(held, f), { force: true });
11555
11591
  }
11556
11592
  function spool(record) {
11557
11593
  const dir = spoolDir();
11558
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
11594
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
11559
11595
  const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}.json`;
11560
- const tmp = path5.join(dir, `.${name}`);
11561
- fs4.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
11562
- fs4.renameSync(tmp, path5.join(dir, name));
11596
+ const tmp = path6.join(dir, `.${name}`);
11597
+ fs5.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
11598
+ fs5.renameSync(tmp, path6.join(dir, name));
11563
11599
  }
11564
11600
  var branchOf = (root) => {
11565
11601
  try {
11566
- const dotgit = path5.join(root, ".git");
11567
- const gitdir = fs4.statSync(dotgit).isFile() ? path5.resolve(root, fs4.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
11568
- const h = fs4.readFileSync(path5.join(gitdir, "HEAD"), "utf8").trim();
11602
+ const dotgit = path6.join(root, ".git");
11603
+ const gitdir = fs5.statSync(dotgit).isFile() ? path6.resolve(root, fs5.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
11604
+ const h = fs5.readFileSync(path6.join(gitdir, "HEAD"), "utf8").trim();
11569
11605
  return h.startsWith("ref: refs/heads/") ? h.slice("ref: refs/heads/".length) : null;
11570
11606
  } catch {
11571
11607
  return null;
@@ -11609,7 +11645,7 @@ Notes: ${notes.trim()}` : "";
11609
11645
  };
11610
11646
  }
11611
11647
  function captureNotice(file = dbFile()) {
11612
- if (!fs4.existsSync(file))
11648
+ if (!fs5.existsSync(file))
11613
11649
  return panel("sphica: no database, so conversations are not recorded", [file], "Create it with sphica init");
11614
11650
  const s = readState();
11615
11651
  if (s.stuck)
@@ -11618,32 +11654,32 @@ function captureNotice(file = dbFile()) {
11618
11654
  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");
11619
11655
  return null;
11620
11656
  }
11621
- var baselineFile = (host, session) => path5.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
11657
+ var baselineFile = (host, session) => path6.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
11622
11658
  function readBaseline(file) {
11623
11659
  try {
11624
- return JSON.parse(fs4.readFileSync(file, "utf8"));
11660
+ return JSON.parse(fs5.readFileSync(file, "utf8"));
11625
11661
  } catch {
11626
11662
  return null;
11627
11663
  }
11628
11664
  }
11629
11665
  function writeBaseline(file, b) {
11630
- fs4.mkdirSync(path5.dirname(file), { recursive: true, mode: 448 });
11666
+ fs5.mkdirSync(path6.dirname(file), { recursive: true, mode: 448 });
11631
11667
  const tmp = `${file}.${process.pid}.tmp`;
11632
- fs4.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
11633
- fs4.renameSync(tmp, file);
11668
+ fs5.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
11669
+ fs5.renameSync(tmp, file);
11634
11670
  }
11635
11671
  function pruneBaselines() {
11636
11672
  const cutoff = Date.now() - HOLD_DAYS * 24 * 60 * 60 * 1000;
11637
11673
  let files;
11638
11674
  try {
11639
- files = fs4.readdirSync(baselineDir());
11675
+ files = fs5.readdirSync(baselineDir());
11640
11676
  } catch {
11641
11677
  return;
11642
11678
  }
11643
11679
  for (const f of files) {
11644
- const file = path5.join(baselineDir(), f);
11645
- if ((fs4.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
11646
- fs4.rmSync(file, { force: true });
11680
+ const file = path6.join(baselineDir(), f);
11681
+ if ((fs5.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
11682
+ fs5.rmSync(file, { force: true });
11647
11683
  }
11648
11684
  }
11649
11685
  function onHook(host, input) {
@@ -11654,7 +11690,7 @@ function onHook(host, input) {
11654
11690
  return { flush: false };
11655
11691
  const file = process.env.CLAUDE_ENV_FILE;
11656
11692
  if (file && input.session_id && /^[A-Za-z0-9_-]+$/.test(input.session_id)) {
11657
- fs4.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
11693
+ fs5.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
11658
11694
  `);
11659
11695
  }
11660
11696
  const place2 = identify(input.cwd ?? process.cwd());
@@ -11740,13 +11776,13 @@ function onHook(host, input) {
11740
11776
  }
11741
11777
  function writeState(s) {
11742
11778
  try {
11743
- fs4.writeFileSync(stateFile(), JSON.stringify(s));
11779
+ fs5.writeFileSync(stateFile(), JSON.stringify(s));
11744
11780
  } catch {}
11745
11781
  }
11746
11782
  function readState() {
11747
11783
  const count = (dir) => {
11748
11784
  try {
11749
- return fs4.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
11785
+ return fs5.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
11750
11786
  } catch {
11751
11787
  return 0;
11752
11788
  }
@@ -11758,7 +11794,7 @@ function readState() {
11758
11794
  };
11759
11795
  let raw = {};
11760
11796
  try {
11761
- const parsed = JSON.parse(fs4.readFileSync(stateFile(), "utf8"));
11797
+ const parsed = JSON.parse(fs5.readFileSync(stateFile(), "utf8"));
11762
11798
  if (parsed && typeof parsed === "object")
11763
11799
  raw = parsed;
11764
11800
  } catch {}
@@ -11772,25 +11808,25 @@ function readState() {
11772
11808
  };
11773
11809
  }
11774
11810
  function lock() {
11775
- const file = path5.join(spoolDir(), ".lock");
11776
- fs4.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
11811
+ const file = path6.join(spoolDir(), ".lock");
11812
+ fs5.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
11777
11813
  for (let attempt = 0;attempt < 2; attempt++) {
11778
11814
  try {
11779
- fs4.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
11815
+ fs5.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
11780
11816
  return () => {
11781
11817
  try {
11782
- if (fs4.readFileSync(file, "utf8") === String(process.pid))
11783
- fs4.rmSync(file, { force: true });
11818
+ if (fs5.readFileSync(file, "utf8") === String(process.pid))
11819
+ fs5.rmSync(file, { force: true });
11784
11820
  } catch {}
11785
11821
  };
11786
11822
  } catch (e) {
11787
11823
  if (e.code !== "EEXIST")
11788
11824
  throw e;
11789
11825
  }
11790
- const st = fs4.statSync(file, { throwIfNoEntry: false });
11826
+ const st = fs5.statSync(file, { throwIfNoEntry: false });
11791
11827
  if (!st)
11792
11828
  continue;
11793
- const holder = Number(fs4.readFileSync(file, "utf8") || 0);
11829
+ const holder = Number(fs5.readFileSync(file, "utf8") || 0);
11794
11830
  const fresh = Date.now() - st.mtimeMs < 5 * 60000;
11795
11831
  const alive = (() => {
11796
11832
  if (holder <= 0)
@@ -11803,7 +11839,7 @@ function lock() {
11803
11839
  })();
11804
11840
  if (alive && fresh)
11805
11841
  return null;
11806
- fs4.rmSync(file, { force: true });
11842
+ fs5.rmSync(file, { force: true });
11807
11843
  }
11808
11844
  return null;
11809
11845
  }
@@ -11887,7 +11923,7 @@ var REJECTED = new Set([18, 19, 20, 25]);
11887
11923
  var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
11888
11924
  var queued = (from) => {
11889
11925
  try {
11890
- return fs4.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
11926
+ return fs5.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
11891
11927
  } catch {
11892
11928
  return [];
11893
11929
  }
@@ -11898,16 +11934,16 @@ async function sendBatch(db, names) {
11898
11934
  for (const { name, from } of names) {
11899
11935
  let r;
11900
11936
  try {
11901
- r = current(JSON.parse(fs4.readFileSync(path5.join(from, name), "utf8")));
11937
+ r = current(JSON.parse(fs5.readFileSync(path6.join(from, name), "utf8")));
11902
11938
  } catch {
11903
- fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11904
- fs4.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
11939
+ fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11940
+ fs5.renameSync(path6.join(from, name), path6.join(rejectedDir(), name));
11905
11941
  continue;
11906
11942
  }
11907
11943
  if (r)
11908
11944
  records.push({ name, from, r });
11909
11945
  else
11910
- fs4.rmSync(path5.join(from, name), { force: true });
11946
+ fs5.rmSync(path6.join(from, name), { force: true });
11911
11947
  }
11912
11948
  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 }]));
11913
11949
  const known = records.filter((x) => projects.has(x.r.project));
@@ -11930,10 +11966,10 @@ async function sendBatch(db, names) {
11930
11966
  }
11931
11967
  }
11932
11968
  if (bad.length) {
11933
- fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11969
+ fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11934
11970
  for (const x of bad) {
11935
11971
  try {
11936
- fs4.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
11972
+ fs5.renameSync(path6.join(x.from, x.name), path6.join(rejectedDir(), x.name));
11937
11973
  } catch (e) {
11938
11974
  if (e.code !== "ENOENT")
11939
11975
  throw e;
@@ -11941,12 +11977,12 @@ async function sendBatch(db, names) {
11941
11977
  }
11942
11978
  }
11943
11979
  if (strayed.length) {
11944
- fs4.mkdirSync(held, { recursive: true, mode: 448 });
11980
+ fs5.mkdirSync(held, { recursive: true, mode: 448 });
11945
11981
  for (const x of strayed) {
11946
11982
  if (x.from === held)
11947
11983
  continue;
11948
11984
  try {
11949
- fs4.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
11985
+ fs5.renameSync(path6.join(x.from, x.name), path6.join(held, x.name));
11950
11986
  } catch (e) {
11951
11987
  if (e.code !== "ENOENT")
11952
11988
  throw e;
@@ -11956,7 +11992,7 @@ async function sendBatch(db, names) {
11956
11992
  const moved = new Set([...bad, ...strayed].map((x) => x.name));
11957
11993
  for (const x of records)
11958
11994
  if (!moved.has(x.name))
11959
- fs4.rmSync(path5.join(x.from, x.name), { force: true });
11995
+ fs5.rmSync(path6.join(x.from, x.name), { force: true });
11960
11996
  return { sent, rejected: bad.length };
11961
11997
  }
11962
11998
  async function flush(file = dbFile(), budgetMs = FLUSH_BUDGET_MS) {