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/deliver.js
CHANGED
|
@@ -15,9 +15,9 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// server/src/deliver.ts
|
|
18
|
-
import
|
|
18
|
+
import fs7 from "node:fs";
|
|
19
19
|
import os2 from "node:os";
|
|
20
|
-
import
|
|
20
|
+
import path8 from "node:path";
|
|
21
21
|
|
|
22
22
|
// server/src/anchors.ts
|
|
23
23
|
import path from "node:path";
|
|
@@ -279,8 +279,8 @@ var leaves = (rel) => rel === ".." || rel.startsWith(`..${path.sep}`) || path.is
|
|
|
279
279
|
|
|
280
280
|
// server/src/capture.ts
|
|
281
281
|
import { spawn } from "node:child_process";
|
|
282
|
-
import
|
|
283
|
-
import
|
|
282
|
+
import fs5 from "node:fs";
|
|
283
|
+
import path6 from "node:path";
|
|
284
284
|
|
|
285
285
|
// server/node_modules/kysely/dist/util/object-utils.js
|
|
286
286
|
function isUndefined(obj) {
|
|
@@ -11085,21 +11085,65 @@ function adapt(raw) {
|
|
|
11085
11085
|
}
|
|
11086
11086
|
|
|
11087
11087
|
// server/src/sqlite.ts
|
|
11088
|
-
import
|
|
11088
|
+
import fs2 from "node:fs";
|
|
11089
11089
|
import os from "node:os";
|
|
11090
|
-
import
|
|
11090
|
+
import path3 from "node:path";
|
|
11091
11091
|
import { constants as C, DatabaseSync } from "node:sqlite";
|
|
11092
|
+
|
|
11093
|
+
// server/src/plugin.ts
|
|
11094
|
+
import fs from "node:fs";
|
|
11095
|
+
import path2 from "node:path";
|
|
11096
|
+
import { fileURLToPath } from "node:url";
|
|
11097
|
+
|
|
11098
|
+
// server/src/panel.ts
|
|
11099
|
+
import { stripVTControlCharacters, styleText } from "node:util";
|
|
11100
|
+
var title = (text) => `✦ ${text}`;
|
|
11101
|
+
var rule = (text) => text.split(`
|
|
11102
|
+
`).map((line) => line ? `│ ${line}` : "│").join(`
|
|
11103
|
+
`);
|
|
11104
|
+
var foot = (text) => `╰─ ${text}`;
|
|
11105
|
+
var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
|
|
11106
|
+
`);
|
|
11107
|
+
var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
|
|
11108
|
+
var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
|
|
11109
|
+
`).replace(/(?![\t\n])\p{Cc}/gu, ""));
|
|
11110
|
+
var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
|
|
11111
|
+
|
|
11112
|
+
// server/src/plugin.ts
|
|
11113
|
+
var MANIFEST = path2.join(".claude-plugin", "plugin.json");
|
|
11114
|
+
var PACKAGE = "package.json";
|
|
11115
|
+
function versionAt(root) {
|
|
11116
|
+
try {
|
|
11117
|
+
const m = JSON.parse(fs.readFileSync(path2.join(root, MANIFEST), "utf8"));
|
|
11118
|
+
return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
|
|
11119
|
+
} catch {
|
|
11120
|
+
return null;
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
11123
|
+
function packageVersionAt(root) {
|
|
11124
|
+
try {
|
|
11125
|
+
const m = JSON.parse(fs.readFileSync(path2.join(root, PACKAGE), "utf8"));
|
|
11126
|
+
return m.name === "sphica" && typeof m.version === "string" ? m.version : null;
|
|
11127
|
+
} catch {
|
|
11128
|
+
return null;
|
|
11129
|
+
}
|
|
11130
|
+
}
|
|
11131
|
+
var here = path2.dirname(fileURLToPath(import.meta.url));
|
|
11132
|
+
var ROOT = [path2.join(here, ".."), path2.join(here, "..", "..", "plugin")].find((r) => versionAt(r) !== null) ?? path2.join(here, "..");
|
|
11133
|
+
var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
|
|
11134
|
+
|
|
11135
|
+
// server/src/sqlite.ts
|
|
11092
11136
|
var SCHEMA_GENERATION = 2;
|
|
11093
11137
|
var SCHEMA_REVISION = 4;
|
|
11094
|
-
var sphicaHome = () => process.env.SPHICA_HOME ||
|
|
11095
|
-
var dbFile = () => process.env.SPHICA_DB ||
|
|
11138
|
+
var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os.homedir(), ".sphica");
|
|
11139
|
+
var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
|
|
11096
11140
|
function requireRuntime() {
|
|
11097
11141
|
const proto = DatabaseSync.prototype;
|
|
11098
11142
|
if (typeof proto.setAuthorizer !== "function" || typeof proto.enableDefensive !== "function")
|
|
11099
11143
|
throw new Error(`Sphica needs Node 24.15 or later (this is ${process.version}). Upgrade Node.`);
|
|
11100
11144
|
}
|
|
11101
11145
|
function requireFile(file) {
|
|
11102
|
-
if (!
|
|
11146
|
+
if (!fs2.existsSync(file))
|
|
11103
11147
|
throw new Error(`No database at ${file}. Create it with \`sphica init\`.`);
|
|
11104
11148
|
}
|
|
11105
11149
|
function prepare(raw, check) {
|
|
@@ -11114,7 +11158,13 @@ function prepare(raw, check) {
|
|
|
11114
11158
|
const got = raw.prepare("pragma user_version").get()?.user_version;
|
|
11115
11159
|
if (got === SCHEMA_REVISION)
|
|
11116
11160
|
return;
|
|
11117
|
-
throw new
|
|
11161
|
+
throw new RevisionMismatch(got ?? 0);
|
|
11162
|
+
}
|
|
11163
|
+
|
|
11164
|
+
class RevisionMismatch extends Error {
|
|
11165
|
+
constructor(got) {
|
|
11166
|
+
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."));
|
|
11167
|
+
}
|
|
11118
11168
|
}
|
|
11119
11169
|
function generationOf(raw) {
|
|
11120
11170
|
const has = raw.prepare("select 1 from sqlite_schema where type = 'table' and name = 'sphica_generation'").get();
|
|
@@ -11351,7 +11401,7 @@ function connectWriter(role, file = dbFile(), create = false) {
|
|
|
11351
11401
|
raw.setAuthorizer((action, p1, p2, _db, triggerOrView) => captureAuthorizer(own, action, p1, p2, triggerOrView));
|
|
11352
11402
|
const prepare2 = raw.prepare.bind(raw);
|
|
11353
11403
|
const exec = raw.exec.bind(raw);
|
|
11354
|
-
const
|
|
11404
|
+
const mark2 = (f) => (...a) => {
|
|
11355
11405
|
own = true;
|
|
11356
11406
|
try {
|
|
11357
11407
|
return f(...a);
|
|
@@ -11359,8 +11409,8 @@ function connectWriter(role, file = dbFile(), create = false) {
|
|
|
11359
11409
|
own = false;
|
|
11360
11410
|
}
|
|
11361
11411
|
};
|
|
11362
|
-
raw.prepare =
|
|
11363
|
-
raw.exec =
|
|
11412
|
+
raw.prepare = mark2(prepare2);
|
|
11413
|
+
raw.exec = mark2(exec);
|
|
11364
11414
|
}
|
|
11365
11415
|
return raw;
|
|
11366
11416
|
}
|
|
@@ -11371,25 +11421,11 @@ function openWriter(role, file = dbFile()) {
|
|
|
11371
11421
|
// server/src/knowledge.ts
|
|
11372
11422
|
var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), host, externalId);
|
|
11373
11423
|
|
|
11374
|
-
// server/src/panel.ts
|
|
11375
|
-
import { stripVTControlCharacters, styleText } from "node:util";
|
|
11376
|
-
var title = (text) => `✦ ${text}`;
|
|
11377
|
-
var rule = (text) => text.split(`
|
|
11378
|
-
`).map((line) => line ? `│ ${line}` : "│").join(`
|
|
11379
|
-
`);
|
|
11380
|
-
var foot = (text) => `╰─ ${text}`;
|
|
11381
|
-
var panel = (head2, lines, end) => [title(head2), ...lines.map(rule), foot(end)].join(`
|
|
11382
|
-
`);
|
|
11383
|
-
var STRING_SEQUENCE = /\u001b[\]P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g;
|
|
11384
|
-
var plain2 = (s) => visible(stripVTControlCharacters(s.replace(STRING_SEQUENCE, "")).replace(/\r\n?|[\v\f\u0085\p{Zl}\p{Zp}]/gu, `
|
|
11385
|
-
`).replace(/(?![\t\n])\p{Cc}/gu, ""));
|
|
11386
|
-
var inline = (s) => plain2(s).replace(/[\n\t]+/g, " ");
|
|
11387
|
-
|
|
11388
11424
|
// server/src/project.ts
|
|
11389
11425
|
import { execFileSync } from "node:child_process";
|
|
11390
|
-
import
|
|
11391
|
-
import
|
|
11392
|
-
var localFile = () =>
|
|
11426
|
+
import fs3 from "node:fs";
|
|
11427
|
+
import path4 from "node:path";
|
|
11428
|
+
var localFile = () => path4.join(sphicaHome(), "projects.json");
|
|
11393
11429
|
var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
|
|
11394
11430
|
function normalizeRemote(url) {
|
|
11395
11431
|
const raw = String(url ?? "").trim();
|
|
@@ -11422,7 +11458,7 @@ var git = (dir, ...args) => {
|
|
|
11422
11458
|
function localMap() {
|
|
11423
11459
|
let raw;
|
|
11424
11460
|
try {
|
|
11425
|
-
raw =
|
|
11461
|
+
raw = fs3.readFileSync(localFile(), "utf8");
|
|
11426
11462
|
} catch (e) {
|
|
11427
11463
|
if (e.code === "ENOENT")
|
|
11428
11464
|
return {};
|
|
@@ -11434,23 +11470,23 @@ function localMap() {
|
|
|
11434
11470
|
} catch {
|
|
11435
11471
|
m = null;
|
|
11436
11472
|
}
|
|
11437
|
-
if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !
|
|
11473
|
+
if (!m || typeof m !== "object" || Array.isArray(m) || Object.entries(m).some(([root, name]) => !path4.isAbsolute(root) || typeof name !== "string" || !LOCAL_KEY.test(name)))
|
|
11438
11474
|
throw new Error(`${localFile()} is not a valid JSON project table. Fix or delete it, then name the project again.`);
|
|
11439
11475
|
return m;
|
|
11440
11476
|
}
|
|
11441
11477
|
function identify(dir) {
|
|
11442
|
-
const given =
|
|
11478
|
+
const given = path4.resolve(dir);
|
|
11443
11479
|
const top = git(given, "rev-parse", "--show-toplevel");
|
|
11444
11480
|
const root = top || given;
|
|
11445
11481
|
const remote = top ? normalizeRemote(git(root, "remote", "get-url", "origin")) : null;
|
|
11446
11482
|
if (remote)
|
|
11447
11483
|
return { key: `git:${remote}`, root, name: remote.split("/").slice(1).join("/") || remote };
|
|
11448
11484
|
const map = localMap();
|
|
11449
|
-
for (let d = root;; d =
|
|
11485
|
+
for (let d = root;; d = path4.dirname(d)) {
|
|
11450
11486
|
const local = map[d];
|
|
11451
11487
|
if (local && LOCAL_KEY.test(local))
|
|
11452
11488
|
return { key: `local:${local}`, root: d, name: local };
|
|
11453
|
-
if (top ||
|
|
11489
|
+
if (top || path4.dirname(d) === d)
|
|
11454
11490
|
return null;
|
|
11455
11491
|
}
|
|
11456
11492
|
}
|
|
@@ -11459,11 +11495,11 @@ async function projectId(db, key) {
|
|
|
11459
11495
|
return r?.id ?? null;
|
|
11460
11496
|
}
|
|
11461
11497
|
function relativeTo(root, file, cwd = root) {
|
|
11462
|
-
const abs =
|
|
11463
|
-
const rel =
|
|
11498
|
+
const abs = path4.resolve(cwd, file);
|
|
11499
|
+
const rel = path4.relative(root, abs);
|
|
11464
11500
|
if (!rel || leaves(rel))
|
|
11465
11501
|
return null;
|
|
11466
|
-
return rel.split(
|
|
11502
|
+
return rel.split(path4.sep).join("/");
|
|
11467
11503
|
}
|
|
11468
11504
|
function patchPaths(patch) {
|
|
11469
11505
|
const out = [];
|
|
@@ -11478,8 +11514,8 @@ function patchPaths(patch) {
|
|
|
11478
11514
|
|
|
11479
11515
|
// server/src/worktree.ts
|
|
11480
11516
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
11481
|
-
import
|
|
11482
|
-
import
|
|
11517
|
+
import fs4 from "node:fs";
|
|
11518
|
+
import path5 from "node:path";
|
|
11483
11519
|
var MAX_PATHS = 200;
|
|
11484
11520
|
var git2 = (root, args) => {
|
|
11485
11521
|
try {
|
|
@@ -11513,7 +11549,7 @@ function snapshot(root) {
|
|
|
11513
11549
|
continue;
|
|
11514
11550
|
let st;
|
|
11515
11551
|
try {
|
|
11516
|
-
st =
|
|
11552
|
+
st = fs4.lstatSync(path5.join(root, p), { throwIfNoEntry: false });
|
|
11517
11553
|
} catch {
|
|
11518
11554
|
st = undefined;
|
|
11519
11555
|
}
|
|
@@ -11536,13 +11572,13 @@ function changed(root, before, after) {
|
|
|
11536
11572
|
}
|
|
11537
11573
|
|
|
11538
11574
|
// server/src/capture.ts
|
|
11539
|
-
var spoolDir = () =>
|
|
11540
|
-
var stateFile = () =>
|
|
11541
|
-
var rejectedDir = () =>
|
|
11542
|
-
var unregisteredDir = () =>
|
|
11575
|
+
var spoolDir = () => path6.join(sphicaHome(), "spool");
|
|
11576
|
+
var stateFile = () => path6.join(sphicaHome(), "capture.json");
|
|
11577
|
+
var rejectedDir = () => path6.join(spoolDir(), "rejected");
|
|
11578
|
+
var unregisteredDir = () => path6.join(spoolDir(), "unregistered");
|
|
11543
11579
|
var HOLD_DAYS = 30;
|
|
11544
11580
|
var HOLD_MAX = 1000;
|
|
11545
|
-
var baselineDir = () =>
|
|
11581
|
+
var baselineDir = () => path6.join(sphicaHome(), "worktree");
|
|
11546
11582
|
function current(raw) {
|
|
11547
11583
|
const r = raw;
|
|
11548
11584
|
if (r.v === 2)
|
|
@@ -11589,7 +11625,7 @@ ${z}`,
|
|
|
11589
11625
|
function prune(held) {
|
|
11590
11626
|
let files;
|
|
11591
11627
|
try {
|
|
11592
|
-
files =
|
|
11628
|
+
files = fs5.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
|
|
11593
11629
|
} catch {
|
|
11594
11630
|
return;
|
|
11595
11631
|
}
|
|
@@ -11597,21 +11633,21 @@ function prune(held) {
|
|
|
11597
11633
|
const stale = files.filter((f) => Number(f.split("-")[0]) < cutoff);
|
|
11598
11634
|
const over = files.slice(0, Math.max(0, files.length - HOLD_MAX));
|
|
11599
11635
|
for (const f of new Set([...stale, ...over]))
|
|
11600
|
-
|
|
11636
|
+
fs5.rmSync(path6.join(held, f), { force: true });
|
|
11601
11637
|
}
|
|
11602
11638
|
function spool(record) {
|
|
11603
11639
|
const dir = spoolDir();
|
|
11604
|
-
|
|
11640
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
11605
11641
|
const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
11606
|
-
const tmp =
|
|
11607
|
-
|
|
11608
|
-
|
|
11642
|
+
const tmp = path6.join(dir, `.${name}`);
|
|
11643
|
+
fs5.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
|
|
11644
|
+
fs5.renameSync(tmp, path6.join(dir, name));
|
|
11609
11645
|
}
|
|
11610
11646
|
var branchOf = (root) => {
|
|
11611
11647
|
try {
|
|
11612
|
-
const dotgit =
|
|
11613
|
-
const gitdir =
|
|
11614
|
-
const h =
|
|
11648
|
+
const dotgit = path6.join(root, ".git");
|
|
11649
|
+
const gitdir = fs5.statSync(dotgit).isFile() ? path6.resolve(root, fs5.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
|
|
11650
|
+
const h = fs5.readFileSync(path6.join(gitdir, "HEAD"), "utf8").trim();
|
|
11615
11651
|
return h.startsWith("ref: refs/heads/") ? h.slice("ref: refs/heads/".length) : null;
|
|
11616
11652
|
} catch {
|
|
11617
11653
|
return null;
|
|
@@ -11655,7 +11691,7 @@ Notes: ${notes.trim()}` : "";
|
|
|
11655
11691
|
};
|
|
11656
11692
|
}
|
|
11657
11693
|
function captureNotice(file = dbFile()) {
|
|
11658
|
-
if (!
|
|
11694
|
+
if (!fs5.existsSync(file))
|
|
11659
11695
|
return panel("sphica: no database, so conversations are not recorded", [file], "Create it with sphica init");
|
|
11660
11696
|
const s = readState();
|
|
11661
11697
|
if (s.stuck)
|
|
@@ -11664,32 +11700,32 @@ function captureNotice(file = dbFile()) {
|
|
|
11664
11700
|
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");
|
|
11665
11701
|
return null;
|
|
11666
11702
|
}
|
|
11667
|
-
var baselineFile = (host, session) =>
|
|
11703
|
+
var baselineFile = (host, session) => path6.join(baselineDir(), `${digest(`${host}\x00${session}`)}.json`);
|
|
11668
11704
|
function readBaseline(file) {
|
|
11669
11705
|
try {
|
|
11670
|
-
return JSON.parse(
|
|
11706
|
+
return JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
11671
11707
|
} catch {
|
|
11672
11708
|
return null;
|
|
11673
11709
|
}
|
|
11674
11710
|
}
|
|
11675
11711
|
function writeBaseline(file, b) {
|
|
11676
|
-
|
|
11712
|
+
fs5.mkdirSync(path6.dirname(file), { recursive: true, mode: 448 });
|
|
11677
11713
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
11678
|
-
|
|
11679
|
-
|
|
11714
|
+
fs5.writeFileSync(tmp, JSON.stringify(b), { mode: 384 });
|
|
11715
|
+
fs5.renameSync(tmp, file);
|
|
11680
11716
|
}
|
|
11681
11717
|
function pruneBaselines() {
|
|
11682
11718
|
const cutoff = Date.now() - HOLD_DAYS * 24 * 60 * 60 * 1000;
|
|
11683
11719
|
let files;
|
|
11684
11720
|
try {
|
|
11685
|
-
files =
|
|
11721
|
+
files = fs5.readdirSync(baselineDir());
|
|
11686
11722
|
} catch {
|
|
11687
11723
|
return;
|
|
11688
11724
|
}
|
|
11689
11725
|
for (const f of files) {
|
|
11690
|
-
const file =
|
|
11691
|
-
if ((
|
|
11692
|
-
|
|
11726
|
+
const file = path6.join(baselineDir(), f);
|
|
11727
|
+
if ((fs5.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) < cutoff)
|
|
11728
|
+
fs5.rmSync(file, { force: true });
|
|
11693
11729
|
}
|
|
11694
11730
|
}
|
|
11695
11731
|
function onHook(host, input) {
|
|
@@ -11700,7 +11736,7 @@ function onHook(host, input) {
|
|
|
11700
11736
|
return { flush: false };
|
|
11701
11737
|
const file = process.env.CLAUDE_ENV_FILE;
|
|
11702
11738
|
if (file && input.session_id && /^[A-Za-z0-9_-]+$/.test(input.session_id)) {
|
|
11703
|
-
|
|
11739
|
+
fs5.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
|
|
11704
11740
|
`);
|
|
11705
11741
|
}
|
|
11706
11742
|
const place2 = identify(input.cwd ?? process.cwd());
|
|
@@ -11786,13 +11822,13 @@ function onHook(host, input) {
|
|
|
11786
11822
|
}
|
|
11787
11823
|
function writeState(s) {
|
|
11788
11824
|
try {
|
|
11789
|
-
|
|
11825
|
+
fs5.writeFileSync(stateFile(), JSON.stringify(s));
|
|
11790
11826
|
} catch {}
|
|
11791
11827
|
}
|
|
11792
11828
|
function readState() {
|
|
11793
11829
|
const count = (dir) => {
|
|
11794
11830
|
try {
|
|
11795
|
-
return
|
|
11831
|
+
return fs5.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
|
|
11796
11832
|
} catch {
|
|
11797
11833
|
return 0;
|
|
11798
11834
|
}
|
|
@@ -11804,7 +11840,7 @@ function readState() {
|
|
|
11804
11840
|
};
|
|
11805
11841
|
let raw = {};
|
|
11806
11842
|
try {
|
|
11807
|
-
const parsed = JSON.parse(
|
|
11843
|
+
const parsed = JSON.parse(fs5.readFileSync(stateFile(), "utf8"));
|
|
11808
11844
|
if (parsed && typeof parsed === "object")
|
|
11809
11845
|
raw = parsed;
|
|
11810
11846
|
} catch {}
|
|
@@ -11818,25 +11854,25 @@ function readState() {
|
|
|
11818
11854
|
};
|
|
11819
11855
|
}
|
|
11820
11856
|
function lock() {
|
|
11821
|
-
const file =
|
|
11822
|
-
|
|
11857
|
+
const file = path6.join(spoolDir(), ".lock");
|
|
11858
|
+
fs5.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
|
|
11823
11859
|
for (let attempt = 0;attempt < 2; attempt++) {
|
|
11824
11860
|
try {
|
|
11825
|
-
|
|
11861
|
+
fs5.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
|
|
11826
11862
|
return () => {
|
|
11827
11863
|
try {
|
|
11828
|
-
if (
|
|
11829
|
-
|
|
11864
|
+
if (fs5.readFileSync(file, "utf8") === String(process.pid))
|
|
11865
|
+
fs5.rmSync(file, { force: true });
|
|
11830
11866
|
} catch {}
|
|
11831
11867
|
};
|
|
11832
11868
|
} catch (e) {
|
|
11833
11869
|
if (e.code !== "EEXIST")
|
|
11834
11870
|
throw e;
|
|
11835
11871
|
}
|
|
11836
|
-
const st =
|
|
11872
|
+
const st = fs5.statSync(file, { throwIfNoEntry: false });
|
|
11837
11873
|
if (!st)
|
|
11838
11874
|
continue;
|
|
11839
|
-
const holder = Number(
|
|
11875
|
+
const holder = Number(fs5.readFileSync(file, "utf8") || 0);
|
|
11840
11876
|
const fresh = Date.now() - st.mtimeMs < 5 * 60000;
|
|
11841
11877
|
const alive = (() => {
|
|
11842
11878
|
if (holder <= 0)
|
|
@@ -11849,7 +11885,7 @@ function lock() {
|
|
|
11849
11885
|
})();
|
|
11850
11886
|
if (alive && fresh)
|
|
11851
11887
|
return null;
|
|
11852
|
-
|
|
11888
|
+
fs5.rmSync(file, { force: true });
|
|
11853
11889
|
}
|
|
11854
11890
|
return null;
|
|
11855
11891
|
}
|
|
@@ -11932,7 +11968,7 @@ var REJECTED = new Set([18, 19, 20, 25]);
|
|
|
11932
11968
|
var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
|
|
11933
11969
|
var queued = (from) => {
|
|
11934
11970
|
try {
|
|
11935
|
-
return
|
|
11971
|
+
return fs5.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
|
|
11936
11972
|
} catch {
|
|
11937
11973
|
return [];
|
|
11938
11974
|
}
|
|
@@ -11943,16 +11979,16 @@ async function sendBatch(db, names) {
|
|
|
11943
11979
|
for (const { name, from } of names) {
|
|
11944
11980
|
let r;
|
|
11945
11981
|
try {
|
|
11946
|
-
r = current(JSON.parse(
|
|
11982
|
+
r = current(JSON.parse(fs5.readFileSync(path6.join(from, name), "utf8")));
|
|
11947
11983
|
} catch {
|
|
11948
|
-
|
|
11949
|
-
|
|
11984
|
+
fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
|
|
11985
|
+
fs5.renameSync(path6.join(from, name), path6.join(rejectedDir(), name));
|
|
11950
11986
|
continue;
|
|
11951
11987
|
}
|
|
11952
11988
|
if (r)
|
|
11953
11989
|
records.push({ name, from, r });
|
|
11954
11990
|
else
|
|
11955
|
-
|
|
11991
|
+
fs5.rmSync(path6.join(from, name), { force: true });
|
|
11956
11992
|
}
|
|
11957
11993
|
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 }]));
|
|
11958
11994
|
const known = records.filter((x) => projects.has(x.r.project));
|
|
@@ -11975,10 +12011,10 @@ async function sendBatch(db, names) {
|
|
|
11975
12011
|
}
|
|
11976
12012
|
}
|
|
11977
12013
|
if (bad.length) {
|
|
11978
|
-
|
|
12014
|
+
fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
|
|
11979
12015
|
for (const x of bad) {
|
|
11980
12016
|
try {
|
|
11981
|
-
|
|
12017
|
+
fs5.renameSync(path6.join(x.from, x.name), path6.join(rejectedDir(), x.name));
|
|
11982
12018
|
} catch (e) {
|
|
11983
12019
|
if (e.code !== "ENOENT")
|
|
11984
12020
|
throw e;
|
|
@@ -11986,12 +12022,12 @@ async function sendBatch(db, names) {
|
|
|
11986
12022
|
}
|
|
11987
12023
|
}
|
|
11988
12024
|
if (strayed.length) {
|
|
11989
|
-
|
|
12025
|
+
fs5.mkdirSync(held, { recursive: true, mode: 448 });
|
|
11990
12026
|
for (const x of strayed) {
|
|
11991
12027
|
if (x.from === held)
|
|
11992
12028
|
continue;
|
|
11993
12029
|
try {
|
|
11994
|
-
|
|
12030
|
+
fs5.renameSync(path6.join(x.from, x.name), path6.join(held, x.name));
|
|
11995
12031
|
} catch (e) {
|
|
11996
12032
|
if (e.code !== "ENOENT")
|
|
11997
12033
|
throw e;
|
|
@@ -12001,7 +12037,7 @@ async function sendBatch(db, names) {
|
|
|
12001
12037
|
const moved = new Set([...bad, ...strayed].map((x) => x.name));
|
|
12002
12038
|
for (const x of records)
|
|
12003
12039
|
if (!moved.has(x.name))
|
|
12004
|
-
|
|
12040
|
+
fs5.rmSync(path6.join(x.from, x.name), { force: true });
|
|
12005
12041
|
return { sent, rejected: bad.length };
|
|
12006
12042
|
}
|
|
12007
12043
|
async function flush(file = dbFile(), budgetMs = FLUSH_BUDGET_MS) {
|
|
@@ -12901,10 +12937,10 @@ function mergeDefs(...defs) {
|
|
|
12901
12937
|
function cloneDef(schema) {
|
|
12902
12938
|
return mergeDefs(schema._zod.def);
|
|
12903
12939
|
}
|
|
12904
|
-
function getElementAtPath(obj,
|
|
12905
|
-
if (!
|
|
12940
|
+
function getElementAtPath(obj, path7) {
|
|
12941
|
+
if (!path7)
|
|
12906
12942
|
return obj;
|
|
12907
|
-
return
|
|
12943
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
12908
12944
|
}
|
|
12909
12945
|
function promiseAllObject(promisesObj) {
|
|
12910
12946
|
const keys = Object.keys(promisesObj);
|
|
@@ -13240,11 +13276,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
13240
13276
|
}
|
|
13241
13277
|
return false;
|
|
13242
13278
|
}
|
|
13243
|
-
function prefixIssues(
|
|
13279
|
+
function prefixIssues(path7, issues) {
|
|
13244
13280
|
return issues.map((iss) => {
|
|
13245
13281
|
var _a5;
|
|
13246
13282
|
(_a5 = iss).path ?? (_a5.path = []);
|
|
13247
|
-
iss.path.unshift(
|
|
13283
|
+
iss.path.unshift(path7);
|
|
13248
13284
|
return iss;
|
|
13249
13285
|
});
|
|
13250
13286
|
}
|
|
@@ -13694,16 +13730,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
|
|
|
13694
13730
|
}
|
|
13695
13731
|
function formatError(error, mapper = (issue2) => issue2.message) {
|
|
13696
13732
|
const fieldErrors = { _errors: [] };
|
|
13697
|
-
const processError = (error2,
|
|
13733
|
+
const processError = (error2, path7 = []) => {
|
|
13698
13734
|
for (const issue2 of error2.issues) {
|
|
13699
13735
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
13700
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
13736
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
13701
13737
|
} else if (issue2.code === "invalid_key") {
|
|
13702
|
-
processError({ issues: issue2.issues }, [...
|
|
13738
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
13703
13739
|
} else if (issue2.code === "invalid_element") {
|
|
13704
|
-
processError({ issues: issue2.issues }, [...
|
|
13740
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
13705
13741
|
} else {
|
|
13706
|
-
const fullpath = [...
|
|
13742
|
+
const fullpath = [...path7, ...issue2.path];
|
|
13707
13743
|
if (fullpath.length === 0) {
|
|
13708
13744
|
fieldErrors._errors.push(mapper(issue2));
|
|
13709
13745
|
} else {
|
|
@@ -13742,17 +13778,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
|
|
|
13742
13778
|
}
|
|
13743
13779
|
function treeifyError(error, mapper = (issue2) => issue2.message) {
|
|
13744
13780
|
const result = { errors: [] };
|
|
13745
|
-
const processError = (error2,
|
|
13781
|
+
const processError = (error2, path7 = []) => {
|
|
13746
13782
|
var _a6;
|
|
13747
13783
|
for (const issue2 of error2.issues) {
|
|
13748
13784
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
13749
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
13785
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
13750
13786
|
} else if (issue2.code === "invalid_key") {
|
|
13751
|
-
processError({ issues: issue2.issues }, [...
|
|
13787
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
13752
13788
|
} else if (issue2.code === "invalid_element") {
|
|
13753
|
-
processError({ issues: issue2.issues }, [...
|
|
13789
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
13754
13790
|
} else {
|
|
13755
|
-
const fullpath = [...
|
|
13791
|
+
const fullpath = [...path7, ...issue2.path];
|
|
13756
13792
|
if (fullpath.length === 0) {
|
|
13757
13793
|
result.errors.push(mapper(issue2));
|
|
13758
13794
|
continue;
|
|
@@ -13791,8 +13827,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
|
|
|
13791
13827
|
}
|
|
13792
13828
|
function toDotPath(_path) {
|
|
13793
13829
|
const segs = [];
|
|
13794
|
-
const
|
|
13795
|
-
for (const seg of
|
|
13830
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
13831
|
+
for (const seg of path7) {
|
|
13796
13832
|
if (typeof seg === "number")
|
|
13797
13833
|
segs.push(`[${seg}]`);
|
|
13798
13834
|
else if (typeof seg === "symbol")
|
|
@@ -30613,13 +30649,13 @@ function resolveRef(ref, ctx) {
|
|
|
30613
30649
|
if (!ref.startsWith("#")) {
|
|
30614
30650
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
30615
30651
|
}
|
|
30616
|
-
const
|
|
30617
|
-
if (
|
|
30652
|
+
const path7 = ref.slice(1).split("/").filter(Boolean);
|
|
30653
|
+
if (path7.length === 0) {
|
|
30618
30654
|
return ctx.rootSchema;
|
|
30619
30655
|
}
|
|
30620
30656
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
30621
|
-
if (
|
|
30622
|
-
const key =
|
|
30657
|
+
if (path7[0] === defsKey) {
|
|
30658
|
+
const key = path7[1] === undefined ? undefined : decodeJSONPointerSegment(path7[1]);
|
|
30623
30659
|
if (!key || !ctx.defs[key]) {
|
|
30624
30660
|
throw new Error(`Reference not found: ${ref}`);
|
|
30625
30661
|
}
|
|
@@ -31486,11 +31522,11 @@ function gitPath(token, prefix) {
|
|
|
31486
31522
|
}
|
|
31487
31523
|
function parseDiff(text) {
|
|
31488
31524
|
const files = [];
|
|
31489
|
-
const at = (
|
|
31490
|
-
const found = files.find((f2) => f2.path ===
|
|
31525
|
+
const at = (path7) => {
|
|
31526
|
+
const found = files.find((f2) => f2.path === path7);
|
|
31491
31527
|
if (found)
|
|
31492
31528
|
return found;
|
|
31493
|
-
const f = { path:
|
|
31529
|
+
const f = { path: path7, added: [], lines: [] };
|
|
31494
31530
|
files.push(f);
|
|
31495
31531
|
return f;
|
|
31496
31532
|
};
|
|
@@ -31603,8 +31639,8 @@ var Findings = exports_external.array(Finding).max(MAX_FINDINGS);
|
|
|
31603
31639
|
|
|
31604
31640
|
// server/src/review-bridge.ts
|
|
31605
31641
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
31606
|
-
import
|
|
31607
|
-
import
|
|
31642
|
+
import fs6 from "node:fs";
|
|
31643
|
+
import path7 from "node:path";
|
|
31608
31644
|
var NAME = /^[\w.:/-]{1,100}$/;
|
|
31609
31645
|
var MAX_DIFF = 4 * 1024 * 1024;
|
|
31610
31646
|
var MAX_FILES = 500;
|
|
@@ -31684,10 +31720,10 @@ function localChange(root, args) {
|
|
|
31684
31720
|
for (const rel of untracked) {
|
|
31685
31721
|
let text = "";
|
|
31686
31722
|
try {
|
|
31687
|
-
const file2 =
|
|
31688
|
-
const st =
|
|
31723
|
+
const file2 = path7.join(root, rel);
|
|
31724
|
+
const st = fs6.lstatSync(file2);
|
|
31689
31725
|
if (st.isFile() && st.size <= MAX_UNTRACKED_BYTES)
|
|
31690
|
-
text =
|
|
31726
|
+
text = fs6.readFileSync(file2, "utf8");
|
|
31691
31727
|
} catch {}
|
|
31692
31728
|
const added = text ? text.split(/\r?\n/) : [];
|
|
31693
31729
|
files.push({ path: rel, added, lines: added.map((_, i) => i + 1) });
|
|
@@ -31854,15 +31890,15 @@ async function namedInCommand(db, projectId2, root, cwd, command) {
|
|
|
31854
31890
|
const edge = `\\s'"=(){}<>|;&,`;
|
|
31855
31891
|
const esc2 = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31856
31892
|
const forms = (p) => {
|
|
31857
|
-
const abs =
|
|
31858
|
-
const fromCwd =
|
|
31893
|
+
const abs = path8.join(root, p);
|
|
31894
|
+
const fromCwd = path8.relative(cwd, abs);
|
|
31859
31895
|
const out = new Set;
|
|
31860
31896
|
for (const f of [p, abs, leaves(fromCwd) ? "" : fromCwd])
|
|
31861
31897
|
if (f)
|
|
31862
31898
|
for (const sep of ["/", "\\"]) {
|
|
31863
31899
|
const t = f.split(/[\\/]/).join(sep);
|
|
31864
31900
|
out.add(t);
|
|
31865
|
-
if (!
|
|
31901
|
+
if (!path8.isAbsolute(f))
|
|
31866
31902
|
out.add(`.${sep}${t}`);
|
|
31867
31903
|
}
|
|
31868
31904
|
return [...out];
|
|
@@ -31916,7 +31952,7 @@ async function waiting(db, projectId2, place) {
|
|
|
31916
31952
|
if (!n)
|
|
31917
31953
|
return "";
|
|
31918
31954
|
const day = new Date().toLocaleDateString("sv-SE");
|
|
31919
|
-
if (!markOnce("pending", `${
|
|
31955
|
+
if (!markOnce("pending", `${path8.resolve(place.file)}\x00${place.key}\x00${day}`))
|
|
31920
31956
|
return "";
|
|
31921
31957
|
const trace = place.host === "codex" ? "$sphica:trace" : "/sphica:trace";
|
|
31922
31958
|
return `- ${n} session${n === 1 ? "" : "s"} waiting to be traced: run ${trace} pending.`;
|
|
@@ -31999,10 +32035,10 @@ function markOnce(kind, key) {
|
|
|
31999
32035
|
return String(process.getuid?.() ?? "user");
|
|
32000
32036
|
}
|
|
32001
32037
|
})();
|
|
32002
|
-
const dir =
|
|
32038
|
+
const dir = path8.join(os2.tmpdir(), `sphica-${sha256(user).toString("hex").slice(0, 12)}`, kind);
|
|
32003
32039
|
try {
|
|
32004
|
-
|
|
32005
|
-
|
|
32040
|
+
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
32041
|
+
fs7.writeFileSync(path8.join(dir, sha256(key).toString("hex").slice(0, 24)), "", { flag: "wx" });
|
|
32006
32042
|
return true;
|
|
32007
32043
|
} catch (e) {
|
|
32008
32044
|
return e.code !== "EEXIST";
|
|
@@ -32039,7 +32075,7 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
|
|
|
32039
32075
|
const event = call ? "review" : name === "SessionStart" ? "session_start" : name === "UserPromptSubmit" ? "prompt" : name === "PreToolUse" ? input2.tool_name === "Read" || input2.tool_name === "Bash" && !(host === "codex" && shellPatch(input2)) ? "pre_read" : "pre_edit" : null;
|
|
32040
32076
|
if (!event || !input2.session_id)
|
|
32041
32077
|
return "";
|
|
32042
|
-
if (event === "prompt" && !isOwnerTurn(input2))
|
|
32078
|
+
if (event === "prompt" && !isOwnerTurn(input2, undefined, undefined, host === "codex" ? process.env.CODEX_THREAD_ID : undefined))
|
|
32043
32079
|
return "";
|
|
32044
32080
|
if (event === "review" && input2.agent_id)
|
|
32045
32081
|
return "";
|
|
@@ -32053,12 +32089,12 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
|
|
|
32053
32089
|
const place = identify(input2.cwd ?? process.cwd());
|
|
32054
32090
|
if (!place)
|
|
32055
32091
|
return "";
|
|
32056
|
-
let rels = targets.map((t) =>
|
|
32092
|
+
let rels = targets.map((t) => path8.relative(place.root, path8.resolve(input2.cwd ?? place.root, t))).filter((r) => r && !leaves(r)).map((r) => r.split(path8.sep).join("/"));
|
|
32057
32093
|
if (onPath && !shell && !rels.length)
|
|
32058
32094
|
return "";
|
|
32059
32095
|
let db = null;
|
|
32060
32096
|
try {
|
|
32061
|
-
if (!
|
|
32097
|
+
if (!fs7.existsSync(file2))
|
|
32062
32098
|
throw new Error(`no database at ${file2}`);
|
|
32063
32099
|
db = openReader(file2);
|
|
32064
32100
|
const pid = await projectId(db, place.key);
|
|
@@ -32085,6 +32121,8 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
|
|
|
32085
32121
|
await log(file2, pid, host, input2.session_id, event, plan, plan.text ? "emitted" : "nothing", branchOf(place.root)).catch(() => {});
|
|
32086
32122
|
return plan.text;
|
|
32087
32123
|
} catch (e) {
|
|
32124
|
+
if (e instanceof RevisionMismatch)
|
|
32125
|
+
return shell || !markOnce("revision", `${host}\x00${input2.session_id}`) ? "" : `Sphica unavailable: ${head(inline(e.message), 400)}`;
|
|
32088
32126
|
if (event === "prompt" || shell || !onceUnavailable(`${host}\x00${input2.session_id}`))
|
|
32089
32127
|
return "";
|
|
32090
32128
|
return `Sphica unavailable: ${head(inline(reason(e)), 200)}. Past decisions for ${onPath ? named(rels) : "this project"} could not be checked.`;
|