sphica 0.6.17 → 0.6.20

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.
@@ -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.17",
4
+ "version": "0.6.20",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.17",
3
+ "version": "0.6.20",
4
4
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
5
5
  "author": {
6
6
  "name": "iroha924",
@@ -0,0 +1,49 @@
1
+ -- Revision 6 → 7 of generation 2: capture prunes the deliveries of sessions idle past a cutoff, so delivery_unit loses its cascade from
2
+ -- delivery (capture may not delete through one) and delivery_ad removes the units. Runs in one transaction with foreign keys off; every
3
+ -- statement matches db/schema.sql at revision 7, and server/test/migrate.test.ts compares the two.
4
+
5
+ drop trigger capture_delivery_insert;
6
+ drop trigger capture_delivery_scoped_insert;
7
+ create table delivery_unit_new (
8
+ delivery_id integer not null references delivery (id),
9
+ unit_id integer not null references unit (id) on delete cascade,
10
+ primary key (delivery_id, unit_id)
11
+ ) strict;
12
+ insert into delivery_unit_new (delivery_id, unit_id) select delivery_id, unit_id from delivery_unit;
13
+ drop table delivery_unit;
14
+ alter table delivery_unit_new rename to delivery_unit;
15
+ create index delivery_unit_unit on delivery_unit (unit_id);
16
+ create trigger delivery_ad after delete on delivery begin
17
+ delete from delivery_unit where delivery_id = old.id;
18
+ end;
19
+ create index delivery_at on delivery (at);
20
+ create trigger capture_delivery_insert instead of insert on capture_delivery begin
21
+ select raise(abort, 'the unit and the delivered session belong to different projects')
22
+ where exists (select 1 from json_each(coalesce(new.units, '[]')) j
23
+ where (select project_id from unit where id = j.value) is not (select project_id from session where id = new.session_id));
24
+ insert into delivery (session_id, event, outcome, reason, path, eligible, omitted, chars, at)
25
+ values (new.session_id, new.event, new.outcome, new.reason, new.path, coalesce(new.eligible, 0), coalesce(new.omitted, 0),
26
+ coalesce(new.chars, 0), new.at);
27
+ insert into delivery_unit (delivery_id, unit_id)
28
+ select last_insert_rowid(), j.value from json_each(coalesce(new.units, '[]')) j where true on conflict do nothing;
29
+ end;
30
+ create trigger capture_delivery_scoped_insert instead of insert on capture_delivery_scoped begin
31
+ select raise(abort, 'the unit and the delivered session belong to different projects')
32
+ where exists (select 1 from json_each(coalesce(new.units, '[]')) j
33
+ where (select project_id from unit where id = j.value) is not (select project_id from session where id = new.session_id));
34
+ insert into delivery (session_id, agent_id, event, outcome, reason, path, eligible, omitted, chars, at)
35
+ values (new.session_id, new.agent_id, new.event, new.outcome, new.reason, new.path, coalesce(new.eligible, 0),
36
+ coalesce(new.omitted, 0), coalesce(new.chars, 0), new.at);
37
+ insert into delivery_unit (delivery_id, unit_id)
38
+ select last_insert_rowid(), j.value from json_each(coalesce(new.units, '[]')) j where true on conflict do nothing;
39
+ end;
40
+ create view capture_delivery_prune as select null as cutoff, null as session_id;
41
+ create trigger capture_delivery_prune_insert instead of insert on capture_delivery_prune begin
42
+ delete from delivery where session_id = new.session_id
43
+ and not exists (select 1 from delivery n where n.session_id = new.session_id and n.at >= new.cutoff);
44
+ delete from delivery where id in (select d.id from delivery d where d.at < new.cutoff
45
+ and (d.session_id is null or not exists (select 1 from delivery n where n.session_id = d.session_id and n.at >= new.cutoff))
46
+ order by d.at, d.id limit 200);
47
+ end;
48
+
49
+ pragma user_version = 7;
package/db/schema.sql CHANGED
@@ -950,12 +950,17 @@ create table delivery (
950
950
  at text not null check (strftime('%Y-%m-%dT%H:%M:%fZ', at) is at)
951
951
  , agent_id text check (length(agent_id) between 1 and 200)) strict;
952
952
  create index delivery_session on delivery (session_id, at);
953
+ create index delivery_at on delivery (at);
954
+ -- A delivery's units go by delivery_ad, not a cascade: capture prunes deliveries, and its authorizer passes a delete only from a named trigger
953
955
  create table delivery_unit (
954
- delivery_id integer not null references delivery (id) on delete cascade,
956
+ delivery_id integer not null references delivery (id),
955
957
  unit_id integer not null references unit (id) on delete cascade,
956
958
  primary key (delivery_id, unit_id)
957
959
  ) strict;
958
960
  create index delivery_unit_unit on delivery_unit (unit_id);
961
+ create trigger delivery_ad after delete on delivery begin
962
+ delete from delivery_unit where delivery_id = old.id;
963
+ end;
959
964
 
960
965
  -- The one way the record server writes a source. It has no session columns, so it can never write a session message: those are the
961
966
  -- owner's own words, and only capture writes them. A view insert returns no id; the writer looks the row up by its item key
@@ -1039,5 +1044,15 @@ create trigger capture_delivery_scoped_insert instead of insert on capture_deliv
1039
1044
  insert into delivery_unit (delivery_id, unit_id)
1040
1045
  select last_insert_rowid(), j.value from json_each(coalesce(new.units, '[]')) j where true on conflict do nothing;
1041
1046
  end;
1047
+ -- Deletes the deliveries of sessions whose last delivery is before cutoff: all of session_id's, then up to 200 of the oldest others (a row
1048
+ -- without a session goes by its own time). Capture inserts here before each delivery it logs, naming that delivery's session
1049
+ create view capture_delivery_prune as select null as cutoff, null as session_id;
1050
+ create trigger capture_delivery_prune_insert instead of insert on capture_delivery_prune begin
1051
+ delete from delivery where session_id = new.session_id
1052
+ and not exists (select 1 from delivery n where n.session_id = new.session_id and n.at >= new.cutoff);
1053
+ delete from delivery where id in (select d.id from delivery d where d.at < new.cutoff
1054
+ and (d.session_id is null or not exists (select 1 from delivery n where n.session_id = d.session_id and n.at >= new.cutoff))
1055
+ order by d.at, d.id limit 200);
1056
+ end;
1042
1057
 
1043
- pragma user_version = 6;
1058
+ pragma user_version = 7;
package/dist/capture.js CHANGED
@@ -10900,6 +10900,7 @@ var QUESTION = new Set([
10900
10900
  ...["add", "get", "make", "have", "has", "not", "no", "yes", "reason"],
10901
10901
  ...["理由", "仕組", "何", "方", "場合", "今", "件"]
10902
10902
  ]);
10903
+ var FOLDED_QUESTION = new Set([...QUESTION].flatMap((w) => terms(w)));
10903
10904
  var sha256 = (s) => crypto.createHash("sha256").update(s).digest();
10904
10905
  function uuidFrom(...parts) {
10905
10906
  const b = crypto.createHash("sha256").update(parts.join("\x00")).digest().subarray(0, 16);
@@ -11109,7 +11110,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11109
11110
 
11110
11111
  // server/src/sqlite.ts
11111
11112
  var SCHEMA_GENERATION = 2;
11112
- var SCHEMA_REVISION = 6;
11113
+ var SCHEMA_REVISION = 7;
11113
11114
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
11114
11115
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
11115
11116
  function requireRuntime() {
@@ -11250,7 +11251,8 @@ var CAPTURE_VIEWS = new Set([
11250
11251
  "capture_message",
11251
11252
  "capture_edit",
11252
11253
  "capture_delivery",
11253
- "capture_delivery_scoped"
11254
+ "capture_delivery_scoped",
11255
+ "capture_delivery_prune"
11254
11256
  ]);
11255
11257
  var TRIGGER_WRITES = {
11256
11258
  capture_session_insert: new Set(["session"]),
@@ -11260,6 +11262,10 @@ var TRIGGER_WRITES = {
11260
11262
  capture_delivery_scoped_insert: new Set(["delivery", "delivery_unit"]),
11261
11263
  source_fts_ai: new Set(["source_fts"])
11262
11264
  };
11265
+ var TRIGGER_DELETES = {
11266
+ capture_delivery_prune_insert: new Set(["delivery"]),
11267
+ delivery_ad: new Set(["delivery_unit"])
11268
+ };
11263
11269
  var TRIGGER_FUNCTIONS = {
11264
11270
  capture_delivery_insert: new Set(["coalesce", "json_each", "last_insert_rowid"]),
11265
11271
  capture_delivery_scoped_insert: new Set(["coalesce", "json_each", "last_insert_rowid"])
@@ -11279,6 +11285,8 @@ function captureAuthorizer(own, action, p1, p2, triggerOrView) {
11279
11285
  return C2.SQLITE_OK;
11280
11286
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
11281
11287
  }
11288
+ if (action === C2.SQLITE_DELETE && triggerOrView !== null && TRIGGER_DELETES[triggerOrView]?.has(table))
11289
+ return C2.SQLITE_OK;
11282
11290
  if (action === C2.SQLITE_UPDATE || action === C2.SQLITE_DELETE)
11283
11291
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
11284
11292
  if (action === C2.SQLITE_READ) {
@@ -11335,6 +11343,7 @@ var INGEST_TRIGGER_WRITES = {
11335
11343
  source_fts_ad: ["delete source_fts"],
11336
11344
  unit_state_apply: ["update unit"],
11337
11345
  unit_state_restore: ["insert unit_state"],
11346
+ delivery_ad: ["delete delivery_unit"],
11338
11347
  ...Object.fromEntries([
11339
11348
  "evidence_i",
11340
11349
  "evidence_u",