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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/capture.js +215 -179
- package/dist/cli.js +3722 -3648
- package/dist/deliver.js +164 -126
- package/dist/mcp-record.js +260 -221
- package/dist/mcp.js +81 -64
- package/package.json +1 -1
- package/skills/forget/SKILL.md +1 -1
package/dist/mcp.js
CHANGED
|
@@ -45997,21 +45997,49 @@ function adapt(raw) {
|
|
|
45997
45997
|
}
|
|
45998
45998
|
|
|
45999
45999
|
// server/src/sqlite.ts
|
|
46000
|
-
import
|
|
46000
|
+
import fs2 from "node:fs";
|
|
46001
46001
|
import os from "node:os";
|
|
46002
|
-
import
|
|
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 ||
|
|
46007
|
-
var dbFile = () => process.env.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 (!
|
|
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
|
|
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
|
|
46103
|
-
import
|
|
46136
|
+
import fs5 from "node:fs";
|
|
46137
|
+
import path5 from "node:path";
|
|
46104
46138
|
|
|
46105
46139
|
// server/src/anchors.ts
|
|
46106
|
-
import
|
|
46107
|
-
import
|
|
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(`..${
|
|
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 =
|
|
46113
|
-
if (leaves(
|
|
46146
|
+
const abs = path3.join(root, rel);
|
|
46147
|
+
if (leaves(path3.relative(root, abs)))
|
|
46114
46148
|
return;
|
|
46115
|
-
const st =
|
|
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 =
|
|
46155
|
+
const inside = path3.relative(fs3.realpathSync(root), fs3.realpathSync(abs));
|
|
46122
46156
|
if (leaves(inside))
|
|
46123
46157
|
return;
|
|
46124
|
-
const buf =
|
|
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(
|
|
46176
|
+
if (leaves(path3.relative(root, path3.join(root, rel))))
|
|
46143
46177
|
return "unknown";
|
|
46144
46178
|
let realRoot;
|
|
46145
46179
|
try {
|
|
46146
|
-
realRoot =
|
|
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 =
|
|
46186
|
+
at = path3.join(at, part);
|
|
46153
46187
|
let st;
|
|
46154
46188
|
try {
|
|
46155
|
-
st =
|
|
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(
|
|
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
|
|
46358
|
-
import
|
|
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 =
|
|
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 =
|
|
46405
|
-
if (leaves(
|
|
46438
|
+
const abs = path4.join(root, rel);
|
|
46439
|
+
if (leaves(path4.relative(root, abs)))
|
|
46406
46440
|
return;
|
|
46407
46441
|
try {
|
|
46408
|
-
const st =
|
|
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(
|
|
46447
|
+
if (leaves(path4.relative(realRoot, fs4.realpathSync(abs))))
|
|
46414
46448
|
return;
|
|
46415
|
-
const buf =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
46696
|
+
const target = path5.resolve(root, given);
|
|
46663
46697
|
const inside = (base, p) => {
|
|
46664
|
-
const r =
|
|
46665
|
-
return r !== "" && r !== ".." && !r.startsWith(`..${
|
|
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
|
|
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 =
|
|
46686
|
-
const rest = [
|
|
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(
|
|
46690
|
-
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 =
|
|
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 ?
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
|
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 :
|
|
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.
|
|
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",
|
package/skills/forget/SKILL.md
CHANGED
|
@@ -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
|