sphica 0.6.22 → 0.6.23

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.23",
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.23",
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() {
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() {
@@ -16097,7 +16116,9 @@ var terms_golden_default = {
16097
16116
  "server/src/db-write.ts",
16098
16117
  "connectwriter",
16099
16118
  "ot-123",
16100
- "#201"
16119
+ "#201",
16120
+ "connect",
16121
+ "writer"
16101
16122
  ]
16102
16123
  },
16103
16124
  {
@@ -16110,21 +16131,39 @@ var terms_golden_default = {
16110
16131
  "name",
16111
16132
  "snake_case_name",
16112
16133
  "camelcasename",
16113
- "kebab-case-name"
16134
+ "kebab-case-name",
16135
+ "snake",
16136
+ "case",
16137
+ "name",
16138
+ "camel",
16139
+ "case",
16140
+ "name"
16114
16141
  ]
16115
16142
  },
16116
16143
  {
16117
16144
  text: "fullwidth ABC 123 と カタカナ",
16118
16145
  terms: ["fullwidth", "abc", "123", "カタカナ", "fullwidth", "abc", "123"]
16119
16146
  },
16120
- { text: "書きました 書きます 書く", terms: ["書", "書", "書"] },
16121
- { text: "emoji 🚀 と 絵文字 👍🏽 のあいだ", terms: ["emoji", "絵文字", "emoji"] },
16122
- { text: "é café と が(が)", terms: ["é", "café", "caf"] },
16147
+ {
16148
+ text: "書きました 書きます 書く",
16149
+ terms: ["書", "書", "書"]
16150
+ },
16151
+ {
16152
+ text: "emoji 🚀 と 絵文字 👍🏽 のあいだ",
16153
+ terms: ["emoji", "絵文字", "emoji"]
16154
+ },
16155
+ {
16156
+ text: "é café と が(が)",
16157
+ terms: ["é", "café", "caf"]
16158
+ },
16123
16159
  {
16124
16160
  text: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa base64 aGVsbG8gd29ybGQgaGVsbG8gd29ybGQ=",
16125
16161
  terms: ["base64", "agvsbg8gd29ybgqgagvsbg8gd29ybgq", "base64", "agvsbg8gd29ybgqgagvsbg8gd29ybgq"]
16126
16162
  },
16127
- { text: "the a an of to in is and or for on it be", terms: [] },
16163
+ {
16164
+ text: "the a an of to in is and or for on it be",
16165
+ terms: []
16166
+ },
16128
16167
  {
16129
16168
  text: "保存先は Postgres じゃなくて SQLite にしよう。各ユーザーに DB サーバーを立てさせたくない。",
16130
16169
  terms: [
@@ -16159,10 +16198,13 @@ var terms_golden_default = {
16159
16198
  "postgre"
16160
16199
  ]
16161
16200
  },
16162
- { text: "WAL モードも有効にしておいて。", terms: ["wal", "モード", "有効", "wal"] },
16201
+ {
16202
+ text: "WAL モードも有効にしておいて。",
16203
+ terms: ["wal", "モード", "有効", "wal"]
16204
+ },
16163
16205
  {
16164
16206
  text: "open() で journal_mode = wal を設定しました。",
16165
- terms: ["open", "journal_mode", "wal", "設定", "open", "journal_mode", "wal"]
16207
+ terms: ["open", "journal_mode", "wal", "設定", "open", "journal_mode", "wal", "journal", "mode"]
16166
16208
  },
16167
16209
  {
16168
16210
  text: "利用状況の送信(テレメトリ)は入れない。ローカルで完結させるのがこのアプリの売りだから。",
@@ -16186,15 +16228,24 @@ var terms_golden_default = {
16186
16228
  },
16187
16229
  {
16188
16230
  text: "fetchCover に最大 3 回の再試行を入れました。",
16189
- terms: ["fetchcover", "最大", "3", "回", "再", "試行", "入", "fetchcover"]
16231
+ terms: ["fetchcover", "最大", "3", "回", "再", "試行", "入", "fetchcover", "fetch", "cover"]
16232
+ },
16233
+ {
16234
+ text: "今日はここまで。お疲れさま。",
16235
+ terms: ["今日", "お疲れさま"]
16236
+ },
16237
+ {
16238
+ text: "お疲れさまでした。",
16239
+ terms: ["お疲れさま"]
16190
16240
  },
16191
- { text: "今日はここまで。お疲れさま。", terms: ["今日", "お疲れさま"] },
16192
- { text: "お疲れさまでした。", terms: ["お疲れさま"] },
16193
16241
  {
16194
16242
  text: "入力の検証に zod を使うのもありかもね。まだ決めないけど。",
16195
16243
  terms: ["入力", "検証", "zod", "使", "決", "zod"]
16196
16244
  },
16197
- { text: "候補として覚えておきます。", terms: ["候補", "覚"] },
16245
+ {
16246
+ text: "候補として覚えておきます。",
16247
+ terms: ["候補", "覚"]
16248
+ },
16198
16249
  {
16199
16250
  text: "Store every timestamp in UTC. Convert to local time only when showing it.",
16200
16251
  terms: [
@@ -16238,7 +16289,8 @@ var terms_golden_default = {
16238
16289
  "write",
16239
16290
  "iso",
16240
16291
  "8601",
16241
- "utc"
16292
+ "utc",
16293
+ "stored"
16242
16294
  ]
16243
16295
  },
16244
16296
  {
@@ -16282,7 +16334,9 @@ var terms_golden_default = {
16282
16334
  "there",
16283
16335
  "reverted",
16284
16336
  "main",
16285
- "thread"
16337
+ "thread",
16338
+ "offscreen",
16339
+ "canva"
16286
16340
  ]
16287
16341
  },
16288
16342
  {
@@ -16301,9 +16355,18 @@ var terms_golden_default = {
16301
16355
  text: "検索結果のキャッシュを Redis に置きます。",
16302
16356
  terms: ["検索", "結果", "キャッシュ", "redis", "置", "redis"]
16303
16357
  },
16304
- { text: "ログその1を受け取りました。", terms: ["ログ", "1", "受け取り"] },
16305
- { text: "ログその2を受け取りました。", terms: ["ログ", "2", "受け取り"] },
16306
- { text: "ログその3を受け取りました。", terms: ["ログ", "3", "受け取り"] },
16358
+ {
16359
+ text: "ログその1を受け取りました。",
16360
+ terms: ["ログ", "1", "受け取り"]
16361
+ },
16362
+ {
16363
+ text: "ログその2を受け取りました。",
16364
+ terms: ["ログ", "2", "受け取り"]
16365
+ },
16366
+ {
16367
+ text: "ログその3を受け取りました。",
16368
+ terms: ["ログ", "3", "受け取り"]
16369
+ },
16307
16370
  {
16308
16371
  text: "CSV export leaks private notes",
16309
16372
  terms: ["csv", "export", "leak", "private", "note", "csv", "export", "leak", "private", "note"]
@@ -16343,7 +16406,10 @@ var terms_golden_default = {
16343
16406
  "csv"
16344
16407
  ]
16345
16408
  },
16346
- { text: "Use pnpm for installs", terms: ["use", "pnpm", "install", "use", "pnpm", "install"] },
16409
+ {
16410
+ text: "Use pnpm for installs",
16411
+ terms: ["use", "pnpm", "install", "use", "pnpm", "install"]
16412
+ },
16347
16413
  {
16348
16414
  text: "Switch the lockfile to pnpm. npm install took minutes on CI.",
16349
16415
  terms: [
@@ -16421,7 +16487,10 @@ var terms_golden_default = {
16421
16487
  text: "Consider synchronous = OFF for speed.",
16422
16488
  terms: ["consider", "synchronous", "off", "speed", "consider", "synchronous", "off", "speed"]
16423
16489
  },
16424
- { text: "chore: switch to pnpm", terms: ["chore", "switch", "pnpm", "chore", "switch", "pnpm"] },
16490
+ {
16491
+ text: "chore: switch to pnpm",
16492
+ terms: ["chore", "switch", "pnpm", "chore", "switch", "pnpm"]
16493
+ },
16425
16494
  {
16426
16495
  text: "Exclude notes from CSV export",
16427
16496
  terms: ["exclude", "note", "from", "csv", "export", "exclude", "note", "from", "csv", "export"]
@@ -16444,7 +16513,8 @@ var terms_golden_default = {
16444
16513
  "write",
16445
16514
  "title",
16446
16515
  "author",
16447
- "only"
16516
+ "only",
16517
+ "csv"
16448
16518
  ]
16449
16519
  },
16450
16520
  {
@@ -16484,10 +16554,14 @@ var terms_golden_default = {
16484
16554
  "drop",
16485
16555
  "author",
16486
16556
  "from",
16487
- "export"
16557
+ "export",
16558
+ "csv"
16488
16559
  ]
16489
16560
  },
16490
- { text: "検索は前方一致にする", terms: ["検索", "前方", "一致"] },
16561
+ {
16562
+ text: "検索は前方一致にする",
16563
+ terms: ["検索", "前方", "一致"]
16564
+ },
16491
16565
  {
16492
16566
  text: "書名の検索は前方一致にする。全文検索はデータが 1 万冊を超えるまで見送る。",
16493
16567
  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() {
@@ -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() {
@@ -47885,13 +47904,13 @@ var isoDate = (v) => {
47885
47904
  const d = new Date(`${v}T00:00:00Z`);
47886
47905
  return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === v;
47887
47906
  };
47888
- var WORD = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{M}\p{N}_]/u;
47907
+ var WORD2 = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{M}\p{N}_]/u;
47889
47908
  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
47909
  function standsAlone(quote, value) {
47891
47910
  for (let at = quote.indexOf(value);at >= 0; at = quote.indexOf(value, at + 1)) {
47892
47911
  const before = [...quote.slice(Math.max(0, at - 2), at)].at(-1) ?? "";
47893
47912
  const after = String.fromCodePoint(quote.codePointAt(at + value.length) ?? 32);
47894
- if (!WORD.test(before) && !WORD.test(after))
47913
+ if (!WORD2.test(before) && !WORD2.test(after))
47895
47914
  return true;
47896
47915
  }
47897
47916
  return false;
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sphica",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
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",