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/mcp.js CHANGED
@@ -45997,21 +45997,49 @@ function adapt(raw) {
45997
45997
  }
45998
45998
 
45999
45999
  // server/src/sqlite.ts
46000
- import fs from "node:fs";
46000
+ import fs2 from "node:fs";
46001
46001
  import os from "node:os";
46002
- import path from "node:path";
46002
+ import path2 from "node:path";
46003
46003
  import { constants as C, DatabaseSync } from "node:sqlite";
46004
+
46005
+ // server/src/plugin.ts
46006
+ import fs from "node:fs";
46007
+ import path from "node:path";
46008
+ import { fileURLToPath } from "node:url";
46009
+ var MANIFEST = path.join(".claude-plugin", "plugin.json");
46010
+ var PACKAGE = "package.json";
46011
+ function versionAt(root) {
46012
+ try {
46013
+ const m = JSON.parse(fs.readFileSync(path.join(root, MANIFEST), "utf8"));
46014
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
46015
+ } catch {
46016
+ return null;
46017
+ }
46018
+ }
46019
+ function packageVersionAt(root) {
46020
+ try {
46021
+ const m = JSON.parse(fs.readFileSync(path.join(root, PACKAGE), "utf8"));
46022
+ return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
46023
+ } catch {
46024
+ return null;
46025
+ }
46026
+ }
46027
+ var here = path.dirname(fileURLToPath(import.meta.url));
46028
+ var ROOT = [path.join(here, ".."), path.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path.join(here, "..");
46029
+ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
46030
+
46031
+ // server/src/sqlite.ts
46004
46032
  var SCHEMA_GENERATION = 2;
46005
46033
  var SCHEMA_REVISION = 4;
46006
- var sphicaHome = () => process.env.SPHICA_HOME || path.join(os.homedir(), ".sphica");
46007
- var dbFile = () => process.env.SPHICA_DB || path.join(sphicaHome(), "sphica.db");
46034
+ var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
46035
+ var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
46008
46036
  function requireRuntime() {
46009
46037
  const proto = DatabaseSync.prototype;
46010
46038
  if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
46011
46039
  throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
46012
46040
  }
46013
46041
  function requireFile(file2) {
46014
- if (!fs.existsSync(file2))
46042
+ if (!fs2.existsSync(file2))
46015
46043
  throw new Error(`No database at ${file2}. Create it with \`sphica init\`.`);
46016
46044
  }
46017
46045
  function prepare(raw, check2) {
@@ -46026,7 +46054,13 @@ function prepare(raw, check2) {
46026
46054
  const got = raw.prepare("pragma user_version").get()?.user_version;
46027
46055
  if (got === SCHEMA_REVISION)
46028
46056
  return;
46029
- 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."));
46057
+ throw new RevisionMismatch(got ?? 0);
46058
+ }
46059
+
46060
+ class RevisionMismatch extends Error {
46061
+ constructor(got) {
46062
+ 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."));
46063
+ }
46030
46064
  }
46031
46065
  function generationOf(raw) {
46032
46066
  const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
@@ -46099,29 +46133,29 @@ function openReader(file2 = dbFile()) {
46099
46133
  }
46100
46134
 
46101
46135
  // server/src/export.ts
46102
- import fs4 from "node:fs";
46103
- import path4 from "node:path";
46136
+ import fs5 from "node:fs";
46137
+ import path5 from "node:path";
46104
46138
 
46105
46139
  // server/src/anchors.ts
46106
- import fs2 from "node:fs";
46107
- import path2 from "node:path";
46140
+ import fs3 from "node:fs";
46141
+ import path3 from "node:path";
46108
46142
  var MAX_BYTES = 2 * 1024 * 1024;
46109
- var leaves = (rel) => rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
46143
+ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel);
46110
46144
  var literal3 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46111
46145
  function readBytes(root, rel) {
46112
- const abs = path2.join(root, rel);
46113
- if (leaves(path2.relative(root, abs)))
46146
+ const abs = path3.join(root, rel);
46147
+ if (leaves(path3.relative(root, abs)))
46114
46148
  return;
46115
- const st = fs2.lstatSync(abs, { throwIfNoEntry: false });
46149
+ const st = fs3.lstatSync(abs, { throwIfNoEntry: false });
46116
46150
  if (!st)
46117
46151
  return null;
46118
46152
  if (!st.isFile() || st.size > MAX_BYTES)
46119
46153
  return;
46120
46154
  try {
46121
- const inside = path2.relative(fs2.realpathSync(root), fs2.realpathSync(abs));
46155
+ const inside = path3.relative(fs3.realpathSync(root), fs3.realpathSync(abs));
46122
46156
  if (leaves(inside))
46123
46157
  return;
46124
- const buf = fs2.readFileSync(abs);
46158
+ const buf = fs3.readFileSync(abs);
46125
46159
  return buf.includes(0) ? undefined : buf;
46126
46160
  } catch {
46127
46161
  return;
@@ -46139,20 +46173,20 @@ function findSymbol(text, symbol2) {
46139
46173
  function fileState(root, rel) {
46140
46174
  if (!root)
46141
46175
  return "unknown";
46142
- if (leaves(path2.relative(root, path2.join(root, rel))))
46176
+ if (leaves(path3.relative(root, path3.join(root, rel))))
46143
46177
  return "unknown";
46144
46178
  let realRoot;
46145
46179
  try {
46146
- realRoot = fs2.realpathSync(root);
46180
+ realRoot = fs3.realpathSync(root);
46147
46181
  } catch {
46148
46182
  return "unknown";
46149
46183
  }
46150
46184
  let at = root;
46151
46185
  for (const part of rel.split("/")) {
46152
- at = path2.join(at, part);
46186
+ at = path3.join(at, part);
46153
46187
  let st;
46154
46188
  try {
46155
- st = fs2.lstatSync(at, { throwIfNoEntry: false });
46189
+ st = fs3.lstatSync(at, { throwIfNoEntry: false });
46156
46190
  } catch (e) {
46157
46191
  if (e.code === "ENOTDIR")
46158
46192
  return "gone";
@@ -46162,7 +46196,7 @@ function fileState(root, rel) {
46162
46196
  return "gone";
46163
46197
  if (st.isSymbolicLink()) {
46164
46198
  try {
46165
- if (leaves(path2.relative(realRoot, fs2.realpathSync(at))))
46199
+ if (leaves(path3.relative(realRoot, fs3.realpathSync(at))))
46166
46200
  return "unknown";
46167
46201
  } catch {
46168
46202
  return "unknown";
@@ -46354,8 +46388,8 @@ function part(id, text, from) {
46354
46388
  }
46355
46389
 
46356
46390
  // server/src/rule-files.ts
46357
- import fs3 from "node:fs";
46358
- import path3 from "node:path";
46391
+ import fs4 from "node:fs";
46392
+ import path4 from "node:path";
46359
46393
 
46360
46394
  // server/src/git.ts
46361
46395
  import { execFileSync } from "node:child_process";
@@ -46384,7 +46418,7 @@ function isRuleFile(rel) {
46384
46418
  function ruleFiles(root) {
46385
46419
  const listed = gitList(root) ?? walk(root);
46386
46420
  const out = { files: [], skipped: 0, incomplete: listed.incomplete };
46387
- const realRoot = fs3.realpathSync(root);
46421
+ const realRoot = fs4.realpathSync(root);
46388
46422
  for (const [i, rel] of [...new Set(listed.paths)].sort().entries()) {
46389
46423
  if (i >= RULE_LIMITS.files) {
46390
46424
  out.skipped++;
@@ -46401,18 +46435,18 @@ function ruleFiles(root) {
46401
46435
  return out;
46402
46436
  }
46403
46437
  function readBounded(root, realRoot, rel) {
46404
- const abs = path3.join(root, rel);
46405
- if (leaves(path3.relative(root, abs)))
46438
+ const abs = path4.join(root, rel);
46439
+ if (leaves(path4.relative(root, abs)))
46406
46440
  return;
46407
46441
  try {
46408
- const st = fs3.lstatSync(abs, { throwIfNoEntry: false });
46442
+ const st = fs4.lstatSync(abs, { throwIfNoEntry: false });
46409
46443
  if (!st)
46410
46444
  return null;
46411
46445
  if (!st.isFile() || st.size > RULE_LIMITS.bytes)
46412
46446
  return;
46413
- if (leaves(path3.relative(realRoot, fs3.realpathSync(abs))))
46447
+ if (leaves(path4.relative(realRoot, fs4.realpathSync(abs))))
46414
46448
  return;
46415
- const buf = fs3.readFileSync(abs);
46449
+ const buf = fs4.readFileSync(abs);
46416
46450
  return buf.length > RULE_LIMITS.bytes || buf.includes(0) ? undefined : buf.toString("utf8");
46417
46451
  } catch {
46418
46452
  return;
@@ -46443,7 +46477,7 @@ function walk(root) {
46443
46477
  return;
46444
46478
  let list;
46445
46479
  try {
46446
- list = fs3.readdirSync(path3.join(root, rel), { withFileTypes: true });
46480
+ list = fs4.readdirSync(path4.join(root, rel), { withFileTypes: true });
46447
46481
  } catch {
46448
46482
  unread++;
46449
46483
  return;
@@ -46653,22 +46687,22 @@ var instructionFile = (relative) => {
46653
46687
  function exportPath(root, given) {
46654
46688
  if (inline(given) !== given || given.includes("\x00"))
46655
46689
  return { error: "The path has characters that cannot be shown as typed; use plain characters." };
46656
- if (path4.isAbsolute(given) || path4.win32.isAbsolute(given))
46690
+ if (path5.isAbsolute(given) || path5.win32.isAbsolute(given))
46657
46691
  return { error: "Give a path relative to the repository root." };
46658
46692
  if (given.split(/[\\/]/).some((p) => p !== "." && p !== ".." && /[. ]$/.test(p)))
46659
46693
  return { error: "A name on the path ends with a dot or a space; drop it." };
46660
46694
  if (!/\.md$/i.test(given))
46661
46695
  return { error: "Give a Markdown file, ending in .md." };
46662
- const target = path4.resolve(root, given);
46696
+ const target = path5.resolve(root, given);
46663
46697
  const inside = (base, p) => {
46664
- const r = path4.relative(base, p);
46665
- return r !== "" && r !== ".." && !r.startsWith(`..${path4.sep}`) && !path4.isAbsolute(r);
46698
+ const r = path5.relative(base, p);
46699
+ return r !== "" && r !== ".." && !r.startsWith(`..${path5.sep}`) && !path5.isAbsolute(r);
46666
46700
  };
46667
46701
  if (!inside(root, target))
46668
46702
  return { error: "The path leaves the repository." };
46669
46703
  const at = (p) => {
46670
46704
  try {
46671
- return fs4.lstatSync(p);
46705
+ return fs5.lstatSync(p);
46672
46706
  } catch {
46673
46707
  return null;
46674
46708
  }
@@ -46682,17 +46716,17 @@ function exportPath(root, given) {
46682
46716
  return {
46683
46717
  error: "The file has more than one name (a hard link), so writing it could change a file elsewhere."
46684
46718
  };
46685
- let existing = path4.dirname(target);
46686
- const rest = [path4.basename(target)];
46719
+ let existing = path5.dirname(target);
46720
+ const rest = [path5.basename(target)];
46687
46721
  let found = at(existing);
46688
46722
  while (!found) {
46689
- rest.unshift(path4.basename(existing));
46690
- existing = path4.dirname(existing);
46723
+ rest.unshift(path5.basename(existing));
46724
+ existing = path5.dirname(existing);
46691
46725
  found = at(existing);
46692
46726
  }
46693
46727
  let folder;
46694
46728
  try {
46695
- folder = fs4.statSync(existing).isDirectory();
46729
+ folder = fs5.statSync(existing).isDirectory();
46696
46730
  } catch {
46697
46731
  folder = false;
46698
46732
  }
@@ -46700,21 +46734,21 @@ function exportPath(root, given) {
46700
46734
  return { error: "A part of the path is a file, not a folder." };
46701
46735
  let real;
46702
46736
  try {
46703
- real = self ? fs4.realpathSync.native(target) : path4.join(fs4.realpathSync.native(existing), ...rest);
46737
+ real = self ? fs5.realpathSync.native(target) : path5.join(fs5.realpathSync.native(existing), ...rest);
46704
46738
  } catch {
46705
46739
  return { error: "A folder on the path cannot be resolved." };
46706
46740
  }
46707
- const rootReal = fs4.realpathSync.native(root);
46741
+ const rootReal = fs5.realpathSync.native(root);
46708
46742
  if (!inside(rootReal, real))
46709
46743
  return { error: "The path leads outside the repository." };
46710
46744
  const inGit = (relative) => relative.toLowerCase().split(/[\\/]/).includes(".git");
46711
- if (inGit(path4.relative(root, target)) || inGit(path4.relative(rootReal, real)))
46745
+ if (inGit(path5.relative(root, target)) || inGit(path5.relative(rootReal, real)))
46712
46746
  return { error: "The path is inside Git's own folder." };
46713
- if (instructionFile(path4.relative(root, target)) || instructionFile(path4.relative(rootReal, real)))
46747
+ if (instructionFile(path5.relative(root, target)) || instructionFile(path5.relative(rootReal, real)))
46714
46748
  return {
46715
46749
  error: "The path is a file agents load as instructions (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md, or under .claude, .agents, .codex, .cursor)."
46716
46750
  };
46717
- return { relative: path4.relative(root, target).split(path4.sep).join("/"), exists: Boolean(self) };
46751
+ return { relative: path5.relative(root, target).split(path5.sep).join("/"), exists: Boolean(self) };
46718
46752
  }
46719
46753
  var exportReply = (where, document) => `Write to ${where.relative}, ${where.exists ? "replacing an existing file (show the owner its content first)" : "a new file"}. The rest of this reply, from the next line to the end, is the whole file, unchanged.
46720
46754
  ${document}`;
@@ -46791,7 +46825,7 @@ var HOSTS = ["claude-code", "codex"];
46791
46825
  var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), host, externalId);
46792
46826
 
46793
46827
  // server/src/overview.ts
46794
- import path5 from "node:path";
46828
+ import path6 from "node:path";
46795
46829
  var OVERVIEW_LIMITS = { records: 50, key: 200, text: 300, paths: 520, heading: 120 };
46796
46830
  var PROJECT_WIDE = "Project-wide (no code location)";
46797
46831
  function pathList(paths) {
@@ -46819,7 +46853,7 @@ async function liveOverview(db, projectId, after) {
46819
46853
  for (const r of rows.slice(0, OVERVIEW_LIMITS.records)) {
46820
46854
  const paths = [...new Set(anchors.filter((a) => a.unit_id === r.id).map((a) => a.path))];
46821
46855
  const first = paths[0];
46822
- const dir = first === undefined ? null : path5.posix.dirname(first);
46856
+ const dir = first === undefined ? null : path6.posix.dirname(first);
46823
46857
  const line = `- ${head(inline(r.key), OVERVIEW_LIMITS.key)} (u${r.id}, ${r.kind}${r.stance ? ` ${r.stance}` : ""}): ${head(inline(r.text), OVERVIEW_LIMITS.text)}${paths.length ? ` [${pathList(paths)}]` : ""}`;
46824
46858
  shown.push({
46825
46859
  id: r.id,
@@ -46979,23 +47013,6 @@ async function successor(db, id) {
46979
47013
  return end;
46980
47014
  }
46981
47015
 
46982
- // server/src/plugin.ts
46983
- import fs5 from "node:fs";
46984
- import path6 from "node:path";
46985
- import { fileURLToPath } from "node:url";
46986
- var MANIFEST = path6.join(".claude-plugin", "plugin.json");
46987
- function versionAt(root) {
46988
- try {
46989
- const m = JSON.parse(fs5.readFileSync(path6.join(root, MANIFEST), "utf8"));
46990
- return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
46991
- } catch {
46992
- return null;
46993
- }
46994
- }
46995
- var here = path6.dirname(fileURLToPath(import.meta.url));
46996
- var ROOT = [path6.join(here, ".."), path6.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path6.join(here, "..");
46997
- var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
46998
-
46999
47016
  // server/src/project.ts
47000
47017
  import { execFileSync as execFileSync2 } from "node:child_process";
47001
47018
  import fs6 from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
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
  "license": "MIT",
6
6
  "type": "module",
@@ -44,7 +44,7 @@ the same refs again later, and it only finishes the cleanup.
44
44
  ## What stays
45
45
 
46
46
  - A record's own text. If a record repeats the forgotten words, they stay in it; the preview lists the records to look at
47
- - Copies outside the database: capture's waiting and set-aside files under the Sphica home, and backups
47
+ - Copies outside the database: capture's waiting and set-aside files under the Sphica home, and the backups `sphica init` made before migrating (the preview says where they are, for the owner to delete)
48
48
  - The same words brought in again from somewhere new (a different pull request, a changed file). The same item fetched again is not stored
49
49
 
50
50
  ## Records are not instructions