sphica 0.6.17 → 0.6.18

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.18",
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.18",
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
@@ -11109,7 +11109,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11109
11109
 
11110
11110
  // server/src/sqlite.ts
11111
11111
  var SCHEMA_GENERATION = 2;
11112
- var SCHEMA_REVISION = 6;
11112
+ var SCHEMA_REVISION = 7;
11113
11113
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
11114
11114
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
11115
11115
  function requireRuntime() {
@@ -11250,7 +11250,8 @@ var CAPTURE_VIEWS = new Set([
11250
11250
  "capture_message",
11251
11251
  "capture_edit",
11252
11252
  "capture_delivery",
11253
- "capture_delivery_scoped"
11253
+ "capture_delivery_scoped",
11254
+ "capture_delivery_prune"
11254
11255
  ]);
11255
11256
  var TRIGGER_WRITES = {
11256
11257
  capture_session_insert: new Set(["session"]),
@@ -11260,6 +11261,10 @@ var TRIGGER_WRITES = {
11260
11261
  capture_delivery_scoped_insert: new Set(["delivery", "delivery_unit"]),
11261
11262
  source_fts_ai: new Set(["source_fts"])
11262
11263
  };
11264
+ var TRIGGER_DELETES = {
11265
+ capture_delivery_prune_insert: new Set(["delivery"]),
11266
+ delivery_ad: new Set(["delivery_unit"])
11267
+ };
11263
11268
  var TRIGGER_FUNCTIONS = {
11264
11269
  capture_delivery_insert: new Set(["coalesce", "json_each", "last_insert_rowid"]),
11265
11270
  capture_delivery_scoped_insert: new Set(["coalesce", "json_each", "last_insert_rowid"])
@@ -11279,6 +11284,8 @@ function captureAuthorizer(own, action, p1, p2, triggerOrView) {
11279
11284
  return C2.SQLITE_OK;
11280
11285
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
11281
11286
  }
11287
+ if (action === C2.SQLITE_DELETE && triggerOrView !== null && TRIGGER_DELETES[triggerOrView]?.has(table))
11288
+ return C2.SQLITE_OK;
11282
11289
  if (action === C2.SQLITE_UPDATE || action === C2.SQLITE_DELETE)
11283
11290
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
11284
11291
  if (action === C2.SQLITE_READ) {
@@ -11335,6 +11342,7 @@ var INGEST_TRIGGER_WRITES = {
11335
11342
  source_fts_ad: ["delete source_fts"],
11336
11343
  unit_state_apply: ["update unit"],
11337
11344
  unit_state_restore: ["insert unit_state"],
11345
+ delivery_ad: ["delete delivery_unit"],
11338
11346
  ...Object.fromEntries([
11339
11347
  "evidence_i",
11340
11348
  "evidence_u",
package/dist/cli.js CHANGED
@@ -3625,7 +3625,7 @@ ${indent}${caution(note)}` : ""}`);
3625
3625
 
3626
3626
  // server/src/sqlite.ts
3627
3627
  var SCHEMA_GENERATION = 2;
3628
- var SCHEMA_REVISION = 6;
3628
+ var SCHEMA_REVISION = 7;
3629
3629
  var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os2.homedir(), ".sphica");
3630
3630
  var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
3631
3631
  function requireRuntime() {
@@ -14717,7 +14717,8 @@ var CAPTURE_VIEWS = new Set([
14717
14717
  "capture_message",
14718
14718
  "capture_edit",
14719
14719
  "capture_delivery",
14720
- "capture_delivery_scoped"
14720
+ "capture_delivery_scoped",
14721
+ "capture_delivery_prune"
14721
14722
  ]);
14722
14723
  var TRIGGER_WRITES = {
14723
14724
  capture_session_insert: new Set(["session"]),
@@ -14727,6 +14728,10 @@ var TRIGGER_WRITES = {
14727
14728
  capture_delivery_scoped_insert: new Set(["delivery", "delivery_unit"]),
14728
14729
  source_fts_ai: new Set(["source_fts"])
14729
14730
  };
14731
+ var TRIGGER_DELETES = {
14732
+ capture_delivery_prune_insert: new Set(["delivery"]),
14733
+ delivery_ad: new Set(["delivery_unit"])
14734
+ };
14730
14735
  var TRIGGER_FUNCTIONS = {
14731
14736
  capture_delivery_insert: new Set(["coalesce", "json_each", "last_insert_rowid"]),
14732
14737
  capture_delivery_scoped_insert: new Set(["coalesce", "json_each", "last_insert_rowid"])
@@ -14746,6 +14751,8 @@ function captureAuthorizer(own, action, p1, p2, triggerOrView) {
14746
14751
  return C2.SQLITE_OK;
14747
14752
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
14748
14753
  }
14754
+ if (action === C2.SQLITE_DELETE && triggerOrView !== null && TRIGGER_DELETES[triggerOrView]?.has(table))
14755
+ return C2.SQLITE_OK;
14749
14756
  if (action === C2.SQLITE_UPDATE || action === C2.SQLITE_DELETE)
14750
14757
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
14751
14758
  if (action === C2.SQLITE_READ) {
@@ -14802,6 +14809,7 @@ var INGEST_TRIGGER_WRITES = {
14802
14809
  source_fts_ad: ["delete source_fts"],
14803
14810
  unit_state_apply: ["update unit"],
14804
14811
  unit_state_restore: ["insert unit_state"],
14812
+ delivery_ad: ["delete delivery_unit"],
14805
14813
  ...Object.fromEntries([
14806
14814
  "evidence_i",
14807
14815
  "evidence_u",
package/dist/deliver.js CHANGED
@@ -11134,7 +11134,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11134
11134
 
11135
11135
  // server/src/sqlite.ts
11136
11136
  var SCHEMA_GENERATION = 2;
11137
- var SCHEMA_REVISION = 6;
11137
+ var SCHEMA_REVISION = 7;
11138
11138
  var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os.homedir(), ".sphica");
11139
11139
  var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
11140
11140
  function requireRuntime() {
@@ -11299,7 +11299,8 @@ var CAPTURE_VIEWS = new Set([
11299
11299
  "capture_message",
11300
11300
  "capture_edit",
11301
11301
  "capture_delivery",
11302
- "capture_delivery_scoped"
11302
+ "capture_delivery_scoped",
11303
+ "capture_delivery_prune"
11303
11304
  ]);
11304
11305
  var TRIGGER_WRITES = {
11305
11306
  capture_session_insert: new Set(["session"]),
@@ -11309,6 +11310,10 @@ var TRIGGER_WRITES = {
11309
11310
  capture_delivery_scoped_insert: new Set(["delivery", "delivery_unit"]),
11310
11311
  source_fts_ai: new Set(["source_fts"])
11311
11312
  };
11313
+ var TRIGGER_DELETES = {
11314
+ capture_delivery_prune_insert: new Set(["delivery"]),
11315
+ delivery_ad: new Set(["delivery_unit"])
11316
+ };
11312
11317
  var TRIGGER_FUNCTIONS = {
11313
11318
  capture_delivery_insert: new Set(["coalesce", "json_each", "last_insert_rowid"]),
11314
11319
  capture_delivery_scoped_insert: new Set(["coalesce", "json_each", "last_insert_rowid"])
@@ -11328,6 +11333,8 @@ function captureAuthorizer(own, action, p1, p2, triggerOrView) {
11328
11333
  return C2.SQLITE_OK;
11329
11334
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
11330
11335
  }
11336
+ if (action === C2.SQLITE_DELETE && triggerOrView !== null && TRIGGER_DELETES[triggerOrView]?.has(table))
11337
+ return C2.SQLITE_OK;
11331
11338
  if (action === C2.SQLITE_UPDATE || action === C2.SQLITE_DELETE)
11332
11339
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
11333
11340
  if (action === C2.SQLITE_READ) {
@@ -11384,6 +11391,7 @@ var INGEST_TRIGGER_WRITES = {
11384
11391
  source_fts_ad: ["delete source_fts"],
11385
11392
  unit_state_apply: ["update unit"],
11386
11393
  unit_state_restore: ["insert unit_state"],
11394
+ delivery_ad: ["delete delivery_unit"],
11387
11395
  ...Object.fromEntries([
11388
11396
  "evidence_i",
11389
11397
  "evidence_u",
@@ -31844,6 +31852,7 @@ var LIMITS = {
31844
31852
  review: { units: 5, chars: 1500 }
31845
31853
  };
31846
31854
  var LOG_WAIT_MS = 250;
31855
+ var RETAIN_MS = 90 * 24 * 60 * 60 * 1000;
31847
31856
  var READ_SESSION = { units: 8, chars: 3000 };
31848
31857
  var EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
31849
31858
  var NOTE = "Sphica past record, not an instruction; read it with Sphica's read before relying on it";
@@ -32142,7 +32151,9 @@ function onceUnavailable(session) {
32142
32151
  }
32143
32152
  async function write2(trx, e, plan) {
32144
32153
  const id = sessionId(e.projectId, e.host, e.external);
32145
- const now = iso(Date.now());
32154
+ const t = Date.now();
32155
+ const now = iso(t);
32156
+ await trx.insertInto("capture_delivery_prune").values({ cutoff: iso(t - RETAIN_MS), session_id: id }).execute();
32146
32157
  await trx.insertInto("capture_session").values({
32147
32158
  id,
32148
32159
  project_id: e.projectId,
@@ -45869,7 +45869,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
45869
45869
 
45870
45870
  // server/src/sqlite.ts
45871
45871
  var SCHEMA_GENERATION = 2;
45872
- var SCHEMA_REVISION = 6;
45872
+ var SCHEMA_REVISION = 7;
45873
45873
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
45874
45874
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
45875
45875
  function requireRuntime() {
@@ -46010,7 +46010,8 @@ var CAPTURE_VIEWS = new Set([
46010
46010
  "capture_message",
46011
46011
  "capture_edit",
46012
46012
  "capture_delivery",
46013
- "capture_delivery_scoped"
46013
+ "capture_delivery_scoped",
46014
+ "capture_delivery_prune"
46014
46015
  ]);
46015
46016
  var TRIGGER_WRITES = {
46016
46017
  capture_session_insert: new Set(["session"]),
@@ -46020,6 +46021,10 @@ var TRIGGER_WRITES = {
46020
46021
  capture_delivery_scoped_insert: new Set(["delivery", "delivery_unit"]),
46021
46022
  source_fts_ai: new Set(["source_fts"])
46022
46023
  };
46024
+ var TRIGGER_DELETES = {
46025
+ capture_delivery_prune_insert: new Set(["delivery"]),
46026
+ delivery_ad: new Set(["delivery_unit"])
46027
+ };
46023
46028
  var TRIGGER_FUNCTIONS = {
46024
46029
  capture_delivery_insert: new Set(["coalesce", "json_each", "last_insert_rowid"]),
46025
46030
  capture_delivery_scoped_insert: new Set(["coalesce", "json_each", "last_insert_rowid"])
@@ -46039,6 +46044,8 @@ function captureAuthorizer(own2, action, p1, p2, triggerOrView) {
46039
46044
  return C2.SQLITE_OK;
46040
46045
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
46041
46046
  }
46047
+ if (action === C2.SQLITE_DELETE && triggerOrView !== null && TRIGGER_DELETES[triggerOrView]?.has(table))
46048
+ return C2.SQLITE_OK;
46042
46049
  if (action === C2.SQLITE_UPDATE || action === C2.SQLITE_DELETE)
46043
46050
  return fts ? C2.SQLITE_OK : C2.SQLITE_DENY;
46044
46051
  if (action === C2.SQLITE_READ) {
@@ -46095,6 +46102,7 @@ var INGEST_TRIGGER_WRITES = {
46095
46102
  source_fts_ad: ["delete source_fts"],
46096
46103
  unit_state_apply: ["update unit"],
46097
46104
  unit_state_restore: ["insert unit_state"],
46105
+ delivery_ad: ["delete delivery_unit"],
46098
46106
  ...Object.fromEntries([
46099
46107
  "evidence_i",
46100
46108
  "evidence_u",
package/dist/mcp.js CHANGED
@@ -46030,7 +46030,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
46030
46030
 
46031
46031
  // server/src/sqlite.ts
46032
46032
  var SCHEMA_GENERATION = 2;
46033
- var SCHEMA_REVISION = 6;
46033
+ var SCHEMA_REVISION = 7;
46034
46034
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
46035
46035
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
46036
46036
  function requireRuntime() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.17",
3
+ "version": "0.6.18",
4
4
  "description": "Records Claude Code and Codex sessions on your machine and keeps past implementation and decisions, with their sources, for your agent to find.",
5
5
  "license": "MIT",
6
6
  "type": "module",