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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/db/migrations/0009.sql +9 -0
- package/db/schema.sql +1 -1
- package/dist/capture.js +26 -5
- package/dist/cli.js +103 -27
- package/dist/deliver.js +27 -6
- package/dist/mcp-record.js +30 -9
- package/dist/mcp.js +24 -5
- package/package.json +1 -1
- package/skills/trace/SKILL.md +1 -1
|
@@ -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.
|
|
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",
|
|
@@ -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
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
{
|
|
16121
|
-
|
|
16122
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
16305
|
-
|
|
16306
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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) => [
|
package/dist/mcp-record.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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.
|
|
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",
|
package/skills/trace/SKILL.md
CHANGED
|
@@ -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
|
|
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,
|