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
|
@@ -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.
|
|
4
|
+
"version": "0.6.13",
|
|
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",
|
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
|
|
6
|
-
import
|
|
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
|
|
10811
|
+
import fs2 from "node:fs";
|
|
10812
10812
|
import os from "node:os";
|
|
10813
|
-
import
|
|
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/
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
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/
|
|
10913
|
-
import {
|
|
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
|
|
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 =
|
|
11315
|
-
raw.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
|
|
11342
|
-
import
|
|
11377
|
+
import fs3 from "node:fs";
|
|
11378
|
+
import path4 from "node:path";
|
|
11343
11379
|
|
|
11344
11380
|
// server/src/anchors.ts
|
|
11345
|
-
import
|
|
11381
|
+
import path3 from "node:path";
|
|
11346
11382
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
11347
|
-
var leaves = (rel) => rel === ".." || rel.startsWith(`..${
|
|
11383
|
+
var leaves = (rel) => rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel);
|
|
11348
11384
|
|
|
11349
11385
|
// server/src/project.ts
|
|
11350
|
-
var localFile = () =>
|
|
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 =
|
|
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]) => !
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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 =
|
|
11417
|
-
const rel =
|
|
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(
|
|
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
|
|
11436
|
-
import
|
|
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 =
|
|
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 = () =>
|
|
11494
|
-
var stateFile = () =>
|
|
11495
|
-
var rejectedDir = () =>
|
|
11496
|
-
var unregisteredDir = () =>
|
|
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 = () =>
|
|
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 =
|
|
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
|
-
|
|
11590
|
+
fs5.rmSync(path6.join(held, f), { force: true });
|
|
11555
11591
|
}
|
|
11556
11592
|
function spool(record) {
|
|
11557
11593
|
const dir = spoolDir();
|
|
11558
|
-
|
|
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 =
|
|
11561
|
-
|
|
11562
|
-
|
|
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 =
|
|
11567
|
-
const gitdir =
|
|
11568
|
-
const h =
|
|
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 (!
|
|
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) =>
|
|
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(
|
|
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
|
-
|
|
11666
|
+
fs5.mkdirSync(path6.dirname(file), { recursive: true, mode: 448 });
|
|
11631
11667
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
11632
|
-
|
|
11633
|
-
|
|
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 =
|
|
11675
|
+
files = fs5.readdirSync(baselineDir());
|
|
11640
11676
|
} catch {
|
|
11641
11677
|
return;
|
|
11642
11678
|
}
|
|
11643
11679
|
for (const f of files) {
|
|
11644
|
-
const file =
|
|
11645
|
-
if ((
|
|
11646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
11776
|
-
|
|
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
|
-
|
|
11815
|
+
fs5.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
|
|
11780
11816
|
return () => {
|
|
11781
11817
|
try {
|
|
11782
|
-
if (
|
|
11783
|
-
|
|
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 =
|
|
11826
|
+
const st = fs5.statSync(file, { throwIfNoEntry: false });
|
|
11791
11827
|
if (!st)
|
|
11792
11828
|
continue;
|
|
11793
|
-
const holder = Number(
|
|
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
|
-
|
|
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
|
|
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(
|
|
11937
|
+
r = current(JSON.parse(fs5.readFileSync(path6.join(from, name), "utf8")));
|
|
11902
11938
|
} catch {
|
|
11903
|
-
|
|
11904
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11969
|
+
fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
|
|
11934
11970
|
for (const x of bad) {
|
|
11935
11971
|
try {
|
|
11936
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|