sphica 0.6.22 → 0.6.24

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.22",
4
+ "version": "0.6.24",
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.22",
3
+ "version": "0.6.24",
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,9 @@
1
+ -- Both full-text indexes must hold terms() output as of revision 9, which includes the parts of camelCase and snake_case names, so they
2
+ -- are rebuilt as `sphica doctor --reindex` does. Tables, views, and triggers are the same as at revision 8.
3
+
4
+ insert into unit_fts (unit_fts) values ('delete-all');
5
+ insert into unit_fts (rowid, body, ident, alias) select id, body, ident, alias from unit_search_text;
6
+ insert into source_fts (source_fts) values ('delete-all');
7
+ insert into source_fts (rowid, lexemes) select id, sphica_terms(text) from source where indexed = 1;
8
+
9
+ pragma user_version = 9;
package/db/schema.sql CHANGED
@@ -1070,4 +1070,4 @@ create trigger capture_delivery_prune_insert instead of insert on capture_delive
1070
1070
  order by d.at, d.id limit 200);
1071
1071
  end;
1072
1072
 
1073
- pragma user_version = 8;
1073
+ pragma user_version = 9;
package/dist/capture.js CHANGED
@@ -10827,6 +10827,12 @@ var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
10827
10827
  var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
10828
10828
  var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
10829
10829
  var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
10830
+ var WORD = /[\p{L}\p{M}\p{N}_]+/gu;
10831
+ var JAPANESE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー々]+/u;
10832
+ var COMPOUND = /^[A-Za-z0-9_]*(?:[A-Za-z0-9]_|_[A-Za-z0-9]|[a-z0-9][A-Z])[A-Za-z0-9_]*$/;
10833
+ var CUT = /_+|(?<=[a-z0-9])(?=[A-Z])/;
10834
+ var MAX_PARTS = 6;
10835
+ var NAME_PART = /^(?:[A-Za-z]+[0-9]*|[0-9]+)$/;
10830
10836
  var MAX_TERM = 100;
10831
10837
  var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
10832
10838
  function singular(w) {
@@ -10842,8 +10848,9 @@ function singular(w) {
10842
10848
  return w.slice(0, -1);
10843
10849
  return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
10844
10850
  }
10845
- function terms(text) {
10846
- const norm = text.normalize("NFKC").toLowerCase();
10851
+ function terms(text, parts = true) {
10852
+ const nfkc = text.normalize("NFKC");
10853
+ const norm = nfkc.toLowerCase();
10847
10854
  const out = [];
10848
10855
  const keep = (w) => {
10849
10856
  if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
@@ -10856,6 +10863,18 @@ function terms(text) {
10856
10863
  for (const m of norm.matchAll(IDENT))
10857
10864
  if (m[0].length >= 3)
10858
10865
  keep(m[0]);
10866
+ if (parts)
10867
+ for (const [word] of nfkc.matchAll(WORD))
10868
+ for (const w of word.split(JAPANESE)) {
10869
+ if (w.length > MAX_TERM || !COMPOUND.test(w))
10870
+ continue;
10871
+ const cut = w.split(CUT).filter(Boolean);
10872
+ if (cut.length <= MAX_PARTS && cut.every((p) => NAME_PART.test(p))) {
10873
+ for (const p of cut)
10874
+ if (p.length >= 2)
10875
+ keep(p.toLowerCase());
10876
+ }
10877
+ }
10859
10878
  return out.filter(Boolean);
10860
10879
  }
10861
10880
  var QUESTION = new Set([
@@ -11110,7 +11129,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11110
11129
 
11111
11130
  // server/src/sqlite.ts
11112
11131
  var SCHEMA_GENERATION = 2;
11113
- var SCHEMA_REVISION = 8;
11132
+ var SCHEMA_REVISION = 9;
11114
11133
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
11115
11134
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
11116
11135
  function requireRuntime() {
@@ -11671,7 +11690,7 @@ function fit(body) {
11671
11690
 
11672
11691
  ${z}`,
11673
11692
  truncated: true,
11674
- redacted: mask(start) !== start || mask(end) !== end,
11693
+ redacted: a !== head(start, KEEP) || z !== tail(end, KEEP),
11675
11694
  originalBytes: all
11676
11695
  };
11677
11696
  }
@@ -11711,9 +11730,11 @@ function isOwnerTurn(input, parent = process.env.SPHICA_PARENT_SESSION, entrypoi
11711
11730
  return false;
11712
11731
  if (codexParent && codexParent !== input.session_id)
11713
11732
  return false;
11733
+ if (entrypoint?.startsWith("sdk-"))
11734
+ return false;
11714
11735
  if (parent)
11715
11736
  return parent === input.session_id;
11716
- return entrypoint !== "sdk-cli";
11737
+ return true;
11717
11738
  }
11718
11739
  var INJECTED = [
11719
11740
  /^<(?:task-notification|channel|cross-session-message|teammate-message|agent-message|slack-ping|slack-tag-message|fetched-web-content|remote-review|remote-review-progress)[\s>]/,
package/dist/cli.js CHANGED
@@ -3016,6 +3016,12 @@ var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
3016
3016
  var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
3017
3017
  var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
3018
3018
  var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
3019
+ var WORD = /[\p{L}\p{M}\p{N}_]+/gu;
3020
+ var JAPANESE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー々]+/u;
3021
+ var COMPOUND = /^[A-Za-z0-9_]*(?:[A-Za-z0-9]_|_[A-Za-z0-9]|[a-z0-9][A-Z])[A-Za-z0-9_]*$/;
3022
+ var CUT = /_+|(?<=[a-z0-9])(?=[A-Z])/;
3023
+ var MAX_PARTS = 6;
3024
+ var NAME_PART = /^(?:[A-Za-z]+[0-9]*|[0-9]+)$/;
3019
3025
  var MAX_TERM = 100;
3020
3026
  var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
3021
3027
  function singular(w) {
@@ -3031,8 +3037,9 @@ function singular(w) {
3031
3037
  return w.slice(0, -1);
3032
3038
  return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
3033
3039
  }
3034
- function terms(text) {
3035
- const norm = text.normalize("NFKC").toLowerCase();
3040
+ function terms(text, parts = true) {
3041
+ const nfkc = text.normalize("NFKC");
3042
+ const norm = nfkc.toLowerCase();
3036
3043
  const out = [];
3037
3044
  const keep = (w) => {
3038
3045
  if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
@@ -3045,6 +3052,18 @@ function terms(text) {
3045
3052
  for (const m of norm.matchAll(IDENT))
3046
3053
  if (m[0].length >= 3)
3047
3054
  keep(m[0]);
3055
+ if (parts)
3056
+ for (const [word] of nfkc.matchAll(WORD))
3057
+ for (const w of word.split(JAPANESE)) {
3058
+ if (w.length > MAX_TERM || !COMPOUND.test(w))
3059
+ continue;
3060
+ const cut = w.split(CUT).filter(Boolean);
3061
+ if (cut.length <= MAX_PARTS && cut.every((p) => NAME_PART.test(p))) {
3062
+ for (const p of cut)
3063
+ if (p.length >= 2)
3064
+ keep(p.toLowerCase());
3065
+ }
3066
+ }
3048
3067
  return out.filter(Boolean);
3049
3068
  }
3050
3069
  var QUESTION = new Set([
@@ -3626,7 +3645,7 @@ ${indent}${caution(note)}` : ""}`);
3626
3645
 
3627
3646
  // server/src/sqlite.ts
3628
3647
  var SCHEMA_GENERATION = 2;
3629
- var SCHEMA_REVISION = 8;
3648
+ var SCHEMA_REVISION = 9;
3630
3649
  var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os2.homedir(), ".sphica");
3631
3650
  var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
3632
3651
  function requireRuntime() {
@@ -15082,7 +15101,7 @@ function fit(body) {
15082
15101
 
15083
15102
  ${z}`,
15084
15103
  truncated: true,
15085
- redacted: mask(start) !== start || mask(end) !== end,
15104
+ redacted: a2 !== head(start, KEEP) || z !== tail(end, KEEP),
15086
15105
  originalBytes: all
15087
15106
  };
15088
15107
  }
@@ -15122,9 +15141,11 @@ function isOwnerTurn(input, parent = process.env.SPHICA_PARENT_SESSION, entrypoi
15122
15141
  return false;
15123
15142
  if (codexParent && codexParent !== input.session_id)
15124
15143
  return false;
15144
+ if (entrypoint?.startsWith("sdk-"))
15145
+ return false;
15125
15146
  if (parent)
15126
15147
  return parent === input.session_id;
15127
- return entrypoint !== "sdk-cli";
15148
+ return true;
15128
15149
  }
15129
15150
  var INJECTED = [
15130
15151
  /^<(?:task-notification|channel|cross-session-message|teammate-message|agent-message|slack-ping|slack-tag-message|fetched-web-content|remote-review|remote-review-progress)[\s>]/,
@@ -16097,7 +16118,9 @@ var terms_golden_default = {
16097
16118
  "server/src/db-write.ts",
16098
16119
  "connectwriter",
16099
16120
  "ot-123",
16100
- "#201"
16121
+ "#201",
16122
+ "connect",
16123
+ "writer"
16101
16124
  ]
16102
16125
  },
16103
16126
  {
@@ -16110,21 +16133,39 @@ var terms_golden_default = {
16110
16133
  "name",
16111
16134
  "snake_case_name",
16112
16135
  "camelcasename",
16113
- "kebab-case-name"
16136
+ "kebab-case-name",
16137
+ "snake",
16138
+ "case",
16139
+ "name",
16140
+ "camel",
16141
+ "case",
16142
+ "name"
16114
16143
  ]
16115
16144
  },
16116
16145
  {
16117
16146
  text: "fullwidth ABC 123 と カタカナ",
16118
16147
  terms: ["fullwidth", "abc", "123", "カタカナ", "fullwidth", "abc", "123"]
16119
16148
  },
16120
- { text: "書きました 書きます 書く", terms: ["書", "書", "書"] },
16121
- { text: "emoji 🚀 と 絵文字 👍🏽 のあいだ", terms: ["emoji", "絵文字", "emoji"] },
16122
- { text: "é café と が(が)", terms: ["é", "café", "caf"] },
16149
+ {
16150
+ text: "書きました 書きます 書く",
16151
+ terms: ["書", "書", "書"]
16152
+ },
16153
+ {
16154
+ text: "emoji 🚀 と 絵文字 👍🏽 のあいだ",
16155
+ terms: ["emoji", "絵文字", "emoji"]
16156
+ },
16157
+ {
16158
+ text: "é café と が(が)",
16159
+ terms: ["é", "café", "caf"]
16160
+ },
16123
16161
  {
16124
16162
  text: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa base64 aGVsbG8gd29ybGQgaGVsbG8gd29ybGQ=",
16125
16163
  terms: ["base64", "agvsbg8gd29ybgqgagvsbg8gd29ybgq", "base64", "agvsbg8gd29ybgqgagvsbg8gd29ybgq"]
16126
16164
  },
16127
- { text: "the a an of to in is and or for on it be", terms: [] },
16165
+ {
16166
+ text: "the a an of to in is and or for on it be",
16167
+ terms: []
16168
+ },
16128
16169
  {
16129
16170
  text: "保存先は Postgres じゃなくて SQLite にしよう。各ユーザーに DB サーバーを立てさせたくない。",
16130
16171
  terms: [
@@ -16159,10 +16200,13 @@ var terms_golden_default = {
16159
16200
  "postgre"
16160
16201
  ]
16161
16202
  },
16162
- { text: "WAL モードも有効にしておいて。", terms: ["wal", "モード", "有効", "wal"] },
16203
+ {
16204
+ text: "WAL モードも有効にしておいて。",
16205
+ terms: ["wal", "モード", "有効", "wal"]
16206
+ },
16163
16207
  {
16164
16208
  text: "open() で journal_mode = wal を設定しました。",
16165
- terms: ["open", "journal_mode", "wal", "設定", "open", "journal_mode", "wal"]
16209
+ terms: ["open", "journal_mode", "wal", "設定", "open", "journal_mode", "wal", "journal", "mode"]
16166
16210
  },
16167
16211
  {
16168
16212
  text: "利用状況の送信(テレメトリ)は入れない。ローカルで完結させるのがこのアプリの売りだから。",
@@ -16186,15 +16230,24 @@ var terms_golden_default = {
16186
16230
  },
16187
16231
  {
16188
16232
  text: "fetchCover に最大 3 回の再試行を入れました。",
16189
- terms: ["fetchcover", "最大", "3", "回", "再", "試行", "入", "fetchcover"]
16233
+ terms: ["fetchcover", "最大", "3", "回", "再", "試行", "入", "fetchcover", "fetch", "cover"]
16234
+ },
16235
+ {
16236
+ text: "今日はここまで。お疲れさま。",
16237
+ terms: ["今日", "お疲れさま"]
16238
+ },
16239
+ {
16240
+ text: "お疲れさまでした。",
16241
+ terms: ["お疲れさま"]
16190
16242
  },
16191
- { text: "今日はここまで。お疲れさま。", terms: ["今日", "お疲れさま"] },
16192
- { text: "お疲れさまでした。", terms: ["お疲れさま"] },
16193
16243
  {
16194
16244
  text: "入力の検証に zod を使うのもありかもね。まだ決めないけど。",
16195
16245
  terms: ["入力", "検証", "zod", "使", "決", "zod"]
16196
16246
  },
16197
- { text: "候補として覚えておきます。", terms: ["候補", "覚"] },
16247
+ {
16248
+ text: "候補として覚えておきます。",
16249
+ terms: ["候補", "覚"]
16250
+ },
16198
16251
  {
16199
16252
  text: "Store every timestamp in UTC. Convert to local time only when showing it.",
16200
16253
  terms: [
@@ -16238,7 +16291,8 @@ var terms_golden_default = {
16238
16291
  "write",
16239
16292
  "iso",
16240
16293
  "8601",
16241
- "utc"
16294
+ "utc",
16295
+ "stored"
16242
16296
  ]
16243
16297
  },
16244
16298
  {
@@ -16282,7 +16336,9 @@ var terms_golden_default = {
16282
16336
  "there",
16283
16337
  "reverted",
16284
16338
  "main",
16285
- "thread"
16339
+ "thread",
16340
+ "offscreen",
16341
+ "canva"
16286
16342
  ]
16287
16343
  },
16288
16344
  {
@@ -16301,9 +16357,18 @@ var terms_golden_default = {
16301
16357
  text: "検索結果のキャッシュを Redis に置きます。",
16302
16358
  terms: ["検索", "結果", "キャッシュ", "redis", "置", "redis"]
16303
16359
  },
16304
- { text: "ログその1を受け取りました。", terms: ["ログ", "1", "受け取り"] },
16305
- { text: "ログその2を受け取りました。", terms: ["ログ", "2", "受け取り"] },
16306
- { text: "ログその3を受け取りました。", terms: ["ログ", "3", "受け取り"] },
16360
+ {
16361
+ text: "ログその1を受け取りました。",
16362
+ terms: ["ログ", "1", "受け取り"]
16363
+ },
16364
+ {
16365
+ text: "ログその2を受け取りました。",
16366
+ terms: ["ログ", "2", "受け取り"]
16367
+ },
16368
+ {
16369
+ text: "ログその3を受け取りました。",
16370
+ terms: ["ログ", "3", "受け取り"]
16371
+ },
16307
16372
  {
16308
16373
  text: "CSV export leaks private notes",
16309
16374
  terms: ["csv", "export", "leak", "private", "note", "csv", "export", "leak", "private", "note"]
@@ -16343,7 +16408,10 @@ var terms_golden_default = {
16343
16408
  "csv"
16344
16409
  ]
16345
16410
  },
16346
- { text: "Use pnpm for installs", terms: ["use", "pnpm", "install", "use", "pnpm", "install"] },
16411
+ {
16412
+ text: "Use pnpm for installs",
16413
+ terms: ["use", "pnpm", "install", "use", "pnpm", "install"]
16414
+ },
16347
16415
  {
16348
16416
  text: "Switch the lockfile to pnpm. npm install took minutes on CI.",
16349
16417
  terms: [
@@ -16421,7 +16489,10 @@ var terms_golden_default = {
16421
16489
  text: "Consider synchronous = OFF for speed.",
16422
16490
  terms: ["consider", "synchronous", "off", "speed", "consider", "synchronous", "off", "speed"]
16423
16491
  },
16424
- { text: "chore: switch to pnpm", terms: ["chore", "switch", "pnpm", "chore", "switch", "pnpm"] },
16492
+ {
16493
+ text: "chore: switch to pnpm",
16494
+ terms: ["chore", "switch", "pnpm", "chore", "switch", "pnpm"]
16495
+ },
16425
16496
  {
16426
16497
  text: "Exclude notes from CSV export",
16427
16498
  terms: ["exclude", "note", "from", "csv", "export", "exclude", "note", "from", "csv", "export"]
@@ -16444,7 +16515,8 @@ var terms_golden_default = {
16444
16515
  "write",
16445
16516
  "title",
16446
16517
  "author",
16447
- "only"
16518
+ "only",
16519
+ "csv"
16448
16520
  ]
16449
16521
  },
16450
16522
  {
@@ -16484,10 +16556,14 @@ var terms_golden_default = {
16484
16556
  "drop",
16485
16557
  "author",
16486
16558
  "from",
16487
- "export"
16559
+ "export",
16560
+ "csv"
16488
16561
  ]
16489
16562
  },
16490
- { text: "検索は前方一致にする", terms: ["検索", "前方", "一致"] },
16563
+ {
16564
+ text: "検索は前方一致にする",
16565
+ terms: ["検索", "前方", "一致"]
16566
+ },
16491
16567
  {
16492
16568
  text: "書名の検索は前方一致にする。全文検索はデータが 1 万冊を超えるまで見送る。",
16493
16569
  terms: ["書名", "検索", "前方", "一致", "全文", "検索", "データ", "1", "万", "冊", "超", "見送"]
package/dist/deliver.js CHANGED
@@ -28,6 +28,12 @@ var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
28
28
  var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
29
29
  var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
30
30
  var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
31
+ var WORD = /[\p{L}\p{M}\p{N}_]+/gu;
32
+ var JAPANESE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー々]+/u;
33
+ var COMPOUND = /^[A-Za-z0-9_]*(?:[A-Za-z0-9]_|_[A-Za-z0-9]|[a-z0-9][A-Z])[A-Za-z0-9_]*$/;
34
+ var CUT = /_+|(?<=[a-z0-9])(?=[A-Z])/;
35
+ var MAX_PARTS = 6;
36
+ var NAME_PART = /^(?:[A-Za-z]+[0-9]*|[0-9]+)$/;
31
37
  var MAX_TERM = 100;
32
38
  var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
33
39
  function singular(w) {
@@ -43,8 +49,9 @@ function singular(w) {
43
49
  return w.slice(0, -1);
44
50
  return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
45
51
  }
46
- function terms(text) {
47
- const norm = text.normalize("NFKC").toLowerCase();
52
+ function terms(text, parts = true) {
53
+ const nfkc = text.normalize("NFKC");
54
+ const norm = nfkc.toLowerCase();
48
55
  const out = [];
49
56
  const keep = (w) => {
50
57
  if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
@@ -57,6 +64,18 @@ function terms(text) {
57
64
  for (const m of norm.matchAll(IDENT))
58
65
  if (m[0].length >= 3)
59
66
  keep(m[0]);
67
+ if (parts)
68
+ for (const [word] of nfkc.matchAll(WORD))
69
+ for (const w of word.split(JAPANESE)) {
70
+ if (w.length > MAX_TERM || !COMPOUND.test(w))
71
+ continue;
72
+ const cut = w.split(CUT).filter(Boolean);
73
+ if (cut.length <= MAX_PARTS && cut.every((p) => NAME_PART.test(p))) {
74
+ for (const p of cut)
75
+ if (p.length >= 2)
76
+ keep(p.toLowerCase());
77
+ }
78
+ }
60
79
  return out.filter(Boolean);
61
80
  }
62
81
  var QUESTION = new Set([
@@ -11135,7 +11154,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
11135
11154
 
11136
11155
  // server/src/sqlite.ts
11137
11156
  var SCHEMA_GENERATION = 2;
11138
- var SCHEMA_REVISION = 8;
11157
+ var SCHEMA_REVISION = 9;
11139
11158
  var sphicaHome = () => process.env.SPHICA_HOME || path3.join(os.homedir(), ".sphica");
11140
11159
  var dbFile = () => process.env.SPHICA_DB || path3.join(sphicaHome(), "sphica.db");
11141
11160
  function requireRuntime() {
@@ -11717,7 +11736,7 @@ function fit(body) {
11717
11736
 
11718
11737
  ${z}`,
11719
11738
  truncated: true,
11720
- redacted: mask(start) !== start || mask(end) !== end,
11739
+ redacted: a !== head(start, KEEP) || z !== tail(end, KEEP),
11721
11740
  originalBytes: all
11722
11741
  };
11723
11742
  }
@@ -11757,9 +11776,11 @@ function isOwnerTurn(input, parent = process.env.SPHICA_PARENT_SESSION, entrypoi
11757
11776
  return false;
11758
11777
  if (codexParent && codexParent !== input.session_id)
11759
11778
  return false;
11779
+ if (entrypoint?.startsWith("sdk-"))
11780
+ return false;
11760
11781
  if (parent)
11761
11782
  return parent === input.session_id;
11762
- return entrypoint !== "sdk-cli";
11783
+ return true;
11763
11784
  }
11764
11785
  var INJECTED = [
11765
11786
  /^<(?:task-notification|channel|cross-session-message|teammate-message|agent-message|slack-ping|slack-tag-message|fetched-web-content|remote-review|remote-review-progress)[\s>]/,
@@ -31851,7 +31872,7 @@ function localChange(root, args) {
31851
31872
  }
31852
31873
 
31853
31874
  // server/src/trace.ts
31854
- var PENDING_DAYS = 30;
31875
+ var PENDING_DAYS = 14;
31855
31876
  var pendingCutoff = (now) => new Date(now.getTime() - PENDING_DAYS * 86400000).toISOString();
31856
31877
  var untracedOwner = (eb) => eb.selectFrom("source as m").whereRef("m.session_id", "=", "s.id").where("m.author_kind", "=", "owner").where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_processing as p").whereRef("p.source_id", "=", "m.id").select(sql`1`.as("x")))));
31857
31878
  var untracedSessions = (db, projectId2) => db.selectFrom("session as s").where("s.project_id", "=", projectId2).where((eb) => eb.exists(untracedOwner(eb).select(sql`1`.as("x")))).select((eb) => [
@@ -45542,6 +45542,12 @@ var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
45542
45542
  var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
45543
45543
  var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
45544
45544
  var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
45545
+ var WORD = /[\p{L}\p{M}\p{N}_]+/gu;
45546
+ var JAPANESE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー々]+/u;
45547
+ var COMPOUND = /^[A-Za-z0-9_]*(?:[A-Za-z0-9]_|_[A-Za-z0-9]|[a-z0-9][A-Z])[A-Za-z0-9_]*$/;
45548
+ var CUT = /_+|(?<=[a-z0-9])(?=[A-Z])/;
45549
+ var MAX_PARTS = 6;
45550
+ var NAME_PART = /^(?:[A-Za-z]+[0-9]*|[0-9]+)$/;
45545
45551
  var MAX_TERM = 100;
45546
45552
  var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
45547
45553
  function singular(w) {
@@ -45557,8 +45563,9 @@ function singular(w) {
45557
45563
  return w.slice(0, -1);
45558
45564
  return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
45559
45565
  }
45560
- function terms(text) {
45561
- const norm = text.normalize("NFKC").toLowerCase();
45566
+ function terms(text, parts = true) {
45567
+ const nfkc = text.normalize("NFKC");
45568
+ const norm = nfkc.toLowerCase();
45562
45569
  const out = [];
45563
45570
  const keep = (w) => {
45564
45571
  if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
@@ -45571,6 +45578,18 @@ function terms(text) {
45571
45578
  for (const m of norm.matchAll(IDENT))
45572
45579
  if (m[0].length >= 3)
45573
45580
  keep(m[0]);
45581
+ if (parts)
45582
+ for (const [word] of nfkc.matchAll(WORD))
45583
+ for (const w of word.split(JAPANESE)) {
45584
+ if (w.length > MAX_TERM || !COMPOUND.test(w))
45585
+ continue;
45586
+ const cut = w.split(CUT).filter(Boolean);
45587
+ if (cut.length <= MAX_PARTS && cut.every((p) => NAME_PART.test(p))) {
45588
+ for (const p of cut)
45589
+ if (p.length >= 2)
45590
+ keep(p.toLowerCase());
45591
+ }
45592
+ }
45574
45593
  return out.filter(Boolean);
45575
45594
  }
45576
45595
  var QUESTION = new Set([
@@ -45871,7 +45890,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
45871
45890
 
45872
45891
  // server/src/sqlite.ts
45873
45892
  var SCHEMA_GENERATION = 2;
45874
- var SCHEMA_REVISION = 8;
45893
+ var SCHEMA_REVISION = 9;
45875
45894
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
45876
45895
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
45877
45896
  function requireRuntime() {
@@ -46569,7 +46588,7 @@ function fit(body) {
46569
46588
 
46570
46589
  ${z2}`,
46571
46590
  truncated: true,
46572
- redacted: mask(start) !== start || mask(end) !== end,
46591
+ redacted: a !== head(start, KEEP) || z2 !== tail(end, KEEP),
46573
46592
  originalBytes: all
46574
46593
  };
46575
46594
  }
@@ -46609,9 +46628,11 @@ function isOwnerTurn(input2, parent = process.env.SPHICA_PARENT_SESSION, entrypo
46609
46628
  return false;
46610
46629
  if (codexParent && codexParent !== input2.session_id)
46611
46630
  return false;
46631
+ if (entrypoint?.startsWith("sdk-"))
46632
+ return false;
46612
46633
  if (parent)
46613
46634
  return parent === input2.session_id;
46614
- return entrypoint !== "sdk-cli";
46635
+ return true;
46615
46636
  }
46616
46637
  var INJECTED = [
46617
46638
  /^<(?:task-notification|channel|cross-session-message|teammate-message|agent-message|slack-ping|slack-tag-message|fetched-web-content|remote-review|remote-review-progress)[\s>]/,
@@ -47885,13 +47906,13 @@ var isoDate = (v) => {
47885
47906
  const d = new Date(`${v}T00:00:00Z`);
47886
47907
  return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === v;
47887
47908
  };
47888
- var WORD = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{M}\p{N}_]/u;
47909
+ var WORD2 = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{M}\p{N}_]/u;
47889
47910
  var NUMBER = /(?<![\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{M}\p{N}\p{Pd}_.+\uFF0B\u2212])[+-]?(?:[0-9]{1,3}(?:,[0-9]{3})+(?![0-9])|[0-9]+)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/gu;
47890
47911
  function standsAlone(quote, value) {
47891
47912
  for (let at = quote.indexOf(value);at >= 0; at = quote.indexOf(value, at + 1)) {
47892
47913
  const before = [...quote.slice(Math.max(0, at - 2), at)].at(-1) ?? "";
47893
47914
  const after = String.fromCodePoint(quote.codePointAt(at + value.length) ?? 32);
47894
- if (!WORD.test(before) && !WORD.test(after))
47915
+ if (!WORD2.test(before) && !WORD2.test(after))
47895
47916
  return true;
47896
47917
  }
47897
47918
  return false;
@@ -48693,7 +48714,7 @@ ${c.errors.map((e) => ` ${e}`).join(`
48693
48714
  }
48694
48715
 
48695
48716
  // server/src/trace.ts
48696
- var PENDING_DAYS = 30;
48717
+ var PENDING_DAYS = 14;
48697
48718
  var pendingCutoff = (now) => new Date(now.getTime() - PENDING_DAYS * 86400000).toISOString();
48698
48719
  var untracedOwner = (eb) => eb.selectFrom("source as m").whereRef("m.session_id", "=", "s.id").where("m.author_kind", "=", "owner").where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_processing as p").whereRef("p.source_id", "=", "m.id").select(sql`1`.as("x")))));
48699
48720
  var untracedSessions = (db, projectId2) => db.selectFrom("session as s").where("s.project_id", "=", projectId2).where((eb) => eb.exists(untracedOwner(eb).select(sql`1`.as("x")))).select((eb) => [
@@ -49298,7 +49319,7 @@ var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, o
49298
49319
  var RECORD = exports_external.record(exports_external.string(), exports_external.unknown()).describe("The record, as the Skill describes");
49299
49320
  server.registerTool("trace_pending", {
49300
49321
  title: "Sessions not traced yet",
49301
- description: "Lists this project's captured sessions with owner messages no trace has looked at, then apart those whose last owner message is over 30 days old.",
49322
+ description: "Lists this project's captured sessions with owner messages no trace has looked at, then apart those whose last owner message is over 14 days old.",
49302
49323
  inputSchema: exports_external.object({ cwd: CWD }).strict(),
49303
49324
  annotations: READ
49304
49325
  }, async (a, extra) => tool(async () => pendingText(conn(), (await projectOf(a.cwd, extra._meta)).projectId)));
package/dist/mcp.js CHANGED
@@ -34729,6 +34729,12 @@ var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
34729
34729
  var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
34730
34730
  var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
34731
34731
  var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
34732
+ var WORD = /[\p{L}\p{M}\p{N}_]+/gu;
34733
+ var JAPANESE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー々]+/u;
34734
+ var COMPOUND = /^[A-Za-z0-9_]*(?:[A-Za-z0-9]_|_[A-Za-z0-9]|[a-z0-9][A-Z])[A-Za-z0-9_]*$/;
34735
+ var CUT = /_+|(?<=[a-z0-9])(?=[A-Z])/;
34736
+ var MAX_PARTS = 6;
34737
+ var NAME_PART = /^(?:[A-Za-z]+[0-9]*|[0-9]+)$/;
34732
34738
  var MAX_TERM = 100;
34733
34739
  var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
34734
34740
  function singular(w) {
@@ -34745,8 +34751,9 @@ function singular(w) {
34745
34751
  return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
34746
34752
  }
34747
34753
  var identTerm = (x) => singular(x.normalize("NFKC").toLowerCase());
34748
- function terms(text) {
34749
- const norm = text.normalize("NFKC").toLowerCase();
34754
+ function terms(text, parts = true) {
34755
+ const nfkc = text.normalize("NFKC");
34756
+ const norm = nfkc.toLowerCase();
34750
34757
  const out = [];
34751
34758
  const keep = (w) => {
34752
34759
  if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
@@ -34759,6 +34766,18 @@ function terms(text) {
34759
34766
  for (const m of norm.matchAll(IDENT))
34760
34767
  if (m[0].length >= 3)
34761
34768
  keep(m[0]);
34769
+ if (parts)
34770
+ for (const [word] of nfkc.matchAll(WORD))
34771
+ for (const w of word.split(JAPANESE)) {
34772
+ if (w.length > MAX_TERM || !COMPOUND.test(w))
34773
+ continue;
34774
+ const cut = w.split(CUT).filter(Boolean);
34775
+ if (cut.length <= MAX_PARTS && cut.every((p) => NAME_PART.test(p))) {
34776
+ for (const p of cut)
34777
+ if (p.length >= 2)
34778
+ keep(p.toLowerCase());
34779
+ }
34780
+ }
34762
34781
  return out.filter(Boolean);
34763
34782
  }
34764
34783
  var QUESTION = new Set([
@@ -34805,7 +34824,7 @@ var QUESTION = new Set([
34805
34824
  ]);
34806
34825
  var FOLDED_QUESTION = new Set([...QUESTION].flatMap((w) => terms(w)));
34807
34826
  function queryTerms(question) {
34808
- return [...new Set(terms(question).filter((w) => !FOLDED_QUESTION.has(w)))].slice(0, 24);
34827
+ return [...new Set(terms(question, false).filter((w) => !FOLDED_QUESTION.has(w)))].slice(0, 24);
34809
34828
  }
34810
34829
  function ftsQuery(question) {
34811
34830
  const ws = queryTerms(question);
@@ -45692,7 +45711,7 @@ var HOST_MARKS = new Set([".orphaned_at", ".in_use"]);
45692
45711
 
45693
45712
  // server/src/sqlite.ts
45694
45713
  var SCHEMA_GENERATION = 2;
45695
- var SCHEMA_REVISION = 8;
45714
+ var SCHEMA_REVISION = 9;
45696
45715
  var sphicaHome = () => process.env.SPHICA_HOME || path2.join(os.homedir(), ".sphica");
45697
45716
  var dbFile = () => process.env.SPHICA_DB || path2.join(sphicaHome(), "sphica.db");
45698
45717
  function requireRuntime() {
@@ -47373,7 +47392,7 @@ async function checkFindings(db, projectId2, files, raw) {
47373
47392
  }
47374
47393
 
47375
47394
  // server/src/trace.ts
47376
- var PENDING_DAYS = 30;
47395
+ var PENDING_DAYS = 14;
47377
47396
  var pendingCutoff = (now) => new Date(now.getTime() - PENDING_DAYS * 86400000).toISOString();
47378
47397
  var untracedOwner = (eb) => eb.selectFrom("source as m").whereRef("m.session_id", "=", "s.id").where("m.author_kind", "=", "owner").where(({ not, exists, selectFrom }) => not(exists(selectFrom("source_processing as p").whereRef("p.source_id", "=", "m.id").select(sql`1`.as("x")))));
47379
47398
  var untracedSessions = (db, projectId2) => db.selectFrom("session as s").where("s.project_id", "=", projectId2).where((eb) => eb.exists(untracedOwner(eb).select(sql`1`.as("x")))).select((eb) => [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.22",
3
+ "version": "0.6.24",
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",
@@ -30,7 +30,7 @@ Everything goes through Sphica's `record` MCP server (its tools are `trace_pendi
30
30
 
31
31
  1. **Pick the session.** Without a target, it is this session: its id is `${CLAUDE_SESSION_ID}` in Claude Code; in Codex, read `CODEX_THREAD_ID`
32
32
  from your shell environment. With `pending`, call `trace_pending`, show the owner the list, and ask which to trace (AskUserQuestion in
33
- Claude Code). Sessions whose last owner message is over 30 days old come last under their own heading: session start does not count
33
+ Claude Code). Sessions whose last owner message is over 14 days old come last under their own heading: session start does not count
34
34
  them, but they can still be traced. Trace one session at a time
35
35
  2. **Begin**: `trace_begin` with that `session`. It returns a `run` id bound to that session and this project; the record never names them
36
36
  3. **Read**: `record_context` with the run. It prints each captured message as `## s<N> owner|assistant <turn> <time>` followed by its text,