sphica 0.5.5 → 0.5.7
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/README.md +3 -1
- package/dist/capture.js +23 -15
- package/dist/cli.js +23 -15
- package/dist/deliver.js +262 -254
- package/dist/mcp-record.js +192 -39
- package/dist/mcp.js +5 -6
- package/package.json +1 -1
- package/skills/glean/SKILL.md +2 -2
- package/skills/harvest/SKILL.md +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.5.
|
|
4
|
+
"version": "0.5.7",
|
|
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",
|
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/sphica#provenance)
|
|
8
8
|
[](https://github.com/iroha924/sphica/blob/main/.github/dependabot.yml)
|
|
9
9
|
|
|
10
|
+
What the badges cover: CI runs the checks in [Contributing](https://github.com/iroha924/sphica#contributing), and how each release is built and published is in [Security](https://github.com/iroha924/sphica#security). A passing badge does not mean the code is free of bugs or vulnerabilities.
|
|
11
|
+
|
|
10
12
|
English | [日本語](https://github.com/iroha924/sphica/blob/main/README.ja.md)
|
|
11
13
|
|
|
12
14
|
**Local memory of past implementation and decisions for Claude Code and Codex.**
|
|
@@ -116,7 +118,7 @@ The agent searches with Sphica's `search` and opens full records with `read`. `s
|
|
|
116
118
|
- **What.** Your prompts, the agent's final reply for each turn, and the paths of changed files. Background-task notifications and messages from other agents are skipped when Sphica recognizes their format. Replies in the middle of a turn are not kept, nor are files created and deleted within one turn without the edit tools.
|
|
117
119
|
- **What was shown.** Each automatic delivery is logged by which records it showed, not their text.
|
|
118
120
|
- **Unregistered repositories.** Sessions in a repository you have not registered stay in the queue and are written after you register it. Held sessions are dropped after 30 days, and when more than 1,000 are waiting the oldest go first.
|
|
119
|
-
- **Secrets.**
|
|
121
|
+
- **Secrets.** In your prompts, pull request text, and the file lines `/sphica:glean` cites, only secrets with a recognizable shape are masked (excerpts saved before 0.5.7 stay as they were):
|
|
120
122
|
- keys with known prefixes
|
|
121
123
|
- `KEY=…` and `"password": …` assignments
|
|
122
124
|
- credentials in URLs
|
package/dist/capture.js
CHANGED
|
@@ -11108,24 +11108,32 @@ var MYSQL_PASSWORD = /(\s-p)(?:'[^'\n]*'|"[^"\n]*"|(?=[^\s-])\S+)/;
|
|
|
11108
11108
|
var URL_CREDENTIALS = /\b((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?|https?):\/\/[^:\s/@]*:)[^\s/]*@([^@\s/?#]+)/g;
|
|
11109
11109
|
var KEY_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/g;
|
|
11110
11110
|
var KEY_END = /-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
11111
|
-
function
|
|
11111
|
+
function keyBlocks(text) {
|
|
11112
11112
|
const ends = [...text.matchAll(KEY_END)].map((m) => [m.index, m.index + m[0].length]);
|
|
11113
|
-
|
|
11114
|
-
return text;
|
|
11115
|
-
let out = "";
|
|
11113
|
+
const out = [];
|
|
11116
11114
|
let last = 0;
|
|
11117
11115
|
let e = 0;
|
|
11118
|
-
|
|
11119
|
-
const
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
e
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11116
|
+
if (ends.length > 0)
|
|
11117
|
+
for (const m of text.matchAll(KEY_BEGIN)) {
|
|
11118
|
+
const after = m.index + m[0].length;
|
|
11119
|
+
if (m.index < last)
|
|
11120
|
+
continue;
|
|
11121
|
+
while (e < ends.length && (ends[e]?.[0] ?? 0) < after)
|
|
11122
|
+
e++;
|
|
11123
|
+
const end = ends[e];
|
|
11124
|
+
if (!end)
|
|
11125
|
+
break;
|
|
11126
|
+
out.push([m.index, end[1]]);
|
|
11127
|
+
last = end[1];
|
|
11128
|
+
}
|
|
11129
|
+
return out;
|
|
11130
|
+
}
|
|
11131
|
+
function maskPrivateKeys(text) {
|
|
11132
|
+
let out = "";
|
|
11133
|
+
let last = 0;
|
|
11134
|
+
for (const [a, b] of keyBlocks(text)) {
|
|
11135
|
+
out += `${text.slice(last, a)}[redacted: private key]`;
|
|
11136
|
+
last = b;
|
|
11129
11137
|
}
|
|
11130
11138
|
return out + text.slice(last);
|
|
11131
11139
|
}
|
package/dist/cli.js
CHANGED
|
@@ -3193,24 +3193,32 @@ var MYSQL_PASSWORD = /(\s-p)(?:'[^'\n]*'|"[^"\n]*"|(?=[^\s-])\S+)/;
|
|
|
3193
3193
|
var URL_CREDENTIALS = /\b((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?|https?):\/\/[^:\s/@]*:)[^\s/]*@([^@\s/?#]+)/g;
|
|
3194
3194
|
var KEY_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/g;
|
|
3195
3195
|
var KEY_END = /-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
3196
|
-
function
|
|
3196
|
+
function keyBlocks(text) {
|
|
3197
3197
|
const ends = [...text.matchAll(KEY_END)].map((m) => [m.index, m.index + m[0].length]);
|
|
3198
|
-
|
|
3199
|
-
return text;
|
|
3200
|
-
let out = "";
|
|
3198
|
+
const out = [];
|
|
3201
3199
|
let last = 0;
|
|
3202
3200
|
let e = 0;
|
|
3203
|
-
|
|
3204
|
-
const
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
e
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3201
|
+
if (ends.length > 0)
|
|
3202
|
+
for (const m of text.matchAll(KEY_BEGIN)) {
|
|
3203
|
+
const after = m.index + m[0].length;
|
|
3204
|
+
if (m.index < last)
|
|
3205
|
+
continue;
|
|
3206
|
+
while (e < ends.length && (ends[e]?.[0] ?? 0) < after)
|
|
3207
|
+
e++;
|
|
3208
|
+
const end = ends[e];
|
|
3209
|
+
if (!end)
|
|
3210
|
+
break;
|
|
3211
|
+
out.push([m.index, end[1]]);
|
|
3212
|
+
last = end[1];
|
|
3213
|
+
}
|
|
3214
|
+
return out;
|
|
3215
|
+
}
|
|
3216
|
+
function maskPrivateKeys(text) {
|
|
3217
|
+
let out = "";
|
|
3218
|
+
let last = 0;
|
|
3219
|
+
for (const [a2, b] of keyBlocks(text)) {
|
|
3220
|
+
out += `${text.slice(last, a2)}[redacted: private key]`;
|
|
3221
|
+
last = b;
|
|
3214
3222
|
}
|
|
3215
3223
|
return out + text.slice(last);
|
|
3216
3224
|
}
|
package/dist/deliver.js
CHANGED
|
@@ -21,6 +21,255 @@ import path7 from "node:path";
|
|
|
21
21
|
|
|
22
22
|
// server/src/anchors.ts
|
|
23
23
|
import path from "node:path";
|
|
24
|
+
|
|
25
|
+
// server/src/text.ts
|
|
26
|
+
import crypto from "node:crypto";
|
|
27
|
+
var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
|
|
28
|
+
var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
|
|
29
|
+
var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
|
|
30
|
+
var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
|
|
31
|
+
var MAX_TERM = 100;
|
|
32
|
+
var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
|
|
33
|
+
function singular(w) {
|
|
34
|
+
if (!/^[a-z]{4,}$/.test(w))
|
|
35
|
+
return w;
|
|
36
|
+
if (w.endsWith("ies"))
|
|
37
|
+
return `${w.slice(0, -3)}y`;
|
|
38
|
+
if (w.endsWith("ie"))
|
|
39
|
+
return `${w.slice(0, -2)}y`;
|
|
40
|
+
if (/(?:ss|us|x|z|ch|sh)es$/.test(w))
|
|
41
|
+
return w.slice(0, -2);
|
|
42
|
+
if (/(?:ch|sh|us)e$/.test(w))
|
|
43
|
+
return w.slice(0, -1);
|
|
44
|
+
return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
|
|
45
|
+
}
|
|
46
|
+
function terms(text) {
|
|
47
|
+
const norm = text.normalize("NFKC").toLowerCase();
|
|
48
|
+
const out = [];
|
|
49
|
+
const keep = (w) => {
|
|
50
|
+
if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
|
|
51
|
+
return;
|
|
52
|
+
out.push(OKURIGANA.exec(w)?.[1] ?? singular(w));
|
|
53
|
+
};
|
|
54
|
+
for (const s of segmenter.segment(norm))
|
|
55
|
+
if (s.isWordLike)
|
|
56
|
+
keep(s.segment.trim());
|
|
57
|
+
for (const m of norm.matchAll(IDENT))
|
|
58
|
+
if (m[0].length >= 3)
|
|
59
|
+
keep(m[0]);
|
|
60
|
+
return out.filter(Boolean);
|
|
61
|
+
}
|
|
62
|
+
var QUESTION = new Set([
|
|
63
|
+
...[
|
|
64
|
+
"why",
|
|
65
|
+
"what",
|
|
66
|
+
"which",
|
|
67
|
+
"when",
|
|
68
|
+
"where",
|
|
69
|
+
"who",
|
|
70
|
+
"how",
|
|
71
|
+
"do",
|
|
72
|
+
"does",
|
|
73
|
+
"did",
|
|
74
|
+
"we",
|
|
75
|
+
"our",
|
|
76
|
+
"us",
|
|
77
|
+
"you",
|
|
78
|
+
"i",
|
|
79
|
+
"are",
|
|
80
|
+
"was",
|
|
81
|
+
"were"
|
|
82
|
+
],
|
|
83
|
+
...[
|
|
84
|
+
"will",
|
|
85
|
+
"would",
|
|
86
|
+
"should",
|
|
87
|
+
"can",
|
|
88
|
+
"could",
|
|
89
|
+
"there",
|
|
90
|
+
"this",
|
|
91
|
+
"that",
|
|
92
|
+
"with",
|
|
93
|
+
"from",
|
|
94
|
+
"any",
|
|
95
|
+
"ever",
|
|
96
|
+
"long",
|
|
97
|
+
"use",
|
|
98
|
+
"used",
|
|
99
|
+
"using"
|
|
100
|
+
],
|
|
101
|
+
...["add", "get", "make", "have", "has", "not", "no", "yes", "reason"],
|
|
102
|
+
...["理由", "仕組", "何", "方", "場合", "今", "件"]
|
|
103
|
+
]);
|
|
104
|
+
var sha256 = (s) => crypto.createHash("sha256").update(s).digest();
|
|
105
|
+
function uuidFrom(...parts) {
|
|
106
|
+
const b = crypto.createHash("sha256").update(parts.join("\x00")).digest().subarray(0, 16);
|
|
107
|
+
b[6] = (b[6] ?? 0) & 15 | 128;
|
|
108
|
+
b[8] = (b[8] ?? 0) & 63 | 128;
|
|
109
|
+
const h = b.toString("hex");
|
|
110
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
111
|
+
}
|
|
112
|
+
var bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
113
|
+
function head(s, n) {
|
|
114
|
+
if (bytes(s) <= n)
|
|
115
|
+
return s;
|
|
116
|
+
let out = "";
|
|
117
|
+
let used = 0;
|
|
118
|
+
for (const ch of s) {
|
|
119
|
+
const b = bytes(ch);
|
|
120
|
+
if (used + b > n)
|
|
121
|
+
break;
|
|
122
|
+
out += ch;
|
|
123
|
+
used += b;
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function tail(s, n) {
|
|
128
|
+
if (bytes(s) <= n)
|
|
129
|
+
return s;
|
|
130
|
+
const chars = [...s];
|
|
131
|
+
let used = 0;
|
|
132
|
+
let i = chars.length;
|
|
133
|
+
while (i > 0) {
|
|
134
|
+
const b = bytes(chars[i - 1] ?? "");
|
|
135
|
+
if (used + b > n)
|
|
136
|
+
break;
|
|
137
|
+
used += b;
|
|
138
|
+
i--;
|
|
139
|
+
}
|
|
140
|
+
return chars.slice(i).join("");
|
|
141
|
+
}
|
|
142
|
+
var clean = (s) => s.replaceAll("\x00", "");
|
|
143
|
+
var visible = (s) => s.replace(/(?!\p{Join_Control}|\p{Variation_Selector})\p{Default_Ignorable_Code_Point}/gu, "");
|
|
144
|
+
var SECRETS = [
|
|
145
|
+
[/\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}/g, "API key"],
|
|
146
|
+
[/\b[srp]k_(?:live|test)_[A-Za-z0-9]{16,}/g, "API key"],
|
|
147
|
+
[/\bwhsec_[A-Za-z0-9+/=]{16,}/g, "webhook signing secret"],
|
|
148
|
+
[/\bpa-[A-Za-z0-9_-]{20,}/g, "API key"],
|
|
149
|
+
[/\bAIza[0-9A-Za-z_-]{35}/g, "API key"],
|
|
150
|
+
[/\bnpg_[A-Za-z0-9]{12,}/g, "database password"],
|
|
151
|
+
[/\bnapi_[A-Za-z0-9]{30,}/g, "API key"],
|
|
152
|
+
[/\bnpm_[A-Za-z0-9]{36}\b/g, "npm token"],
|
|
153
|
+
[/\bglpat-[A-Za-z0-9_-]{20,}/g, "GitLab token"],
|
|
154
|
+
[/\bgh[pousr]_[A-Za-z0-9]{30,}/g, "GitHub token"],
|
|
155
|
+
[/\bgithub_pat_[A-Za-z0-9_]{40,}/g, "GitHub token"],
|
|
156
|
+
[/\bxox[abprs]-[A-Za-z0-9-]{10,}/g, "Slack token"],
|
|
157
|
+
[/https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/g, "Slack webhook"],
|
|
158
|
+
[/\bAKIA[0-9A-Z]{16}\b/g, "AWS key"],
|
|
159
|
+
[/(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "JWT"],
|
|
160
|
+
[/\b(?:Bearer|BEARER)\s+(?=[A-Za-z0-9._~+/=-]{0,512}\d)[A-Za-z0-9._~+/=-]{16,}/g, "auth header value"]
|
|
161
|
+
];
|
|
162
|
+
var AUTH_HEADER = /(\bAuthorization["']?\s*[:=]\s*(?:["']\s*)?(?:Bearer|Basic|Token|Digest)\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
163
|
+
var HEADER_BEARER = /(:[ \t]*(?:["'][ \t]*)?bearer[ \t]+)[A-Za-z0-9._~+/=-]{16,}/gi;
|
|
164
|
+
var ENV_ASSIGN = /\b((?:[A-Z][A-Z0-9_]*_)?(?:API|SECRET|MASTER|ENCRYPTION|PRIVATE|ACCESS|SIGNING|AUTH)?KEY|[A-Z][A-Z0-9_]*_(?:PASS|PWD)|(?:[A-Z][A-Z0-9_]*?)?(?:TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIALS?))(\s*=\s*)(?:"(?!\$)[^"\n]+"|'(?!\$)[^'\n]+'|(?![$"'])[^\s"']+)/g;
|
|
165
|
+
var FIELD_NAME = /(?:(?:api|account|access|private|secret)[-_]?key|secret|token|passw(?:or)?d)["']?\s*(?::=|=>|[:=])\s*/gi;
|
|
166
|
+
var MAX_QUOTED = 4096;
|
|
167
|
+
var BARE_HEAD = /[^\s"',;)]{1,256}/y;
|
|
168
|
+
var BARE_REST = /[^\s"',;)]*/y;
|
|
169
|
+
var NEXT_PARAM = /&[A-Za-z_][\w.-]*=/;
|
|
170
|
+
var bareAt = (re, text, at) => {
|
|
171
|
+
re.lastIndex = at;
|
|
172
|
+
const v = re.exec(text)?.[0] ?? "";
|
|
173
|
+
const cut = v.search(NEXT_PARAM);
|
|
174
|
+
return cut < 0 ? v : v.slice(0, cut);
|
|
175
|
+
};
|
|
176
|
+
function secretValue(quoted, v) {
|
|
177
|
+
if (v.length < 8 || /^\$(?:\{|[A-Za-z_])/.test(v))
|
|
178
|
+
return false;
|
|
179
|
+
if (!quoted)
|
|
180
|
+
return !/^[#$]/.test(v) && /\d/.test(v) && /[A-Za-z]/.test(v) && !/[()]/.test(v);
|
|
181
|
+
if (/^#[0-9a-f]{3,8}$/i.test(v) || !/[A-Za-z0-9]/.test(v))
|
|
182
|
+
return false;
|
|
183
|
+
return !/\s/.test(v) || v.split(/\s+/).some((w) => /\d/.test(w) && /[A-Za-z]/.test(w));
|
|
184
|
+
}
|
|
185
|
+
function maskFields(text) {
|
|
186
|
+
let out = "";
|
|
187
|
+
let last = 0;
|
|
188
|
+
FIELD_NAME.lastIndex = 0;
|
|
189
|
+
for (let m = FIELD_NAME.exec(text);m; m = FIELD_NAME.exec(text)) {
|
|
190
|
+
const at = m.index + m[0].length;
|
|
191
|
+
const q = text[at];
|
|
192
|
+
let quote = "";
|
|
193
|
+
let value;
|
|
194
|
+
if (q === '"' || q === "'") {
|
|
195
|
+
const close = text.indexOf(q, at + 1);
|
|
196
|
+
if (close < 0 || close - at - 1 > MAX_QUOTED)
|
|
197
|
+
continue;
|
|
198
|
+
value = text.slice(at + 1, close);
|
|
199
|
+
if (value.includes(`
|
|
200
|
+
`))
|
|
201
|
+
continue;
|
|
202
|
+
quote = q;
|
|
203
|
+
} else {
|
|
204
|
+
value = bareAt(BARE_HEAD, text, at);
|
|
205
|
+
}
|
|
206
|
+
if (!secretValue(quote !== "", value))
|
|
207
|
+
continue;
|
|
208
|
+
if (!quote && value.length === 256)
|
|
209
|
+
value += bareAt(BARE_REST, text, at + 256);
|
|
210
|
+
out += `${text.slice(last, at)}${quote}[redacted]`;
|
|
211
|
+
last = at + quote.length + value.length;
|
|
212
|
+
FIELD_NAME.lastIndex = last;
|
|
213
|
+
}
|
|
214
|
+
return out + text.slice(last);
|
|
215
|
+
}
|
|
216
|
+
var MYSQL_COMMAND = /\bmysql(?:dump|admin)?\b(?:'[^'\n]*'|"[^"\n]*"|[^\n;&|\\'"]|\\\r?\n|\\(?!\r?\n))*/g;
|
|
217
|
+
var MYSQL_PASSWORD = /(\s-p)(?:'[^'\n]*'|"[^"\n]*"|(?=[^\s-])\S+)/;
|
|
218
|
+
var URL_CREDENTIALS = /\b((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?|https?):\/\/[^:\s/@]*:)[^\s/]*@([^@\s/?#]+)/g;
|
|
219
|
+
var KEY_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/g;
|
|
220
|
+
var KEY_END = /-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
221
|
+
function keyBlocks(text) {
|
|
222
|
+
const ends = [...text.matchAll(KEY_END)].map((m) => [m.index, m.index + m[0].length]);
|
|
223
|
+
const out = [];
|
|
224
|
+
let last = 0;
|
|
225
|
+
let e = 0;
|
|
226
|
+
if (ends.length > 0)
|
|
227
|
+
for (const m of text.matchAll(KEY_BEGIN)) {
|
|
228
|
+
const after = m.index + m[0].length;
|
|
229
|
+
if (m.index < last)
|
|
230
|
+
continue;
|
|
231
|
+
while (e < ends.length && (ends[e]?.[0] ?? 0) < after)
|
|
232
|
+
e++;
|
|
233
|
+
const end = ends[e];
|
|
234
|
+
if (!end)
|
|
235
|
+
break;
|
|
236
|
+
out.push([m.index, end[1]]);
|
|
237
|
+
last = end[1];
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
function maskPrivateKeys(text) {
|
|
242
|
+
let out = "";
|
|
243
|
+
let last = 0;
|
|
244
|
+
for (const [a, b] of keyBlocks(text)) {
|
|
245
|
+
out += `${text.slice(last, a)}[redacted: private key]`;
|
|
246
|
+
last = b;
|
|
247
|
+
}
|
|
248
|
+
return out + text.slice(last);
|
|
249
|
+
}
|
|
250
|
+
function mask(text) {
|
|
251
|
+
let out = maskFields(maskPrivateKeys(text).replace(URL_CREDENTIALS, "$1[redacted]@$2").replace(AUTH_HEADER, "$1[redacted]").replace(HEADER_BEARER, "$1[redacted]").replace(ENV_ASSIGN, "$1$2[redacted]")).replace(MYSQL_COMMAND, (command) => command.replace(MYSQL_PASSWORD, "$1[redacted]"));
|
|
252
|
+
for (const [re, what] of SECRETS)
|
|
253
|
+
out = out.replace(re, `[redacted: ${what}]`);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
var reason = (e) => explain(e, 0) || "unknown failure";
|
|
257
|
+
var plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
258
|
+
function explain(e, depth) {
|
|
259
|
+
if (!(e instanceof Error)) {
|
|
260
|
+
try {
|
|
261
|
+
return String(e);
|
|
262
|
+
} catch {
|
|
263
|
+
return "";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const own = e.message || (e.name === "Error" || e.name === "AggregateError" ? "" : e.name);
|
|
267
|
+
const parts = depth >= 3 ? [] : [...e instanceof AggregateError ? e.errors : [], ...e.cause === undefined ? [] : [e.cause]];
|
|
268
|
+
const inner = parts.map((x) => explain(x, depth + 1)).filter(Boolean).join(" / ");
|
|
269
|
+
return own && inner ? `${own} (${inner})` : own || inner;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// server/src/anchors.ts
|
|
24
273
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
25
274
|
var leaves = (rel) => rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel);
|
|
26
275
|
|
|
@@ -4154,8 +4403,8 @@ class Deferred {
|
|
|
4154
4403
|
this.#resolve?.(value);
|
|
4155
4404
|
this.#resolve = this.#reject = undefined;
|
|
4156
4405
|
};
|
|
4157
|
-
reject = (
|
|
4158
|
-
this.#reject?.(
|
|
4406
|
+
reject = (reason2) => {
|
|
4407
|
+
this.#reject?.(reason2);
|
|
4159
4408
|
this.#reject = this.#resolve = undefined;
|
|
4160
4409
|
};
|
|
4161
4410
|
}
|
|
@@ -4207,9 +4456,9 @@ function assertNotAborted(signal, timing, beforeThrow) {
|
|
|
4207
4456
|
throwReasonWithTiming(signal.reason, timing);
|
|
4208
4457
|
}
|
|
4209
4458
|
}
|
|
4210
|
-
function throwReasonWithTiming(
|
|
4211
|
-
decorateWithTiming(
|
|
4212
|
-
throw
|
|
4459
|
+
function throwReasonWithTiming(reason2, timing) {
|
|
4460
|
+
decorateWithTiming(reason2, timing);
|
|
4461
|
+
throw reason2;
|
|
4213
4462
|
}
|
|
4214
4463
|
var ABORTED = {};
|
|
4215
4464
|
async function waitOrAbort(promise, signal, name, onAbort) {
|
|
@@ -4234,11 +4483,11 @@ async function waitOrAbort(promise, signal, name, onAbort) {
|
|
|
4234
4483
|
}
|
|
4235
4484
|
}
|
|
4236
4485
|
function printBackgroundFail(name) {
|
|
4237
|
-
return (
|
|
4486
|
+
return (reason2) => console.error(`\`${name}\` failed in the background after abortion: ${getMessage(reason2)}`);
|
|
4238
4487
|
}
|
|
4239
|
-
function decorateWithTiming(
|
|
4240
|
-
if (
|
|
4241
|
-
Object.defineProperty(
|
|
4488
|
+
function decorateWithTiming(reason2, timing) {
|
|
4489
|
+
if (reason2 !== null && typeof reason2 === "object" && !Object.isFrozen(reason2)) {
|
|
4490
|
+
Object.defineProperty(reason2, "__kysely_timing__", {
|
|
4242
4491
|
configurable: true,
|
|
4243
4492
|
enumerable: false,
|
|
4244
4493
|
value: timing,
|
|
@@ -8163,9 +8412,9 @@ class RuntimeDriver {
|
|
|
8163
8412
|
}
|
|
8164
8413
|
this.#initPromise ??= this.#driver.init(options).then(() => {
|
|
8165
8414
|
this.#initDone = true;
|
|
8166
|
-
}).catch((
|
|
8415
|
+
}).catch((reason2) => {
|
|
8167
8416
|
this.#initPromise = undefined;
|
|
8168
|
-
throw
|
|
8417
|
+
throw reason2;
|
|
8169
8418
|
});
|
|
8170
8419
|
await waitOrAbort(this.#initPromise, options?.signal, "init");
|
|
8171
8420
|
}
|
|
@@ -8226,9 +8475,9 @@ class RuntimeDriver {
|
|
|
8226
8475
|
return;
|
|
8227
8476
|
}
|
|
8228
8477
|
await waitOrAbort(this.#initPromise, options?.signal, "destroy:initPromise");
|
|
8229
|
-
this.#destroyPromise ??= this.#driver.destroy(options).catch((
|
|
8478
|
+
this.#destroyPromise ??= this.#driver.destroy(options).catch((reason2) => {
|
|
8230
8479
|
this.#destroyPromise = undefined;
|
|
8231
|
-
throw
|
|
8480
|
+
throw reason2;
|
|
8232
8481
|
});
|
|
8233
8482
|
await waitOrAbort(this.#destroyPromise, options?.signal, "destroy");
|
|
8234
8483
|
}
|
|
@@ -10959,247 +11208,6 @@ function sqliteCode(e) {
|
|
|
10959
11208
|
|
|
10960
11209
|
// server/src/db-write.ts
|
|
10961
11210
|
import { constants as C2, DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
10962
|
-
|
|
10963
|
-
// server/src/text.ts
|
|
10964
|
-
import crypto from "node:crypto";
|
|
10965
|
-
var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
|
|
10966
|
-
var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
|
|
10967
|
-
var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
|
|
10968
|
-
var IDENT = /#\d+|[a-z0-9][a-z0-9_./#-]*[a-z0-9]/g;
|
|
10969
|
-
var MAX_TERM = 100;
|
|
10970
|
-
var OKURIGANA = /^(\p{Script=Han}+)[\p{Script=Hiragana}ー]+$/u;
|
|
10971
|
-
function singular(w) {
|
|
10972
|
-
if (!/^[a-z]{4,}$/.test(w))
|
|
10973
|
-
return w;
|
|
10974
|
-
if (w.endsWith("ies"))
|
|
10975
|
-
return `${w.slice(0, -3)}y`;
|
|
10976
|
-
if (w.endsWith("ie"))
|
|
10977
|
-
return `${w.slice(0, -2)}y`;
|
|
10978
|
-
if (/(?:ss|us|x|z|ch|sh)es$/.test(w))
|
|
10979
|
-
return w.slice(0, -2);
|
|
10980
|
-
if (/(?:ch|sh|us)e$/.test(w))
|
|
10981
|
-
return w.slice(0, -1);
|
|
10982
|
-
return w.endsWith("s") && !/(?:ss|us|is)$/.test(w) ? w.slice(0, -1) : w;
|
|
10983
|
-
}
|
|
10984
|
-
function terms(text) {
|
|
10985
|
-
const norm = text.normalize("NFKC").toLowerCase();
|
|
10986
|
-
const out = [];
|
|
10987
|
-
const keep = (w) => {
|
|
10988
|
-
if (w.length > MAX_TERM || STOP.has(w) || HIRAGANA_ONLY.test(w))
|
|
10989
|
-
return;
|
|
10990
|
-
out.push(OKURIGANA.exec(w)?.[1] ?? singular(w));
|
|
10991
|
-
};
|
|
10992
|
-
for (const s of segmenter.segment(norm))
|
|
10993
|
-
if (s.isWordLike)
|
|
10994
|
-
keep(s.segment.trim());
|
|
10995
|
-
for (const m of norm.matchAll(IDENT))
|
|
10996
|
-
if (m[0].length >= 3)
|
|
10997
|
-
keep(m[0]);
|
|
10998
|
-
return out.filter(Boolean);
|
|
10999
|
-
}
|
|
11000
|
-
var QUESTION = new Set([
|
|
11001
|
-
...[
|
|
11002
|
-
"why",
|
|
11003
|
-
"what",
|
|
11004
|
-
"which",
|
|
11005
|
-
"when",
|
|
11006
|
-
"where",
|
|
11007
|
-
"who",
|
|
11008
|
-
"how",
|
|
11009
|
-
"do",
|
|
11010
|
-
"does",
|
|
11011
|
-
"did",
|
|
11012
|
-
"we",
|
|
11013
|
-
"our",
|
|
11014
|
-
"us",
|
|
11015
|
-
"you",
|
|
11016
|
-
"i",
|
|
11017
|
-
"are",
|
|
11018
|
-
"was",
|
|
11019
|
-
"were"
|
|
11020
|
-
],
|
|
11021
|
-
...[
|
|
11022
|
-
"will",
|
|
11023
|
-
"would",
|
|
11024
|
-
"should",
|
|
11025
|
-
"can",
|
|
11026
|
-
"could",
|
|
11027
|
-
"there",
|
|
11028
|
-
"this",
|
|
11029
|
-
"that",
|
|
11030
|
-
"with",
|
|
11031
|
-
"from",
|
|
11032
|
-
"any",
|
|
11033
|
-
"ever",
|
|
11034
|
-
"long",
|
|
11035
|
-
"use",
|
|
11036
|
-
"used",
|
|
11037
|
-
"using"
|
|
11038
|
-
],
|
|
11039
|
-
...["add", "get", "make", "have", "has", "not", "no", "yes", "reason"],
|
|
11040
|
-
...["理由", "仕組", "何", "方", "場合", "今", "件"]
|
|
11041
|
-
]);
|
|
11042
|
-
var sha256 = (s) => crypto.createHash("sha256").update(s).digest();
|
|
11043
|
-
function uuidFrom(...parts) {
|
|
11044
|
-
const b = crypto.createHash("sha256").update(parts.join("\x00")).digest().subarray(0, 16);
|
|
11045
|
-
b[6] = (b[6] ?? 0) & 15 | 128;
|
|
11046
|
-
b[8] = (b[8] ?? 0) & 63 | 128;
|
|
11047
|
-
const h = b.toString("hex");
|
|
11048
|
-
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
11049
|
-
}
|
|
11050
|
-
var bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
11051
|
-
function head(s, n) {
|
|
11052
|
-
if (bytes(s) <= n)
|
|
11053
|
-
return s;
|
|
11054
|
-
let out = "";
|
|
11055
|
-
let used = 0;
|
|
11056
|
-
for (const ch of s) {
|
|
11057
|
-
const b = bytes(ch);
|
|
11058
|
-
if (used + b > n)
|
|
11059
|
-
break;
|
|
11060
|
-
out += ch;
|
|
11061
|
-
used += b;
|
|
11062
|
-
}
|
|
11063
|
-
return out;
|
|
11064
|
-
}
|
|
11065
|
-
function tail(s, n) {
|
|
11066
|
-
if (bytes(s) <= n)
|
|
11067
|
-
return s;
|
|
11068
|
-
const chars = [...s];
|
|
11069
|
-
let used = 0;
|
|
11070
|
-
let i = chars.length;
|
|
11071
|
-
while (i > 0) {
|
|
11072
|
-
const b = bytes(chars[i - 1] ?? "");
|
|
11073
|
-
if (used + b > n)
|
|
11074
|
-
break;
|
|
11075
|
-
used += b;
|
|
11076
|
-
i--;
|
|
11077
|
-
}
|
|
11078
|
-
return chars.slice(i).join("");
|
|
11079
|
-
}
|
|
11080
|
-
var clean = (s) => s.replaceAll("\x00", "");
|
|
11081
|
-
var visible = (s) => s.replace(/(?!\p{Join_Control}|\p{Variation_Selector})\p{Default_Ignorable_Code_Point}/gu, "");
|
|
11082
|
-
var SECRETS = [
|
|
11083
|
-
[/\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}/g, "API key"],
|
|
11084
|
-
[/\b[srp]k_(?:live|test)_[A-Za-z0-9]{16,}/g, "API key"],
|
|
11085
|
-
[/\bwhsec_[A-Za-z0-9+/=]{16,}/g, "webhook signing secret"],
|
|
11086
|
-
[/\bpa-[A-Za-z0-9_-]{20,}/g, "API key"],
|
|
11087
|
-
[/\bAIza[0-9A-Za-z_-]{35}/g, "API key"],
|
|
11088
|
-
[/\bnpg_[A-Za-z0-9]{12,}/g, "database password"],
|
|
11089
|
-
[/\bnapi_[A-Za-z0-9]{30,}/g, "API key"],
|
|
11090
|
-
[/\bnpm_[A-Za-z0-9]{36}\b/g, "npm token"],
|
|
11091
|
-
[/\bglpat-[A-Za-z0-9_-]{20,}/g, "GitLab token"],
|
|
11092
|
-
[/\bgh[pousr]_[A-Za-z0-9]{30,}/g, "GitHub token"],
|
|
11093
|
-
[/\bgithub_pat_[A-Za-z0-9_]{40,}/g, "GitHub token"],
|
|
11094
|
-
[/\bxox[abprs]-[A-Za-z0-9-]{10,}/g, "Slack token"],
|
|
11095
|
-
[/https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/g, "Slack webhook"],
|
|
11096
|
-
[/\bAKIA[0-9A-Z]{16}\b/g, "AWS key"],
|
|
11097
|
-
[/(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "JWT"],
|
|
11098
|
-
[/\b(?:Bearer|BEARER)\s+(?=[A-Za-z0-9._~+/=-]{0,512}\d)[A-Za-z0-9._~+/=-]{16,}/g, "auth header value"]
|
|
11099
|
-
];
|
|
11100
|
-
var AUTH_HEADER = /(\bAuthorization["']?\s*[:=]\s*(?:["']\s*)?(?:Bearer|Basic|Token|Digest)\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
11101
|
-
var HEADER_BEARER = /(:[ \t]*(?:["'][ \t]*)?bearer[ \t]+)[A-Za-z0-9._~+/=-]{16,}/gi;
|
|
11102
|
-
var ENV_ASSIGN = /\b((?:[A-Z][A-Z0-9_]*_)?(?:API|SECRET|MASTER|ENCRYPTION|PRIVATE|ACCESS|SIGNING|AUTH)?KEY|[A-Z][A-Z0-9_]*_(?:PASS|PWD)|(?:[A-Z][A-Z0-9_]*?)?(?:TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIALS?))(\s*=\s*)(?:"(?!\$)[^"\n]+"|'(?!\$)[^'\n]+'|(?![$"'])[^\s"']+)/g;
|
|
11103
|
-
var FIELD_NAME = /(?:(?:api|account|access|private|secret)[-_]?key|secret|token|passw(?:or)?d)["']?\s*(?::=|=>|[:=])\s*/gi;
|
|
11104
|
-
var MAX_QUOTED = 4096;
|
|
11105
|
-
var BARE_HEAD = /[^\s"',;)]{1,256}/y;
|
|
11106
|
-
var BARE_REST = /[^\s"',;)]*/y;
|
|
11107
|
-
var NEXT_PARAM = /&[A-Za-z_][\w.-]*=/;
|
|
11108
|
-
var bareAt = (re, text, at) => {
|
|
11109
|
-
re.lastIndex = at;
|
|
11110
|
-
const v = re.exec(text)?.[0] ?? "";
|
|
11111
|
-
const cut = v.search(NEXT_PARAM);
|
|
11112
|
-
return cut < 0 ? v : v.slice(0, cut);
|
|
11113
|
-
};
|
|
11114
|
-
function secretValue(quoted, v) {
|
|
11115
|
-
if (v.length < 8 || /^\$(?:\{|[A-Za-z_])/.test(v))
|
|
11116
|
-
return false;
|
|
11117
|
-
if (!quoted)
|
|
11118
|
-
return !/^[#$]/.test(v) && /\d/.test(v) && /[A-Za-z]/.test(v) && !/[()]/.test(v);
|
|
11119
|
-
if (/^#[0-9a-f]{3,8}$/i.test(v) || !/[A-Za-z0-9]/.test(v))
|
|
11120
|
-
return false;
|
|
11121
|
-
return !/\s/.test(v) || v.split(/\s+/).some((w) => /\d/.test(w) && /[A-Za-z]/.test(w));
|
|
11122
|
-
}
|
|
11123
|
-
function maskFields(text) {
|
|
11124
|
-
let out = "";
|
|
11125
|
-
let last = 0;
|
|
11126
|
-
FIELD_NAME.lastIndex = 0;
|
|
11127
|
-
for (let m = FIELD_NAME.exec(text);m; m = FIELD_NAME.exec(text)) {
|
|
11128
|
-
const at = m.index + m[0].length;
|
|
11129
|
-
const q = text[at];
|
|
11130
|
-
let quote = "";
|
|
11131
|
-
let value;
|
|
11132
|
-
if (q === '"' || q === "'") {
|
|
11133
|
-
const close = text.indexOf(q, at + 1);
|
|
11134
|
-
if (close < 0 || close - at - 1 > MAX_QUOTED)
|
|
11135
|
-
continue;
|
|
11136
|
-
value = text.slice(at + 1, close);
|
|
11137
|
-
if (value.includes(`
|
|
11138
|
-
`))
|
|
11139
|
-
continue;
|
|
11140
|
-
quote = q;
|
|
11141
|
-
} else {
|
|
11142
|
-
value = bareAt(BARE_HEAD, text, at);
|
|
11143
|
-
}
|
|
11144
|
-
if (!secretValue(quote !== "", value))
|
|
11145
|
-
continue;
|
|
11146
|
-
if (!quote && value.length === 256)
|
|
11147
|
-
value += bareAt(BARE_REST, text, at + 256);
|
|
11148
|
-
out += `${text.slice(last, at)}${quote}[redacted]`;
|
|
11149
|
-
last = at + quote.length + value.length;
|
|
11150
|
-
FIELD_NAME.lastIndex = last;
|
|
11151
|
-
}
|
|
11152
|
-
return out + text.slice(last);
|
|
11153
|
-
}
|
|
11154
|
-
var MYSQL_COMMAND = /\bmysql(?:dump|admin)?\b(?:'[^'\n]*'|"[^"\n]*"|[^\n;&|\\'"]|\\\r?\n|\\(?!\r?\n))*/g;
|
|
11155
|
-
var MYSQL_PASSWORD = /(\s-p)(?:'[^'\n]*'|"[^"\n]*"|(?=[^\s-])\S+)/;
|
|
11156
|
-
var URL_CREDENTIALS = /\b((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?|https?):\/\/[^:\s/@]*:)[^\s/]*@([^@\s/?#]+)/g;
|
|
11157
|
-
var KEY_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/g;
|
|
11158
|
-
var KEY_END = /-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
11159
|
-
function maskPrivateKeys(text) {
|
|
11160
|
-
const ends = [...text.matchAll(KEY_END)].map((m) => [m.index, m.index + m[0].length]);
|
|
11161
|
-
if (ends.length === 0)
|
|
11162
|
-
return text;
|
|
11163
|
-
let out = "";
|
|
11164
|
-
let last = 0;
|
|
11165
|
-
let e = 0;
|
|
11166
|
-
for (const m of text.matchAll(KEY_BEGIN)) {
|
|
11167
|
-
const after = m.index + m[0].length;
|
|
11168
|
-
if (m.index < last)
|
|
11169
|
-
continue;
|
|
11170
|
-
while (e < ends.length && (ends[e]?.[0] ?? 0) < after)
|
|
11171
|
-
e++;
|
|
11172
|
-
const end = ends[e];
|
|
11173
|
-
if (!end)
|
|
11174
|
-
break;
|
|
11175
|
-
out += `${text.slice(last, m.index)}[redacted: private key]`;
|
|
11176
|
-
last = end[1];
|
|
11177
|
-
}
|
|
11178
|
-
return out + text.slice(last);
|
|
11179
|
-
}
|
|
11180
|
-
function mask(text) {
|
|
11181
|
-
let out = maskFields(maskPrivateKeys(text).replace(URL_CREDENTIALS, "$1[redacted]@$2").replace(AUTH_HEADER, "$1[redacted]").replace(HEADER_BEARER, "$1[redacted]").replace(ENV_ASSIGN, "$1$2[redacted]")).replace(MYSQL_COMMAND, (command) => command.replace(MYSQL_PASSWORD, "$1[redacted]"));
|
|
11182
|
-
for (const [re, what] of SECRETS)
|
|
11183
|
-
out = out.replace(re, `[redacted: ${what}]`);
|
|
11184
|
-
return out;
|
|
11185
|
-
}
|
|
11186
|
-
var reason = (e) => explain(e, 0) || "unknown failure";
|
|
11187
|
-
var plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
11188
|
-
function explain(e, depth) {
|
|
11189
|
-
if (!(e instanceof Error)) {
|
|
11190
|
-
try {
|
|
11191
|
-
return String(e);
|
|
11192
|
-
} catch {
|
|
11193
|
-
return "";
|
|
11194
|
-
}
|
|
11195
|
-
}
|
|
11196
|
-
const own = e.message || (e.name === "Error" || e.name === "AggregateError" ? "" : e.name);
|
|
11197
|
-
const parts = depth >= 3 ? [] : [...e instanceof AggregateError ? e.errors : [], ...e.cause === undefined ? [] : [e.cause]];
|
|
11198
|
-
const inner = parts.map((x) => explain(x, depth + 1)).filter(Boolean).join(" / ");
|
|
11199
|
-
return own && inner ? `${own} (${inner})` : own || inner;
|
|
11200
|
-
}
|
|
11201
|
-
|
|
11202
|
-
// server/src/db-write.ts
|
|
11203
11211
|
var ddl = null;
|
|
11204
11212
|
var DDL = () => ddl ??= new Set([
|
|
11205
11213
|
"SQLITE_ALTER_TABLE",
|
package/dist/mcp-record.js
CHANGED
|
@@ -45822,27 +45822,80 @@ var MYSQL_PASSWORD = /(\s-p)(?:'[^'\n]*'|"[^"\n]*"|(?=[^\s-])\S+)/;
|
|
|
45822
45822
|
var URL_CREDENTIALS = /\b((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?|https?):\/\/[^:\s/@]*:)[^\s/]*@([^@\s/?#]+)/g;
|
|
45823
45823
|
var KEY_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/g;
|
|
45824
45824
|
var KEY_END = /-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
45825
|
-
function
|
|
45825
|
+
function keyBlocks(text) {
|
|
45826
45826
|
const ends = [...text.matchAll(KEY_END)].map((m) => [m.index, m.index + m[0].length]);
|
|
45827
|
-
|
|
45828
|
-
return text;
|
|
45829
|
-
let out = "";
|
|
45827
|
+
const out = [];
|
|
45830
45828
|
let last = 0;
|
|
45831
45829
|
let e = 0;
|
|
45832
|
-
|
|
45833
|
-
const
|
|
45834
|
-
|
|
45835
|
-
|
|
45836
|
-
|
|
45837
|
-
e
|
|
45838
|
-
|
|
45839
|
-
|
|
45840
|
-
|
|
45841
|
-
|
|
45842
|
-
|
|
45830
|
+
if (ends.length > 0)
|
|
45831
|
+
for (const m of text.matchAll(KEY_BEGIN)) {
|
|
45832
|
+
const after = m.index + m[0].length;
|
|
45833
|
+
if (m.index < last)
|
|
45834
|
+
continue;
|
|
45835
|
+
while (e < ends.length && (ends[e]?.[0] ?? 0) < after)
|
|
45836
|
+
e++;
|
|
45837
|
+
const end = ends[e];
|
|
45838
|
+
if (!end)
|
|
45839
|
+
break;
|
|
45840
|
+
out.push([m.index, end[1]]);
|
|
45841
|
+
last = end[1];
|
|
45842
|
+
}
|
|
45843
|
+
return out;
|
|
45844
|
+
}
|
|
45845
|
+
function maskPrivateKeys(text) {
|
|
45846
|
+
let out = "";
|
|
45847
|
+
let last = 0;
|
|
45848
|
+
for (const [a, b] of keyBlocks(text)) {
|
|
45849
|
+
out += `${text.slice(last, a)}[redacted: private key]`;
|
|
45850
|
+
last = b;
|
|
45843
45851
|
}
|
|
45844
45852
|
return out + text.slice(last);
|
|
45845
45853
|
}
|
|
45854
|
+
function privateKeyRanges(text) {
|
|
45855
|
+
return byteRanges(text, keyBlocks(text));
|
|
45856
|
+
}
|
|
45857
|
+
function byteRanges(text, ranges) {
|
|
45858
|
+
const out = [];
|
|
45859
|
+
let at = 0;
|
|
45860
|
+
let pos = 0;
|
|
45861
|
+
for (const [a, b] of ranges) {
|
|
45862
|
+
pos += bytes(text.slice(at, a));
|
|
45863
|
+
const end = pos + bytes(text.slice(a, b));
|
|
45864
|
+
out.push([pos, end]);
|
|
45865
|
+
at = b;
|
|
45866
|
+
pos = end;
|
|
45867
|
+
}
|
|
45868
|
+
return out;
|
|
45869
|
+
}
|
|
45870
|
+
var PLACEHOLDER = /\[redacted(?:: [^\]\n]{1,40})?\]/g;
|
|
45871
|
+
function placeholderRanges(masked) {
|
|
45872
|
+
return [...masked.matchAll(PLACEHOLDER)].map((x) => [x.index, x.index + x[0].length]);
|
|
45873
|
+
}
|
|
45874
|
+
function starts(hay, needle) {
|
|
45875
|
+
const out = [];
|
|
45876
|
+
for (let at = hay.indexOf(needle);at >= 0; at = hay.indexOf(needle, at + 1))
|
|
45877
|
+
out.push(at);
|
|
45878
|
+
return out;
|
|
45879
|
+
}
|
|
45880
|
+
function quoteSpan(raw, masked, quote) {
|
|
45881
|
+
const q = Buffer.from(quote, "utf8");
|
|
45882
|
+
if (q.length === 0)
|
|
45883
|
+
return null;
|
|
45884
|
+
const m = Buffer.from(masked, "utf8");
|
|
45885
|
+
if (raw === masked) {
|
|
45886
|
+
const at = m.indexOf(q);
|
|
45887
|
+
return at < 0 ? null : [at, at + q.length];
|
|
45888
|
+
}
|
|
45889
|
+
const holes = byteRanges(masked, placeholderRanges(masked));
|
|
45890
|
+
let h = 0;
|
|
45891
|
+
const kept = starts(m, q).filter((at) => {
|
|
45892
|
+
while ((holes[h]?.[1] ?? Number.POSITIVE_INFINITY) <= at)
|
|
45893
|
+
h++;
|
|
45894
|
+
return !((holes[h]?.[0] ?? Number.POSITIVE_INFINITY) < at + q.length);
|
|
45895
|
+
});
|
|
45896
|
+
const first = kept[0];
|
|
45897
|
+
return first !== undefined && kept.length === starts(Buffer.from(raw, "utf8"), q).length ? [first, first + q.length] : null;
|
|
45898
|
+
}
|
|
45846
45899
|
function mask(text) {
|
|
45847
45900
|
let out = maskFields(maskPrivateKeys(text).replace(URL_CREDENTIALS, "$1[redacted]@$2").replace(AUTH_HEADER, "$1[redacted]").replace(HEADER_BEARER, "$1[redacted]").replace(ENV_ASSIGN, "$1$2[redacted]")).replace(MYSQL_COMMAND, (command) => command.replace(MYSQL_PASSWORD, "$1[redacted]"));
|
|
45848
45901
|
for (const [re, what] of SECRETS)
|
|
@@ -46053,14 +46106,61 @@ function readText(root, rel) {
|
|
|
46053
46106
|
function findSymbol(text, symbol2) {
|
|
46054
46107
|
const re = new RegExp(`(?<![\\w$])${literal3(symbol2)}(?![\\w$])`);
|
|
46055
46108
|
const lines = text.split(/\r?\n/);
|
|
46056
|
-
|
|
46057
|
-
|
|
46109
|
+
return { lines, i: lines.findIndex((l) => re.test(l)) };
|
|
46110
|
+
}
|
|
46111
|
+
function swallowed(root, rel, symbol2) {
|
|
46112
|
+
if (mask(symbol2) !== symbol2)
|
|
46113
|
+
return true;
|
|
46114
|
+
const text = root ? readText(root, rel) : null;
|
|
46115
|
+
if (text === undefined)
|
|
46116
|
+
return true;
|
|
46117
|
+
if (text === null)
|
|
46118
|
+
return false;
|
|
46119
|
+
const re = new RegExp(`(?<![\\w$])${literal3(symbol2)}(?![\\w$])`, "g");
|
|
46120
|
+
let raw = 0;
|
|
46121
|
+
for (let m = re.exec(text);m; m = re.exec(text))
|
|
46122
|
+
raw++;
|
|
46123
|
+
if (raw === 0)
|
|
46124
|
+
return false;
|
|
46125
|
+
const masked = mask(text);
|
|
46126
|
+
const holes = placeholderRanges(masked);
|
|
46127
|
+
let h = 0;
|
|
46128
|
+
let kept = 0;
|
|
46129
|
+
re.lastIndex = 0;
|
|
46130
|
+
for (let m = re.exec(masked);m; m = re.exec(masked)) {
|
|
46131
|
+
while ((holes[h]?.[1] ?? Number.POSITIVE_INFINITY) < m.index)
|
|
46132
|
+
h++;
|
|
46133
|
+
if (!((holes[h]?.[0] ?? Number.POSITIVE_INFINITY) <= m.index + symbol2.length))
|
|
46134
|
+
kept++;
|
|
46135
|
+
}
|
|
46136
|
+
return kept !== raw;
|
|
46137
|
+
}
|
|
46138
|
+
function masksSymbol(root, rel, symbol2) {
|
|
46139
|
+
const trimmed = symbol2.trim();
|
|
46140
|
+
return swallowed(root, rel, symbol2) || trimmed !== symbol2 && trimmed !== "" && swallowed(root, rel, trimmed);
|
|
46058
46141
|
}
|
|
46059
46142
|
function locate(root, rel, symbol2) {
|
|
46060
46143
|
if (!root)
|
|
46061
46144
|
return null;
|
|
46062
46145
|
const text = readText(root, rel);
|
|
46063
|
-
|
|
46146
|
+
if (typeof text !== "string")
|
|
46147
|
+
return null;
|
|
46148
|
+
const { lines, i } = findSymbol(text, symbol2);
|
|
46149
|
+
if (i < 0)
|
|
46150
|
+
return null;
|
|
46151
|
+
const line = lines[i] ?? "";
|
|
46152
|
+
const from = bytes(lines.slice(0, i).join(`
|
|
46153
|
+
`)) + (i > 0 ? 1 : 0);
|
|
46154
|
+
if (privateKeyRanges(lines.join(`
|
|
46155
|
+
`)).some(([a, b]) => a < from + bytes(line) && b > from))
|
|
46156
|
+
return { line: i + 1, excerpt: "[redacted: private key]" };
|
|
46157
|
+
const before = lines.slice(0, i).map((l) => `${l}
|
|
46158
|
+
`).join("");
|
|
46159
|
+
const after = lines.slice(i + 1).map((l) => `
|
|
46160
|
+
${l}`).join("");
|
|
46161
|
+
const own2 = mask(line);
|
|
46162
|
+
const cut = mask(before + line) !== mask(before) + own2 || mask(line + after) !== own2 + mask(after);
|
|
46163
|
+
return { line: i + 1, excerpt: cut ? "[redacted]" : own2.trim().slice(0, 200) };
|
|
46064
46164
|
}
|
|
46065
46165
|
|
|
46066
46166
|
// server/src/project.ts
|
|
@@ -46750,9 +46850,23 @@ import { promisify } from "node:util";
|
|
|
46750
46850
|
var exec = promisify(execFile);
|
|
46751
46851
|
var MAX_ISSUES = 5;
|
|
46752
46852
|
var CLOSES = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+(?:([\w.-]+\/[\w.-]+)#|https:\/\/github\.com\/([\w.-]+\/[\w.-]+)\/issues\/|#)(\d{1,9})\b/gi;
|
|
46853
|
+
function visible2(body) {
|
|
46854
|
+
let out = "";
|
|
46855
|
+
let at = 0;
|
|
46856
|
+
for (;; ) {
|
|
46857
|
+
const open2 = body.indexOf("<!--", at);
|
|
46858
|
+
if (open2 < 0)
|
|
46859
|
+
return out + body.slice(at);
|
|
46860
|
+
out += body.slice(at, open2);
|
|
46861
|
+
const close = body.indexOf("-->", open2 + 4);
|
|
46862
|
+
if (close < 0)
|
|
46863
|
+
return out;
|
|
46864
|
+
at = close + 3;
|
|
46865
|
+
}
|
|
46866
|
+
}
|
|
46753
46867
|
function closingRefs(body, repo) {
|
|
46754
46868
|
const here = repo?.toLowerCase();
|
|
46755
|
-
return [...body.matchAll(CLOSES)].flatMap((m) => {
|
|
46869
|
+
return [...visible2(body).matchAll(CLOSES)].flatMap((m) => {
|
|
46756
46870
|
const named = (m[1] ?? m[2])?.toLowerCase();
|
|
46757
46871
|
return !named || named === here ? [Number(m[3])] : [];
|
|
46758
46872
|
});
|
|
@@ -47198,19 +47312,36 @@ async function checkRecord(db, target, raw) {
|
|
|
47198
47312
|
adoption.push({ source: s.id, start: span[0], end: span[1], route });
|
|
47199
47313
|
}
|
|
47200
47314
|
const anchors = [];
|
|
47315
|
+
const fallbacks = new Set;
|
|
47201
47316
|
for (const a of u.anchors) {
|
|
47202
47317
|
const p = repoPath(a.path);
|
|
47203
47318
|
if (!p) {
|
|
47204
47319
|
problems.push(`${key}: anchor path ${JSON.stringify(head(a.path, 80))} is not inside the repository; left out`);
|
|
47205
47320
|
continue;
|
|
47206
47321
|
}
|
|
47322
|
+
const symbol2 = a.symbol && masksSymbol(target.root, p, a.symbol) ? undefined : a.symbol;
|
|
47323
|
+
if (a.symbol && !symbol2)
|
|
47324
|
+
problems.push(`${key}: anchor symbol in ${p} is text Sphica masks; the anchor keeps only its path`);
|
|
47207
47325
|
let commit = a.commit;
|
|
47208
47326
|
if (commit && !(target.root && commitHolds(target.root, commit, p))) {
|
|
47209
47327
|
problems.push(`${key}: commit ${commit.slice(0, 12)} does not hold ${p} in the repository${target.root ? "" : " (no working tree to check)"}; the anchor keeps no commit`);
|
|
47210
47328
|
commit = undefined;
|
|
47211
47329
|
}
|
|
47212
47330
|
const observation = a.role === "evidence" && !commit && target.sessionId ? (await db.selectFrom("edit_observation").select("id").where("session_id", "=", target.sessionId).where("path", "=", p).orderBy("id", "desc").executeTakeFirst())?.id ?? null : null;
|
|
47213
|
-
|
|
47331
|
+
const planned = { ...a, symbol: symbol2, commit, path: p, observation };
|
|
47332
|
+
if (a.symbol && !symbol2)
|
|
47333
|
+
fallbacks.add(planned);
|
|
47334
|
+
anchors.push(planned);
|
|
47335
|
+
}
|
|
47336
|
+
const place = (x) => `${x.path}\x00${x.role}\x00${x.commit}\x00${x.lines ? [x.lines[0], Math.max(...x.lines)] : ""}`;
|
|
47337
|
+
const covered = new Set(anchors.filter((x) => !x.symbol && !fallbacks.has(x)).map(place));
|
|
47338
|
+
for (const x of [...fallbacks]) {
|
|
47339
|
+
if (!covered.has(place(x))) {
|
|
47340
|
+
covered.add(place(x));
|
|
47341
|
+
continue;
|
|
47342
|
+
}
|
|
47343
|
+
problems.push(`${key}: another path-only anchor on ${x.path} already covers it; left out`);
|
|
47344
|
+
anchors.splice(anchors.indexOf(x), 1);
|
|
47214
47345
|
}
|
|
47215
47346
|
const aliases = [...new Set(u.aliases.map((a) => a.trim()))];
|
|
47216
47347
|
const bad = aliases.filter((a) => !a || a.length > 40);
|
|
@@ -47332,12 +47463,13 @@ ${checked.errors.map((e) => ` ${e}`).join(`
|
|
|
47332
47463
|
}).onConflict((oc) => oc.doNothing()).execute();
|
|
47333
47464
|
}
|
|
47334
47465
|
for (const a of p.anchors) {
|
|
47335
|
-
const
|
|
47466
|
+
const symbol2 = a.symbol && !masksSymbol(target.root, a.path, a.symbol) ? a.symbol : undefined;
|
|
47467
|
+
const at = a.lines ? null : symbol2 ? locate(target.root, a.path, symbol2) : null;
|
|
47336
47468
|
const lines = a.lines ?? (at ? [at.line, at.line] : null);
|
|
47337
47469
|
await trx.insertInto("unit_anchor").values({
|
|
47338
47470
|
unit_id: id,
|
|
47339
47471
|
path: a.path,
|
|
47340
|
-
symbol:
|
|
47472
|
+
symbol: symbol2 ?? null,
|
|
47341
47473
|
commit_sha: a.commit ?? null,
|
|
47342
47474
|
line_start: lines?.[0] ?? null,
|
|
47343
47475
|
line_end: lines ? Math.max(lines[0], lines[1]) : null,
|
|
@@ -47535,15 +47667,24 @@ function readExcerpt(root, file2) {
|
|
|
47535
47667
|
} catch {
|
|
47536
47668
|
throw new Error(`${p} is not UTF-8 text`);
|
|
47537
47669
|
}
|
|
47538
|
-
const
|
|
47670
|
+
const starts2 = [0];
|
|
47539
47671
|
for (let i = 0;i < buf.length; i++)
|
|
47540
47672
|
if (buf[i] === 10 && i + 1 < buf.length)
|
|
47541
|
-
|
|
47673
|
+
starts2.push(i + 1);
|
|
47542
47674
|
const [a, b] = file2.lines;
|
|
47543
|
-
if (a > b || b >
|
|
47544
|
-
throw new Error(`${p} has ${
|
|
47545
|
-
const
|
|
47546
|
-
|
|
47675
|
+
if (a > b || b > starts2.length)
|
|
47676
|
+
throw new Error(`${p} has ${starts2.length} lines; lines ${a}-${b} are not in it`);
|
|
47677
|
+
const from = starts2[a - 1] ?? 0;
|
|
47678
|
+
const to = b < starts2.length ? starts2[b] ?? buf.length : buf.length;
|
|
47679
|
+
if (privateKeyRanges(buf.toString("utf8")).some(([s, e]) => s < to && e > from && (s < from || e > to)))
|
|
47680
|
+
throw new Error(`${p} lines ${a}-${b} are inside a private key; cite lines outside it`);
|
|
47681
|
+
const raw = buf.subarray(from, to).toString("utf8");
|
|
47682
|
+
const text2 = mask(raw);
|
|
47683
|
+
const before = buf.subarray(0, from).toString("utf8");
|
|
47684
|
+
const after = buf.subarray(to).toString("utf8");
|
|
47685
|
+
if (mask(before + raw) !== mask(before) + text2 || mask(raw + after) !== text2 + mask(after))
|
|
47686
|
+
throw new Error(`${p} lines ${a}-${b} cut through text Sphica masks; cite more lines around it`);
|
|
47687
|
+
return { path: p, commit, blob, size, lines: [a, b], raw, text: text2 };
|
|
47547
47688
|
}
|
|
47548
47689
|
var locate3 = (body, q) => {
|
|
47549
47690
|
const at = Buffer.from(body, "utf8").indexOf(Buffer.from(q, "utf8"));
|
|
@@ -47607,8 +47748,10 @@ async function checkGlean(db, target, raw) {
|
|
|
47607
47748
|
if (!target.root)
|
|
47608
47749
|
throw new Error("the repository is not known");
|
|
47609
47750
|
excerpt = readExcerpt(target.root, op.file);
|
|
47610
|
-
if (!locate3(excerpt.
|
|
47751
|
+
if (!locate3(excerpt.raw, op.quote))
|
|
47611
47752
|
errors3.push(`${what}: quote not found in ${excerpt.path} lines ${op.file.lines.join("-")}`);
|
|
47753
|
+
else if (!quoteSpan(excerpt.raw, excerpt.text, op.quote))
|
|
47754
|
+
errors3.push(`${what}: the quote also appears in text Sphica masks; quote a longer or different part`);
|
|
47612
47755
|
} catch (e) {
|
|
47613
47756
|
errors3.push(`${what}: ${e.message}`);
|
|
47614
47757
|
}
|
|
@@ -47629,6 +47772,10 @@ async function checkGlean(db, target, raw) {
|
|
|
47629
47772
|
}
|
|
47630
47773
|
if (op.op === "anchor" && !repoPath(op.path))
|
|
47631
47774
|
errors3.push(`${what}: the path is not inside the repository`);
|
|
47775
|
+
const pinned = op.op === "anchor" ? op : op.op === "replace_anchor" ? op.to : null;
|
|
47776
|
+
const pinnedPath = pinned && repoPath(pinned.path);
|
|
47777
|
+
if (pinned?.symbol && pinnedPath && masksSymbol(target.root, pinnedPath, pinned.symbol))
|
|
47778
|
+
errors3.push(`${what}: the symbol is text Sphica masks; anchor a name, not a key`);
|
|
47632
47779
|
if (op.op === "anchor" && op.commit) {
|
|
47633
47780
|
const rel2 = repoPath(op.path);
|
|
47634
47781
|
if (rel2 && !(target.root && commitHolds(target.root, op.commit, rel2)))
|
|
@@ -47718,24 +47865,25 @@ async function checkGlean(db, target, raw) {
|
|
|
47718
47865
|
}
|
|
47719
47866
|
async function excerptSource(trx, projectId2, x) {
|
|
47720
47867
|
const external2 = `file:${x.path}@${x.blob}#L${x.lines[0]}-${x.lines[1]}`;
|
|
47721
|
-
const
|
|
47722
|
-
|
|
47868
|
+
const hash2 = sha256(x.text);
|
|
47869
|
+
const found = await trx.selectFrom("source").select(["id", "revision", "content_hash"]).where("project_id", "=", projectId2).where("kind", "=", "file_excerpt").where("external_id", "=", external2).orderBy("revision", "desc").executeTakeFirst();
|
|
47870
|
+
if (found?.content_hash.equals(hash2))
|
|
47723
47871
|
return found.id;
|
|
47724
47872
|
const now = iso(Date.now());
|
|
47725
|
-
const partial2 = Buffer.byteLength(x.text, "utf8") !== x.size;
|
|
47726
47873
|
const row = await trx.insertInto("source").values({
|
|
47727
47874
|
project_id: projectId2,
|
|
47728
47875
|
kind: "file_excerpt",
|
|
47729
47876
|
artifact: `file:${x.path}`,
|
|
47730
47877
|
external_id: external2,
|
|
47731
|
-
revision: 1,
|
|
47878
|
+
revision: (found?.revision ?? 0) + 1,
|
|
47732
47879
|
author_kind: "person",
|
|
47733
47880
|
created_at: now,
|
|
47734
47881
|
captured_at: now,
|
|
47735
47882
|
text: x.text,
|
|
47736
|
-
truncated:
|
|
47883
|
+
truncated: bytes(x.raw) !== x.size ? 1 : 0,
|
|
47884
|
+
redacted: x.text !== x.raw ? 1 : 0,
|
|
47737
47885
|
original_bytes: x.size,
|
|
47738
|
-
content_hash:
|
|
47886
|
+
content_hash: hash2,
|
|
47739
47887
|
path: x.path,
|
|
47740
47888
|
line_start: x.lines[0],
|
|
47741
47889
|
line_end: x.lines[1],
|
|
@@ -47785,7 +47933,10 @@ ${c.errors.map((e) => ` ${e}`).join(`
|
|
|
47785
47933
|
touched.set(p.unitId, op.unit);
|
|
47786
47934
|
if (op.op === "add_evidence") {
|
|
47787
47935
|
const s = p.excerpt ? { id: await excerptSource(trx, target.projectId, p.excerpt), text: p.excerpt.text } : await trx.selectFrom("source").select(["id", "text"]).where("id", "=", Number(op.source?.slice(1))).executeTakeFirstOrThrow();
|
|
47788
|
-
const at = locate3(s.text, op.quote) ?? [
|
|
47936
|
+
const at = (p.excerpt ? quoteSpan(p.excerpt.raw, s.text, op.quote) : locate3(s.text, op.quote)) ?? [
|
|
47937
|
+
0,
|
|
47938
|
+
0
|
|
47939
|
+
];
|
|
47789
47940
|
await trx.insertInto("unit_evidence").values({
|
|
47790
47941
|
unit_id: p.unitId,
|
|
47791
47942
|
source_id: s.id,
|
|
@@ -47812,11 +47963,13 @@ ${c.errors.map((e) => ` ${e}`).join(`
|
|
|
47812
47963
|
changed2.push(`${op.unit}: adopted`);
|
|
47813
47964
|
} else if (op.op === "anchor" || op.op === "replace_anchor") {
|
|
47814
47965
|
const to = op.op === "anchor" ? op : op.to;
|
|
47815
|
-
const
|
|
47966
|
+
const rel = repoPath(to.path) ?? to.path;
|
|
47967
|
+
const symbol2 = to.symbol && !masksSymbol(target.root, rel, to.symbol) ? to.symbol : null;
|
|
47968
|
+
const at = symbol2 ? locate(target.root, rel, symbol2) : null;
|
|
47816
47969
|
const added = await trx.insertInto("unit_anchor").values({
|
|
47817
47970
|
unit_id: p.unitId,
|
|
47818
|
-
path:
|
|
47819
|
-
symbol:
|
|
47971
|
+
path: rel,
|
|
47972
|
+
symbol: symbol2,
|
|
47820
47973
|
commit_sha: op.op === "anchor" ? op.commit ?? null : null,
|
|
47821
47974
|
line_start: at?.line ?? null,
|
|
47822
47975
|
line_end: at?.line ?? null,
|
package/dist/mcp.js
CHANGED
|
@@ -45821,8 +45821,7 @@ function readText(root, rel) {
|
|
|
45821
45821
|
function findSymbol(text, symbol2) {
|
|
45822
45822
|
const re = new RegExp(`(?<![\\w$])${literal3(symbol2)}(?![\\w$])`);
|
|
45823
45823
|
const lines = text.split(/\r?\n/);
|
|
45824
|
-
|
|
45825
|
-
return i < 0 ? null : { line: i + 1, excerpt: (lines[i] ?? "").trim().slice(0, 200) };
|
|
45824
|
+
return { lines, i: lines.findIndex((l) => re.test(l)) };
|
|
45826
45825
|
}
|
|
45827
45826
|
function checkAnchor(root, a) {
|
|
45828
45827
|
if (!root)
|
|
@@ -45834,12 +45833,12 @@ function checkAnchor(root, a) {
|
|
|
45834
45833
|
return { state: "unknown", line: null };
|
|
45835
45834
|
if (!a.symbol)
|
|
45836
45835
|
return { state: "located", line: a.line_start };
|
|
45837
|
-
const
|
|
45838
|
-
if (
|
|
45836
|
+
const { i } = findSymbol(text, a.symbol);
|
|
45837
|
+
if (i < 0)
|
|
45839
45838
|
return { state: "missing", line: null };
|
|
45840
45839
|
return {
|
|
45841
|
-
state: a.line_start === null || a.line_start ===
|
|
45842
|
-
line:
|
|
45840
|
+
state: a.line_start === null || a.line_start === i + 1 ? "located" : "moved",
|
|
45841
|
+
line: i + 1
|
|
45843
45842
|
};
|
|
45844
45843
|
}
|
|
45845
45844
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sphica",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
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/glean/SKILL.md
CHANGED
|
@@ -67,9 +67,9 @@ read tools `search` and `read`. Pass the repository root as `cwd` to every tool.
|
|
|
67
67
|
|
|
68
68
|
| Op | What it does |
|
|
69
69
|
|---|---|
|
|
70
|
-
| `add_evidence` | Cites a `source` ref or a committed `file` (path, commit, lines). `role` as in trace. When the owner reports what someone else said, add `reported_speaker`: it stays the owner's report, never that person's statement or an adoption |
|
|
70
|
+
| `add_evidence` | Cites a `source` ref or a committed `file` (path, commit, lines). `role` as in trace. When the owner reports what someone else said, add `reported_speaker`: it stays the owner's report, never that person's statement or an adoption. A file excerpt is stored with keys masked, so quote words around a key, never the key, and cite whole lines around it: a range that cuts through a private key, or leaves a key's name outside, is refused |
|
|
71
71
|
| `adopt` | The owner's (or a maintainer's) words that settle a decision or constraint. "Kimura said it was agreed" is not adoption; the owner saying "let's make it final" is |
|
|
72
|
-
| `anchor` / `replace_anchor` | Adds a code location, or replaces one whose code moved (`from` and `to`, citing the owner's words); the old one is kept as history. A replacement carries no commit, so an implementation whose proof was the replaced anchor goes back to candidate: add an `anchor` op with `commit` in the same batch to keep it active |
|
|
72
|
+
| `anchor` / `replace_anchor` | Adds a code location, or replaces one whose code moved (`from` and `to`, citing the owner's words); the old one is kept as history. A replacement carries no commit, so an implementation whose proof was the replaced anchor goes back to candidate: add an `anchor` op with `commit` in the same batch to keep it active. A `symbol` that is a key or a value Sphica masks is refused: anchor a name, or the path alone |
|
|
73
73
|
| `retract_evidence` / `retract_adoption` | Marks a link mistaken, citing the owner's words (`reason_source`, `reason_quote`). When the record cites the same source more than once, add `quote` to say which one. It is kept as history, and the record is judged again |
|
|
74
74
|
| `resolve_conflict` | Ends an unresolved conflict between `unit` and `with`, citing the owner's words (`reason_source`, `reason_quote`). Until then neither record is shown on its own |
|
|
75
75
|
| `withdraw` | Withdraws a record the owner says no longer holds, citing the owner's words |
|
package/skills/harvest/SKILL.md
CHANGED
|
@@ -53,7 +53,7 @@ and only by saying so: "we rejected yarn", "let's keep SQLite". check refuses th
|
|
|
53
53
|
## What to record
|
|
54
54
|
|
|
55
55
|
- Options someone proposed and a maintainer declined, with the reason given: a `decision` whose rejected option carries its `why` and evidence
|
|
56
|
-
- What the pull request implemented: an `implementation` citing the commit message or the body, with an `evidence` anchor when a path and symbol are named
|
|
56
|
+
- What the pull request implemented: an `implementation` citing the commit message or the body, with an `evidence` anchor when a path and symbol are named (a `symbol` that is a key or a value Sphica masks is dropped, keeping the path)
|
|
57
57
|
- The problem the closed issue describes, when it states a rule ("exports must never include private notes"): a `constraint` citing the issue body
|
|
58
58
|
- Review findings that led to a change (`finding`), paths tried and abandoned (`dead_end`), questions left open (`question`)
|
|
59
59
|
|
package/skills/trace/SKILL.md
CHANGED
|
@@ -80,7 +80,7 @@ The `"..."` stands for the other language's words: in this example, `"データ
|
|
|
80
80
|
| `evidence` | Required. `source` is a ref from context, `quote` is copied **exactly** from that message (a phrase is enough). `role`: `states`, `proposes`, `rejects`, `explains`, `implements`. When the owner reports what someone else said, add `reported_speaker` |
|
|
81
81
|
| `options` | Options compared, with `outcome` `chosen` / `rejected` / `deferred` / `proposed` and the `why` given. Evidence is optional per option |
|
|
82
82
|
| `adoption` | Decisions and constraints only: the owner's words that settle it. **Only owner messages adopt.** The AI proposing something and the owner not objecting is not adoption; leave it out and the record stays a candidate |
|
|
83
|
-
| `anchors` | Only where the record has a code location: `path` relative to the repository root, `symbol` when there is one, `role` `applies_to` (where it applies) or `evidence` (code that shows it was done; add `commit` when known). When an adopted decision or constraint governs how one existing code location behaves (keeping it as it is included), give it `applies_to` there, even if this work did not change it: delivery shows it when that file is read or edited. Confirm the path in the repository; do not infer one from a broad topic, and leave it unanchored when several places are plausible. `no_code_surface` may say why there is none |
|
|
83
|
+
| `anchors` | Only where the record has a code location: `path` relative to the repository root, `symbol` when there is one, `role` `applies_to` (where it applies) or `evidence` (code that shows it was done; add `commit` when known). When an adopted decision or constraint governs how one existing code location behaves (keeping it as it is included), give it `applies_to` there, even if this work did not change it: delivery shows it when that file is read or edited. Confirm the path in the repository; do not infer one from a broad topic, and leave it unanchored when several places are plausible. `no_code_surface` may say why there is none. A `symbol` must be a name in the code, never a key or a value Sphica masks: such a symbol is dropped and the anchor keeps only its path (save reports it) |
|
|
84
84
|
| `aliases` | 8 to 12 short search words in **both Japanese and English** a later reader might type: synonyms, the other language's words, abbreviations. Search only; never evidence. Not broad words that match everything (`code`, `fix`, `update`) |
|
|
85
85
|
| `supersedes` | The key of a live record this one replaces (context lists them). The old one is marked superseded, never deleted |
|
|
86
86
|
| `conflicts` | Keys of live records this one contradicts without replacing them. Both are held back from automatic injection until resolved |
|