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