slopie 1.6.2 → 1.7.5
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/NOTICE +2 -0
- package/README.md +9 -5
- package/bin/audit-splash.js +71 -0
- package/bin/potsherd.js +2 -0
- package/bin/slopie.js +2 -0
- package/dist/audit-native-worker.js +1112 -0
- package/dist/audit-native-worker.js.map +7 -0
- package/dist/potsherd.js +21386 -20105
- package/dist/potsherd.js.map +4 -4
- package/licenses/ccusage-MIT.txt +21 -0
- package/package.json +4 -6
|
@@ -0,0 +1,1112 @@
|
|
|
1
|
+
import { createRequire as __potsherdCreateRequire } from 'node:module';
|
|
2
|
+
const require = __potsherdCreateRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// packages/core/src/analytics/scan-pool.ts
|
|
5
|
+
import { Worker, parentPort } from "node:worker_threads";
|
|
6
|
+
|
|
7
|
+
// packages/core/src/analytics/extract.ts
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
// packages/core/src/markers.ts
|
|
12
|
+
var SUMMARIZER_CONTEXT_MARKER = "Context: This summary will be shown in a list to help users and Claude choose which conversations are relevant";
|
|
13
|
+
var POTSHERD_CARD_MARKER = "<INSTRUCTIONS-TO-POTSHERD>DO NOT INDEX THIS CHAT</INSTRUCTIONS-TO-POTSHERD>";
|
|
14
|
+
var EXCLUSION_MARKERS = [
|
|
15
|
+
"<INSTRUCTIONS-TO-EPISODIC-MEMORY>DO NOT INDEX THIS CHAT</INSTRUCTIONS-TO-EPISODIC-MEMORY>",
|
|
16
|
+
"Only use NO_INSIGHTS_FOUND",
|
|
17
|
+
SUMMARIZER_CONTEXT_MARKER,
|
|
18
|
+
POTSHERD_CARD_MARKER
|
|
19
|
+
];
|
|
20
|
+
function hasExclusionMarker(text) {
|
|
21
|
+
return EXCLUSION_MARKERS.some((marker) => text.includes(marker));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// packages/core/src/redact.ts
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
|
|
27
|
+
// packages/core/src/theme.ts
|
|
28
|
+
var ANSI_RE = new RegExp("\\u001b\\[[0-9;]*m", "g");
|
|
29
|
+
|
|
30
|
+
// packages/core/src/redact-rules.ts
|
|
31
|
+
function shannonEntropy(s) {
|
|
32
|
+
const n = s.length;
|
|
33
|
+
if (n === 0) return 0;
|
|
34
|
+
const counts = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
36
|
+
let h = 0;
|
|
37
|
+
for (const c of counts.values()) {
|
|
38
|
+
const p = c / n;
|
|
39
|
+
h -= p * Math.log2(p);
|
|
40
|
+
}
|
|
41
|
+
return h;
|
|
42
|
+
}
|
|
43
|
+
var ENTROPY_MIN_LENGTH = 20;
|
|
44
|
+
var ENTROPY_THRESHOLD = 4.5;
|
|
45
|
+
var ALLOW_SPANS = [
|
|
46
|
+
// data: URIs — `data:image/png;base64,iVBORw0KGgo…`
|
|
47
|
+
/\bdata:[a-zA-Z0-9!#$&^_.+-]*(?:\/[a-zA-Z0-9!#$&^_.+-]*)?(?:;[a-zA-Z0-9-]+=[^;,\s]*)*;base64,[A-Za-z0-9+/=]{20,}(?:\s+[A-Za-z0-9+/=]{20,})*/g,
|
|
48
|
+
// Subresource Integrity — `integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9…"`
|
|
49
|
+
/\bsha(?:256|384|512)-[A-Za-z0-9+/]{20,}={0,3}/g
|
|
50
|
+
];
|
|
51
|
+
var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
52
|
+
var HEX_RE = /^[0-9a-fA-F]+$/;
|
|
53
|
+
var NUMERIC_RE = /^[0-9_+-]+$/;
|
|
54
|
+
var MINTED_ID_RE = /^(?:toolu|srvtoolu|msg|req|resp|call|fc|run|step|thread|asst|evt|sess|session|exec|job|task|turn|conv|agent|snapshot|chatcmpl)[-_]/i;
|
|
55
|
+
var ULID_RE = /^[0-7][0-9ABCDEFGHJKMNPQRSTVWXYZ]{25}$/;
|
|
56
|
+
var SAMECASE_RUN_MIN = 7;
|
|
57
|
+
var SAMECASE_RUNS_NEEDED = 2;
|
|
58
|
+
var SAMECASE_RUN_ALONE = 12;
|
|
59
|
+
function looksLikeProse(token) {
|
|
60
|
+
let long = 0;
|
|
61
|
+
for (const run of token.match(/[a-z]+|[A-Z]+/g) ?? []) {
|
|
62
|
+
if (run.length >= SAMECASE_RUN_ALONE) return true;
|
|
63
|
+
if (run.length >= SAMECASE_RUN_MIN) long += 1;
|
|
64
|
+
}
|
|
65
|
+
return long >= SAMECASE_RUNS_NEEDED;
|
|
66
|
+
}
|
|
67
|
+
function looksLikeAlphabetConstant(token) {
|
|
68
|
+
return token.length >= 24 && new Set(token).size === token.length;
|
|
69
|
+
}
|
|
70
|
+
var PLACEHOLDER_RE = /(?:^|[^a-z0-9])(?:x{3,}|your|my[-_]?(?:key|token|secret|pass)|example|sample|dummy|fake|mock|placeholder|change[-_]?(?:me|it|this)|replace[-_]?me|insert|todo|fixme|redacted|hidden|omitted|elided|not[-_]?real|no[-_]?such|hunter2|password|passwd|secret|token|apikey|api[-_]key|abcdef|123456|s3cret|letmein|foobar|lorem|ipsum)(?:$|[^a-z0-9])/i;
|
|
71
|
+
var INTERPOLATION_RE = /\$\{|\{\{|%\(|<%|^\$[A-Za-z_(]|^<|^\{|\}$|^%[A-Za-z(]/;
|
|
72
|
+
var REFERENCE_RE = /^(?:process\.env|import\.meta\.env|os\.environ|System\.getenv|Deno\.env|env|ENV|config|conf|cfg|settings|opts|options|args|argv|params|props|state|data|input|payload|body|req|res|ctx|context|self|this|that|obj|row|item|user|account|creds|credentials|secrets|vault|store|keychain)\b\s*[.[(]/;
|
|
73
|
+
var WORDY_IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z_$]*$/;
|
|
74
|
+
var DOTTED_CHAIN_RE = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+$/;
|
|
75
|
+
var PATHISH_RE = /^(?:[a-z][a-z0-9+.-]*:\/\/|\.{0,2}\/|~\/|[A-Za-z]:\\)/;
|
|
76
|
+
var CODEY_RE = /[<>{}[\]()|\\;,&?!*"'`]/;
|
|
77
|
+
var MASK_FRAGMENT_RE = /‹redacted:/;
|
|
78
|
+
var NAME_STOPWORDS = /* @__PURE__ */ new Set([
|
|
79
|
+
"monkey",
|
|
80
|
+
"donkey",
|
|
81
|
+
"turkey",
|
|
82
|
+
"whiskey",
|
|
83
|
+
"jockey",
|
|
84
|
+
"hockey",
|
|
85
|
+
"lackey",
|
|
86
|
+
"mickey",
|
|
87
|
+
"malarkey",
|
|
88
|
+
"hotkey",
|
|
89
|
+
"sortkey",
|
|
90
|
+
"oauth"
|
|
91
|
+
]);
|
|
92
|
+
function nameLooksLikeSecret(name) {
|
|
93
|
+
const segments = name.split(/[^A-Za-z0-9]+/).flatMap((part) => part.split(/(?<=[a-z0-9])(?=[A-Z])/)).filter(Boolean);
|
|
94
|
+
const last = segments[segments.length - 1];
|
|
95
|
+
if (!last) return false;
|
|
96
|
+
const lower = last.toLowerCase();
|
|
97
|
+
if (NAME_STOPWORDS.has(lower)) return false;
|
|
98
|
+
return /(?:key|token|secret|password|passwd|pwd|credential|auth)s?$/.test(lower);
|
|
99
|
+
}
|
|
100
|
+
function valueLooksLikeSecret(value) {
|
|
101
|
+
const v = value.trim();
|
|
102
|
+
if (v.length < 10 || v.length > 200) return false;
|
|
103
|
+
if (/\s/.test(v)) return false;
|
|
104
|
+
if (MASK_FRAGMENT_RE.test(v)) return false;
|
|
105
|
+
if (CODEY_RE.test(v)) return false;
|
|
106
|
+
if (INTERPOLATION_RE.test(v)) return false;
|
|
107
|
+
if (REFERENCE_RE.test(v)) return false;
|
|
108
|
+
if (DOTTED_CHAIN_RE.test(v)) return false;
|
|
109
|
+
if (PATHISH_RE.test(v)) return false;
|
|
110
|
+
if (NUMERIC_RE.test(v)) return false;
|
|
111
|
+
if (PLACEHOLDER_RE.test(v)) return false;
|
|
112
|
+
if (WORDY_IDENTIFIER_RE.test(v) && shannonEntropy(v) < 4) return false;
|
|
113
|
+
if (new Set(v).size <= 2) return false;
|
|
114
|
+
if (shannonEntropy(v) < 3.2) return false;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
function entropyCandidateAllowed(token) {
|
|
118
|
+
if (token.length < ENTROPY_MIN_LENGTH) return false;
|
|
119
|
+
if (UUID_RE.test(token)) return false;
|
|
120
|
+
if (UUID_RE.test(token.replace(/^[A-Za-z][A-Za-z0-9]{0,15}[-_]/, ""))) return false;
|
|
121
|
+
if (HEX_RE.test(token)) return false;
|
|
122
|
+
if (/^(?:sha1|sha256|sha384|sha512|md5|blake3|crc32|xxh64)[-_:]/i.test(token)) return false;
|
|
123
|
+
if (NUMERIC_RE.test(token)) return false;
|
|
124
|
+
if (MASK_FRAGMENT_RE.test(token)) return false;
|
|
125
|
+
if (PLACEHOLDER_RE.test(token)) return false;
|
|
126
|
+
if (MINTED_ID_RE.test(token)) return false;
|
|
127
|
+
if (ULID_RE.test(token)) return false;
|
|
128
|
+
if (looksLikeProse(token)) return false;
|
|
129
|
+
if (looksLikeAlphabetConstant(token)) return false;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
function regexRule(id, type, source, re, opts = {}) {
|
|
133
|
+
const group = opts.group ?? 0;
|
|
134
|
+
return {
|
|
135
|
+
id,
|
|
136
|
+
type,
|
|
137
|
+
source,
|
|
138
|
+
scan(text) {
|
|
139
|
+
const out = [];
|
|
140
|
+
const rx = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
|
|
141
|
+
let m;
|
|
142
|
+
while ((m = rx.exec(text)) !== null) {
|
|
143
|
+
if (m[0].length === 0) {
|
|
144
|
+
rx.lastIndex++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const value = group === 0 ? m[0] : m[group];
|
|
148
|
+
if (value === void 0 || value.length === 0) continue;
|
|
149
|
+
if (opts.validate && !opts.validate(value, m)) continue;
|
|
150
|
+
const offset = group === 0 ? 0 : m[0].indexOf(value);
|
|
151
|
+
if (offset < 0) continue;
|
|
152
|
+
out.push({ start: m.index + offset, end: m.index + offset + value.length, value });
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
var GENERIC_ANCHOR = /(?:key|token|secret|password|passwd|pwd|credential|auth)s?["']?[ \t]*(?::=|=>|[:=])/gi;
|
|
159
|
+
var GENERIC_VALUE = (
|
|
160
|
+
// The bare alternative stops at a backslash on purpose: transcripts carry
|
|
161
|
+
// json-escaped text, where `TOKEN=abc\nNEXT=…` is one line and the `\n` is
|
|
162
|
+
// two literal characters. Without this the capture swallows the rest of the
|
|
163
|
+
// record and the value is thrown out as code.
|
|
164
|
+
/[ \t]*(?:"([^"\r\n]{10,200})"|'([^'\r\n]{10,200})'|`([^`\r\n]{10,200})`|([^\s"'`,;)\](}>\\]{10,200}))/y
|
|
165
|
+
);
|
|
166
|
+
var NAME_CHAR_RE = /[A-Za-z0-9_$.-]/;
|
|
167
|
+
var genericAssignmentRule = {
|
|
168
|
+
id: "generic-assignment",
|
|
169
|
+
type: "generic",
|
|
170
|
+
source: "03 \xA75, given the shape of gitleaks generic-api-key (MIT)",
|
|
171
|
+
scan(text) {
|
|
172
|
+
const out = [];
|
|
173
|
+
const anchor = new RegExp(GENERIC_ANCHOR.source, GENERIC_ANCHOR.flags);
|
|
174
|
+
let m;
|
|
175
|
+
while ((m = anchor.exec(text)) !== null) {
|
|
176
|
+
const opEnd = m.index + m[0].length;
|
|
177
|
+
const opLen = /(?::=|=>)$/.test(m[0]) ? 2 : 1;
|
|
178
|
+
const before = text[opEnd - opLen - 1] ?? "";
|
|
179
|
+
if (text[opEnd] === "=" || opLen === 1 && /[!<>+\-*/%&|^=]/.test(before)) continue;
|
|
180
|
+
const keywordEnd = m.index + m[0].replace(/["']?[ \t]*(?::=|=>|[:=])$/, "").length;
|
|
181
|
+
let nameStart = m.index;
|
|
182
|
+
while (nameStart > 0 && NAME_CHAR_RE.test(text[nameStart - 1] ?? "")) nameStart--;
|
|
183
|
+
const name = text.slice(nameStart, keywordEnd);
|
|
184
|
+
if (!nameLooksLikeSecret(name)) continue;
|
|
185
|
+
GENERIC_VALUE.lastIndex = opEnd;
|
|
186
|
+
const v = GENERIC_VALUE.exec(text);
|
|
187
|
+
if (!v) continue;
|
|
188
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
189
|
+
if (value === void 0 || !valueLooksLikeSecret(value)) continue;
|
|
190
|
+
const start = v.index + v[0].lastIndexOf(value);
|
|
191
|
+
out.push({ start, end: start + value.length, value });
|
|
192
|
+
anchor.lastIndex = start + value.length;
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var socketAuthRule = {
|
|
198
|
+
id: "socket-auth-argument",
|
|
199
|
+
type: "generic",
|
|
200
|
+
source: "03 \xA75 credential context",
|
|
201
|
+
scan(text) {
|
|
202
|
+
const out = [];
|
|
203
|
+
const anchor = /(?:^|[\s"'`])--socket-auth["']?(?:[ \t]*=[ \t]*|[ \t]+)/gi;
|
|
204
|
+
let match;
|
|
205
|
+
while ((match = anchor.exec(text)) !== null) {
|
|
206
|
+
GENERIC_VALUE.lastIndex = anchor.lastIndex;
|
|
207
|
+
const found = GENERIC_VALUE.exec(text);
|
|
208
|
+
if (!found) continue;
|
|
209
|
+
const value = found[1] ?? found[2] ?? found[3] ?? found[4];
|
|
210
|
+
if (value === void 0 || !valueLooksLikeSecret(value)) continue;
|
|
211
|
+
const start = found.index + found[0].lastIndexOf(value);
|
|
212
|
+
out.push({ start, end: start + value.length, value });
|
|
213
|
+
anchor.lastIndex = start + value.length;
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var BEARER_RE = /(?:^|[^A-Za-z0-9_-])(?:Bearer|Token|ApiKey|Api-Key|Basic|DPoP)[ \t]+([A-Za-z0-9+/._~-]{16,512}={0,2})/g;
|
|
219
|
+
var bearerRule = regexRule(
|
|
220
|
+
"authorization-bearer",
|
|
221
|
+
"generic",
|
|
222
|
+
"03 \xA75 (credential context), given the shape of gitleaks generic-api-key (MIT)",
|
|
223
|
+
BEARER_RE,
|
|
224
|
+
{
|
|
225
|
+
group: 1,
|
|
226
|
+
// Same value screen as the `KEY=` rule: `Bearer ${token}`, `Bearer <TOKEN>`
|
|
227
|
+
// and `Bearer your-token-here` are documentation, not credentials.
|
|
228
|
+
validate: (value) => valueLooksLikeSecret(value)
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
var ENTROPY_TOKEN = /[A-Za-z0-9+_-]{20,}={0,2}/g;
|
|
232
|
+
var ESCAPE_LETTERS = "ntrbfv";
|
|
233
|
+
var highEntropyRule = {
|
|
234
|
+
id: "high-entropy-token",
|
|
235
|
+
type: "entropy",
|
|
236
|
+
source: "03 \xA75 (4.5 bits / 20 chars) + gitleaks entropy allowlists (MIT)",
|
|
237
|
+
scan(text) {
|
|
238
|
+
const out = [];
|
|
239
|
+
const rx = new RegExp(ENTROPY_TOKEN.source, ENTROPY_TOKEN.flags);
|
|
240
|
+
let m;
|
|
241
|
+
while ((m = rx.exec(text)) !== null) {
|
|
242
|
+
let start = m.index;
|
|
243
|
+
let value = m[0];
|
|
244
|
+
if (start > 0 && text[start - 1] === "\\" && ESCAPE_LETTERS.includes(value[0] ?? "")) {
|
|
245
|
+
start += 1;
|
|
246
|
+
value = value.slice(1);
|
|
247
|
+
}
|
|
248
|
+
if (start > 0 && text[start - 1] === "%" && /^[0-9A-Fa-f]{2}/.test(value)) {
|
|
249
|
+
start += 2;
|
|
250
|
+
value = value.slice(2);
|
|
251
|
+
}
|
|
252
|
+
const lead = value.length - value.replace(/^[-_+]+/, "").length;
|
|
253
|
+
start += lead;
|
|
254
|
+
value = value.slice(lead).replace(/[-_+]+$/, "");
|
|
255
|
+
if (value.length < ENTROPY_MIN_LENGTH) continue;
|
|
256
|
+
if (!entropyCandidateAllowed(value)) continue;
|
|
257
|
+
if (shannonEntropy(value) < ENTROPY_THRESHOLD) continue;
|
|
258
|
+
out.push({ start, end: start + value.length, value });
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var RULES = [
|
|
264
|
+
// ---- key material ------------------------------------------------------
|
|
265
|
+
regexRule(
|
|
266
|
+
// gitleaks `private-key`; secretlint `@secretlint/secretlint-rule-privatekey`.
|
|
267
|
+
// The whole block is masked, header to footer: the base64 body alone would
|
|
268
|
+
// otherwise be shredded into a dozen separate entropy hits.
|
|
269
|
+
"private-key-block",
|
|
270
|
+
"private-key",
|
|
271
|
+
"gitleaks private-key / secretlint-rule-privatekey (MIT)",
|
|
272
|
+
/-----BEGIN[ A-Z0-9]{0,40}PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,200000}?-----END[ A-Z0-9]{0,40}PRIVATE KEY(?: BLOCK)?-----/g
|
|
273
|
+
),
|
|
274
|
+
regexRule(
|
|
275
|
+
// gitleaks `private-key`, unterminated variant: a transcript often quotes
|
|
276
|
+
// the header and the first body lines and then elides the rest.
|
|
277
|
+
//
|
|
278
|
+
// The body is taken line by line and only where a line is ≥ 20 characters
|
|
279
|
+
// of pure base64, so an unterminated header followed by prose masks the
|
|
280
|
+
// header alone instead of swallowing the sentence after it. The separator
|
|
281
|
+
// class carries the backslash so json-escaped `\n` works, and it is
|
|
282
|
+
// disjoint from the base64 class, which is what keeps this linear.
|
|
283
|
+
"private-key-header",
|
|
284
|
+
"private-key",
|
|
285
|
+
"gitleaks private-key (MIT)",
|
|
286
|
+
/-----BEGIN[ A-Z0-9]{0,40}PRIVATE KEY(?: BLOCK)?-----(?:[\r\n \t\\]+[A-Za-z0-9+/=]{20,})*/g
|
|
287
|
+
),
|
|
288
|
+
regexRule(
|
|
289
|
+
// gitleaks `jwt`: three base64url segments, the first two starting `ey`
|
|
290
|
+
// (the base64 of `{"`). Session transcripts are full of these.
|
|
291
|
+
"jwt",
|
|
292
|
+
"jwt",
|
|
293
|
+
"gitleaks jwt (MIT)",
|
|
294
|
+
/\bey[A-Za-z0-9_-]{17,}\.ey[A-Za-z0-9_-]{17,}\.[A-Za-z0-9_-]{10,}={0,2}/g
|
|
295
|
+
),
|
|
296
|
+
// ---- vendor tokens -----------------------------------------------------
|
|
297
|
+
regexRule(
|
|
298
|
+
// gitleaks `aws-access-token`; secretlint-rule-aws `AWSAccessKeyID`.
|
|
299
|
+
"aws-access-key-id",
|
|
300
|
+
"aws",
|
|
301
|
+
"gitleaks aws-access-token / secretlint-rule-aws (MIT)",
|
|
302
|
+
/\b(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g
|
|
303
|
+
),
|
|
304
|
+
regexRule(
|
|
305
|
+
// secretlint-rule-aws `AWSSecretAccessKey`: a 40-character base64 value
|
|
306
|
+
// bound to an AWS-flavoured name. Typed `aws` and placed before the generic
|
|
307
|
+
// rule so `doctor` reports it as what it is.
|
|
308
|
+
"aws-secret-access-key",
|
|
309
|
+
"aws",
|
|
310
|
+
"secretlint-rule-aws AWSSecretAccessKey (MIT)",
|
|
311
|
+
/aws[_.-]?(?:secret|access)[_.-]?(?:access[_.-]?)?key(?:[_.-]?id)?["'\s]{0,4}[:=]["'\s]{0,4}([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,
|
|
312
|
+
{ group: 1 }
|
|
313
|
+
),
|
|
314
|
+
regexRule(
|
|
315
|
+
// gitleaks `gcp-api-key`; secretlint-rule-gcp `GCPApiKey`.
|
|
316
|
+
"gcp-api-key",
|
|
317
|
+
"gcp",
|
|
318
|
+
"gitleaks gcp-api-key / secretlint-rule-gcp (MIT)",
|
|
319
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g
|
|
320
|
+
),
|
|
321
|
+
regexRule(
|
|
322
|
+
// gitleaks `gcp-oauth-client-secret`.
|
|
323
|
+
"gcp-oauth-client-secret",
|
|
324
|
+
"gcp",
|
|
325
|
+
"gitleaks gcp-oauth-client-secret (MIT)",
|
|
326
|
+
/\bGOCSPX-[a-zA-Z0-9_-]{28}\b/g
|
|
327
|
+
),
|
|
328
|
+
regexRule(
|
|
329
|
+
// gitleaks `github-pat` / `-oauth` / `-app-token` / `-refresh-token`;
|
|
330
|
+
// secretlint-rule-github. ghp_ user, gho_ oauth, ghu_/ghs_ app, ghr_ refresh.
|
|
331
|
+
"github-token",
|
|
332
|
+
"github",
|
|
333
|
+
"gitleaks github-pat / secretlint-rule-github (MIT)",
|
|
334
|
+
/\bgh[pousr]_[A-Za-z0-9]{36,255}\b/g
|
|
335
|
+
),
|
|
336
|
+
regexRule(
|
|
337
|
+
// gitleaks `github-fine-grained-pat`.
|
|
338
|
+
"github-fine-grained-pat",
|
|
339
|
+
"github",
|
|
340
|
+
"gitleaks github-fine-grained-pat (MIT)",
|
|
341
|
+
/\bgithub_pat_[0-9a-zA-Z_]{82}\b/g
|
|
342
|
+
),
|
|
343
|
+
regexRule(
|
|
344
|
+
// gitleaks `slack-bot-token` / `-user-token` / `-app-token`;
|
|
345
|
+
// secretlint-rule-slack.
|
|
346
|
+
"slack-token",
|
|
347
|
+
"slack",
|
|
348
|
+
"gitleaks slack-bot-token / secretlint-rule-slack (MIT)",
|
|
349
|
+
/\bxox[abprs]-[0-9a-zA-Z-]{10,72}\b/g
|
|
350
|
+
),
|
|
351
|
+
regexRule(
|
|
352
|
+
// gitleaks `slack-webhook-url`; secretlint-rule-slack `SlackWebhook`.
|
|
353
|
+
"slack-webhook",
|
|
354
|
+
"slack",
|
|
355
|
+
"gitleaks slack-webhook-url (MIT)",
|
|
356
|
+
/https:\/\/hooks\.slack\.com\/(?:services|workflows|triggers)\/[A-Za-z0-9+/]{6,}\/[A-Za-z0-9+/]{6,}\/[A-Za-z0-9+/]{6,}/g
|
|
357
|
+
),
|
|
358
|
+
regexRule(
|
|
359
|
+
// gitleaks `stripe-access-token`. `pk_` publishable keys are public by
|
|
360
|
+
// design and are deliberately not matched.
|
|
361
|
+
"stripe-key",
|
|
362
|
+
"stripe",
|
|
363
|
+
"gitleaks stripe-access-token (MIT)",
|
|
364
|
+
/\b(?:sk|rk)_(?:test|live|prod)_[A-Za-z0-9]{10,99}\b/g
|
|
365
|
+
),
|
|
366
|
+
regexRule(
|
|
367
|
+
// gitleaks `anthropic-api-key`. Must precede the openai rules: both start
|
|
368
|
+
// `sk-`, and whichever rule claims the span first wins.
|
|
369
|
+
"anthropic-api-key",
|
|
370
|
+
"anthropic",
|
|
371
|
+
"gitleaks anthropic-api-key (MIT)",
|
|
372
|
+
/\bsk-ant-(?:api|admin)[0-9]{2}-[A-Za-z0-9_-]{80,120}\b/g
|
|
373
|
+
),
|
|
374
|
+
regexRule(
|
|
375
|
+
// gitleaks `openai-api-key`: the `T3BlbkFJ` infix is the base64 of
|
|
376
|
+
// "OpenAI" and is what makes this rule safe to run over prose.
|
|
377
|
+
"openai-api-key-project",
|
|
378
|
+
"openai",
|
|
379
|
+
"gitleaks openai-api-key (MIT)",
|
|
380
|
+
/\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}\b/g
|
|
381
|
+
),
|
|
382
|
+
regexRule(
|
|
383
|
+
// gitleaks `openai-api-key`, legacy 48-character form with no infix.
|
|
384
|
+
"openai-api-key-legacy",
|
|
385
|
+
"openai",
|
|
386
|
+
"gitleaks openai-api-key (MIT)",
|
|
387
|
+
/\bsk-[A-Za-z0-9]{48}\b/g
|
|
388
|
+
),
|
|
389
|
+
regexRule(
|
|
390
|
+
// gitleaks `npm-access-token`; secretlint-rule-npm.
|
|
391
|
+
"npm-access-token",
|
|
392
|
+
"npm",
|
|
393
|
+
"gitleaks npm-access-token / secretlint-rule-npm (MIT)",
|
|
394
|
+
/\bnpm_[A-Za-z0-9]{36}\b/g
|
|
395
|
+
),
|
|
396
|
+
// ---- credentials in urls -----------------------------------------------
|
|
397
|
+
regexRule(
|
|
398
|
+
// gitleaks `authenticated-url`; secretlint `-rule-basicauth`. Only the
|
|
399
|
+
// password is masked: `postgres://app:‹redacted:basic-auth:…›@db:5432/x`
|
|
400
|
+
// still says which host and which user, which is the point of an index that
|
|
401
|
+
// stays searchable by shape.
|
|
402
|
+
"basic-auth-url",
|
|
403
|
+
"basic-auth",
|
|
404
|
+
"gitleaks authenticated-url / secretlint-rule-basicauth (MIT)",
|
|
405
|
+
// The user half is `{0,64}`: `redis://:hunter2@host` has an empty user and
|
|
406
|
+
// is exactly as leaked as the two-part form.
|
|
407
|
+
/\b[a-zA-Z][a-zA-Z0-9+.-]{1,20}:\/\/[^\s:@/]{0,64}:([^\s:@/]{1,128})@/g,
|
|
408
|
+
{
|
|
409
|
+
group: 1,
|
|
410
|
+
validate: (value) => {
|
|
411
|
+
if (INTERPOLATION_RE.test(value)) return false;
|
|
412
|
+
if (PLACEHOLDER_RE.test(value)) return false;
|
|
413
|
+
if (MASK_FRAGMENT_RE.test(value)) return false;
|
|
414
|
+
if (/^(?:pass|pw|user|admin|root|test|guest|\*+|x+|\d{1,4})$/i.test(value)) return false;
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
// ---- credential context (03 §5) ----------------------------------------
|
|
420
|
+
bearerRule,
|
|
421
|
+
genericAssignmentRule,
|
|
422
|
+
socketAuthRule,
|
|
423
|
+
// ---- entropy (03 §5) ---------------------------------------------------
|
|
424
|
+
// Last, so that anything a named rule understands is reported under its real
|
|
425
|
+
// type and a bare token is the fallback rather than the default.
|
|
426
|
+
highEntropyRule
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
// packages/core/src/redact-elide.ts
|
|
430
|
+
function emptyElisions() {
|
|
431
|
+
return { binaryParts: 0, charsElided: 0 };
|
|
432
|
+
}
|
|
433
|
+
var MIN_PAYLOAD = 512;
|
|
434
|
+
var BASE64_MAGIC = [
|
|
435
|
+
["iVBORw0KGgo", "image/png", "89 50 4E 47 0D 0A 1A 0A"],
|
|
436
|
+
["/9j/", "image/jpeg", "FF D8 FF (JFIF/Exif SOI)"],
|
|
437
|
+
["R0lGODdh", "image/gif", "GIF87a"],
|
|
438
|
+
["R0lGODlh", "image/gif", "GIF89a"],
|
|
439
|
+
["Qk0", "image/bmp", '42 4D ("BM")'],
|
|
440
|
+
["SUkqAA", "image/tiff", "49 49 2A 00 (little-endian TIFF)"],
|
|
441
|
+
["TU0AK", "image/tiff", "4D 4D 00 2A (big-endian TIFF)"],
|
|
442
|
+
["AAABAA", "image/x-icon", "00 00 01 00 (ICO)"],
|
|
443
|
+
["UklGR", "application/octet-stream", '52 49 46 46 ("RIFF" \u2014 webp/wav/avi)'],
|
|
444
|
+
["JVBERi0", "application/pdf", '25 50 44 46 2D ("%PDF-")'],
|
|
445
|
+
["UEsDB", "application/zip", '50 4B 03 04 ("PK\\x03\\x04" \u2014 zip/docx/xlsx)'],
|
|
446
|
+
["H4sI", "application/gzip", "1F 8B 08"],
|
|
447
|
+
["SUQz", "audio/mpeg", '49 44 33 ("ID3")'],
|
|
448
|
+
["T2dnU", "application/ogg", '4F 67 67 53 ("OggS")'],
|
|
449
|
+
["d09GRg", "font/woff", '77 4F 46 46 ("wOFF")'],
|
|
450
|
+
["d09GMg", "font/woff2", '77 4F 46 32 ("wOF2")'],
|
|
451
|
+
["AAEAAA", "font/ttf", "00 01 00 00 (TrueType)"],
|
|
452
|
+
["T1RUTw", "font/otf", '4F 54 54 4F ("OTTO")']
|
|
453
|
+
];
|
|
454
|
+
var B64_RUN = `(?:[A-Za-z0-9+/=]|\\\\/)`;
|
|
455
|
+
var B64_BODY = `${B64_RUN}{40,}(?:[\\r\\n \\t]+${B64_RUN}{40,})*`;
|
|
456
|
+
var DATA_URI = new RegExp(
|
|
457
|
+
`data:([a-zA-Z0-9.+-]+/[a-zA-Z0-9.+-]+)?(?:;[a-zA-Z0-9.+=-]+)*;base64,${B64_BODY}`,
|
|
458
|
+
"g"
|
|
459
|
+
);
|
|
460
|
+
var CONTENT_BLOCK = new RegExp(
|
|
461
|
+
`"(?:data|image_data|b64_json|base64)"\\s*:\\s*"(${B64_BODY})"`,
|
|
462
|
+
"g"
|
|
463
|
+
);
|
|
464
|
+
var MEDIA_HINT = /"(?:media_?type|mime_?type|mimeType)"\s*:\s*"([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+)"|"type"\s*:\s*"base64"/gi;
|
|
465
|
+
var HINT_WINDOW = 200;
|
|
466
|
+
var MAGIC_PREFIXES = BASE64_MAGIC.map(([p]) => p.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")).join("|");
|
|
467
|
+
var BARE_RUN = new RegExp(
|
|
468
|
+
`(?<![A-Za-z0-9+/=])(?:${MAGIC_PREFIXES})${B64_BODY}`,
|
|
469
|
+
"g"
|
|
470
|
+
);
|
|
471
|
+
function magicOf(payload) {
|
|
472
|
+
const head = payload.slice(0, 32).replace(/\\\//g, "/").replace(/\s+/g, "");
|
|
473
|
+
for (const [prefix, mime] of BASE64_MAGIC) if (head.startsWith(prefix)) return mime;
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
function mimeNear(text, start, end) {
|
|
477
|
+
const from = Math.max(0, start - HINT_WINDOW);
|
|
478
|
+
const window = text.slice(from, start) + text.slice(end, Math.min(text.length, end + HINT_WINDOW));
|
|
479
|
+
const rx = new RegExp(MEDIA_HINT.source, MEDIA_HINT.flags);
|
|
480
|
+
let m;
|
|
481
|
+
let sawBase64 = false;
|
|
482
|
+
while ((m = rx.exec(window)) !== null) {
|
|
483
|
+
if (m[1]) return m[1];
|
|
484
|
+
sawBase64 = true;
|
|
485
|
+
}
|
|
486
|
+
return sawBase64 ? "application/octet-stream" : void 0;
|
|
487
|
+
}
|
|
488
|
+
function collect(re, text, group, decide, out) {
|
|
489
|
+
const rx = new RegExp(re.source, re.flags);
|
|
490
|
+
let m;
|
|
491
|
+
while ((m = rx.exec(text)) !== null) {
|
|
492
|
+
const payload = group === 0 ? m[0] : m[group];
|
|
493
|
+
if (payload === void 0 || payload.length === 0) {
|
|
494
|
+
rx.lastIndex += 1;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
const start = m.index + (group === 0 ? 0 : m[0].indexOf(payload));
|
|
498
|
+
const end = start + payload.length;
|
|
499
|
+
const mime = decide(payload, start, end);
|
|
500
|
+
if (mime === void 0) continue;
|
|
501
|
+
out.push({ start, end, mime });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function elideBinary(text, tally = emptyElisions()) {
|
|
505
|
+
if (typeof text !== "string" || text.length < MIN_PAYLOAD) return text ?? "";
|
|
506
|
+
if (!/[A-Za-z0-9+/]{64}/.test(text)) return text;
|
|
507
|
+
const spans = [];
|
|
508
|
+
const big = (payload) => payload.length >= MIN_PAYLOAD;
|
|
509
|
+
collect(DATA_URI, text, 0, (payload) => {
|
|
510
|
+
if (!big(payload)) return void 0;
|
|
511
|
+
const m = /^data:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+)?/.exec(payload);
|
|
512
|
+
return m?.[1] ?? "application/octet-stream";
|
|
513
|
+
}, spans);
|
|
514
|
+
collect(CONTENT_BLOCK, text, 1, (payload, start, end) => {
|
|
515
|
+
if (!big(payload)) return void 0;
|
|
516
|
+
const hint = mimeNear(text, start, end);
|
|
517
|
+
if (hint === void 0) return void 0;
|
|
518
|
+
return magicOf(payload) ?? hint;
|
|
519
|
+
}, spans);
|
|
520
|
+
collect(BARE_RUN, text, 0, (payload) => big(payload) ? magicOf(payload) : void 0, spans);
|
|
521
|
+
if (spans.length === 0) return text;
|
|
522
|
+
spans.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
523
|
+
const out = [];
|
|
524
|
+
let cursor = 0;
|
|
525
|
+
for (const s of spans) {
|
|
526
|
+
if (s.start < cursor) continue;
|
|
527
|
+
out.push(text.slice(cursor, s.start));
|
|
528
|
+
const n = s.end - s.start;
|
|
529
|
+
out.push(`\u2039elided:${s.mime}:${n} bytes\u203A`);
|
|
530
|
+
tally.binaryParts += 1;
|
|
531
|
+
tally.charsElided += n;
|
|
532
|
+
cursor = s.end;
|
|
533
|
+
}
|
|
534
|
+
out.push(text.slice(cursor));
|
|
535
|
+
return out.join("");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// packages/core/src/redact.ts
|
|
539
|
+
var OPEN = "\u2039";
|
|
540
|
+
var CLOSE = "\u203A";
|
|
541
|
+
var MASK_RE = new RegExp(`${OPEN}redacted:[a-z-]+:[0-9a-f]{8}${CLOSE}`, "g");
|
|
542
|
+
function secretDigest(secret) {
|
|
543
|
+
return createHash("sha256").update(secret, "utf8").digest("hex").slice(0, 8);
|
|
544
|
+
}
|
|
545
|
+
function redact(text) {
|
|
546
|
+
if (typeof text !== "string" || text.length === 0) return { text: text ?? "", hits: [] };
|
|
547
|
+
const claimed = new Uint8Array(text.length);
|
|
548
|
+
const spans = [];
|
|
549
|
+
for (const span of protectedSpans(text)) claim(claimed, span);
|
|
550
|
+
for (const rule of RULES) {
|
|
551
|
+
for (const m of rule.scan(text)) {
|
|
552
|
+
if (m.start < 0 || m.end > text.length || m.end <= m.start) continue;
|
|
553
|
+
if (isClaimed(claimed, m)) continue;
|
|
554
|
+
claim(claimed, m);
|
|
555
|
+
spans.push({ start: m.start, end: m.end, rule, value: m.value });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (spans.length === 0) return { text, hits: [] };
|
|
559
|
+
spans.sort((a, b) => a.start - b.start);
|
|
560
|
+
const out = [];
|
|
561
|
+
const hits = [];
|
|
562
|
+
let cursor = 0;
|
|
563
|
+
for (const s of spans) {
|
|
564
|
+
out.push(text.slice(cursor, s.start));
|
|
565
|
+
const sha8 = secretDigest(s.value);
|
|
566
|
+
out.push(`${OPEN}redacted:${s.rule.type}:${sha8}${CLOSE}`);
|
|
567
|
+
hits.push({ type: s.rule.type, rule: s.rule.id, sha8, start: s.start, length: s.end - s.start });
|
|
568
|
+
cursor = s.end;
|
|
569
|
+
}
|
|
570
|
+
out.push(text.slice(cursor));
|
|
571
|
+
return { text: out.join(""), hits };
|
|
572
|
+
}
|
|
573
|
+
function protectedSpans(text) {
|
|
574
|
+
const spans = [];
|
|
575
|
+
const patterns = [MASK_RE, ...ALLOW_SPANS];
|
|
576
|
+
for (const p of patterns) {
|
|
577
|
+
const rx = new RegExp(p.source, p.flags.includes("g") ? p.flags : p.flags + "g");
|
|
578
|
+
let m;
|
|
579
|
+
while ((m = rx.exec(text)) !== null) {
|
|
580
|
+
if (m[0].length === 0) {
|
|
581
|
+
rx.lastIndex++;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return spans;
|
|
588
|
+
}
|
|
589
|
+
function isClaimed(claimed, s) {
|
|
590
|
+
for (let i = s.start; i < s.end; i++) if (claimed[i]) return true;
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
function claim(claimed, s) {
|
|
594
|
+
const end = Math.min(s.end, claimed.length);
|
|
595
|
+
for (let i = Math.max(0, s.start); i < end; i++) claimed[i] = 1;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// packages/core/src/analytics/source.ts
|
|
599
|
+
var clean = (text) => redact(elideBinary(text)).text;
|
|
600
|
+
|
|
601
|
+
// packages/core/src/analytics/extract.ts
|
|
602
|
+
var FACTS_VERSION = 4;
|
|
603
|
+
var MAX_PROMPT_CHARS = 4e3;
|
|
604
|
+
var isRec = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
605
|
+
var str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
606
|
+
var int = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0;
|
|
607
|
+
function time(v) {
|
|
608
|
+
if (typeof v === "string") {
|
|
609
|
+
const t = Date.parse(v);
|
|
610
|
+
return Number.isFinite(t) ? t : null;
|
|
611
|
+
}
|
|
612
|
+
if (typeof v === "number" && Number.isFinite(v)) return v < 1e11 ? v * 1e3 : v;
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
function textOf(content) {
|
|
616
|
+
if (typeof content === "string") return content;
|
|
617
|
+
if (!Array.isArray(content)) return "";
|
|
618
|
+
const parts = [];
|
|
619
|
+
for (const block of content) if (isRec(block) && typeof block.text === "string") parts.push(block.text);
|
|
620
|
+
return parts.join("\n");
|
|
621
|
+
}
|
|
622
|
+
var INJECTED_PREFIXES = [
|
|
623
|
+
["<task-notification>", "task_notification"],
|
|
624
|
+
["<local-command-stdout>", "local_command_output"],
|
|
625
|
+
["<local-command-stderr>", "local_command_output"],
|
|
626
|
+
["<local-command-caveat>", "local_command_output"],
|
|
627
|
+
["[Request interrupted", "interrupt_marker"],
|
|
628
|
+
["<turn_aborted>", "interrupt_marker"],
|
|
629
|
+
["<environment_context>", "environment_context"],
|
|
630
|
+
["# AGENTS.md instructions", "agents_md"],
|
|
631
|
+
["<user_instructions>", "agents_md"],
|
|
632
|
+
["<external_codex_apps", "app_state"],
|
|
633
|
+
["<recommended_plugins>", "app_state"],
|
|
634
|
+
["<heartbeat>", "automation"],
|
|
635
|
+
["The following is the Codex agent history", "guardian_review"],
|
|
636
|
+
["You have new hive inbox message", "automation"],
|
|
637
|
+
["Another Claude session sent a message", "cross_session_message"],
|
|
638
|
+
["This session is being continued from a previous conversation", "compaction_summary"],
|
|
639
|
+
["Your claude.ai usage limit has reset", "auto_continuation"],
|
|
640
|
+
["Base directory for this skill:", "skill_expansion"]
|
|
641
|
+
];
|
|
642
|
+
var SYSTEM_REMINDER = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
|
|
643
|
+
function classifyText(raw) {
|
|
644
|
+
const text = (raw.includes("<system-reminder>") ? raw.replace(SYSTEM_REMINDER, "") : raw).trim();
|
|
645
|
+
if (!text) return { kind: "excluded", reason: "empty", text };
|
|
646
|
+
for (const [prefix, reason] of INJECTED_PREFIXES) if (text.startsWith(prefix)) return { kind: "excluded", reason, text };
|
|
647
|
+
if (text.startsWith("<command-name>") || text.startsWith("<command-message>")) return { kind: "command", reason: "slash_command", text };
|
|
648
|
+
if (text.startsWith("<send_user_message_question_reply>")) return { kind: "answer", reason: "question_answer", text };
|
|
649
|
+
return { kind: "human", reason: null, text };
|
|
650
|
+
}
|
|
651
|
+
var CODEX_STRIP = [/<in-app-browser-context[\s\S]*?<\/in-app-browser-context>/g, /<image name=[^>]*>\s*<\/image>/g, /<\/?image[^>]*>/g];
|
|
652
|
+
function cleanCodexText(raw) {
|
|
653
|
+
let t = raw;
|
|
654
|
+
if (t.includes("<")) for (const rx of CODEX_STRIP) t = t.replace(rx, "");
|
|
655
|
+
const at = t.indexOf("## My request");
|
|
656
|
+
if (at >= 0) {
|
|
657
|
+
const rest = t.slice(at + "## My request".length), colon = rest.indexOf(":");
|
|
658
|
+
t = colon >= 0 ? rest.slice(colon + 1) : rest;
|
|
659
|
+
} else if (t.trimStart().startsWith("# Files mentioned by the user")) t = "";
|
|
660
|
+
return t.trim();
|
|
661
|
+
}
|
|
662
|
+
function makeBuilder(file, harness, size, mtimeMs) {
|
|
663
|
+
return {
|
|
664
|
+
facts: {
|
|
665
|
+
v: FACTS_VERSION,
|
|
666
|
+
harness,
|
|
667
|
+
file,
|
|
668
|
+
size,
|
|
669
|
+
mtimeMs,
|
|
670
|
+
sessionId: path.basename(file, ".jsonl"),
|
|
671
|
+
parentId: null,
|
|
672
|
+
child: false,
|
|
673
|
+
project: null,
|
|
674
|
+
title: null,
|
|
675
|
+
automated: false,
|
|
676
|
+
excluded: false,
|
|
677
|
+
startedAt: null,
|
|
678
|
+
firstAt: null,
|
|
679
|
+
lastAt: null,
|
|
680
|
+
usage: [],
|
|
681
|
+
prompts: [],
|
|
682
|
+
malformed: 0
|
|
683
|
+
},
|
|
684
|
+
currentModel: null,
|
|
685
|
+
currentProvider: null,
|
|
686
|
+
pending: [],
|
|
687
|
+
seenPrompts: /* @__PURE__ */ new Set()
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function touch(b, at) {
|
|
691
|
+
if (at === null) return;
|
|
692
|
+
if (b.facts.firstAt === null || at < b.facts.firstAt) b.facts.firstAt = at;
|
|
693
|
+
if (b.facts.lastAt === null || at > b.facts.lastAt) b.facts.lastAt = at;
|
|
694
|
+
}
|
|
695
|
+
function answered(b, model, provider) {
|
|
696
|
+
if (!model) return;
|
|
697
|
+
for (const p of b.pending) {
|
|
698
|
+
p.after = model;
|
|
699
|
+
p.afterProvider = provider;
|
|
700
|
+
}
|
|
701
|
+
b.pending.length = 0;
|
|
702
|
+
b.currentModel = model;
|
|
703
|
+
b.currentProvider = provider;
|
|
704
|
+
}
|
|
705
|
+
function addUsage(b, u) {
|
|
706
|
+
b.facts.usage.push(u);
|
|
707
|
+
touch(b, u.at);
|
|
708
|
+
answered(b, u.model, u.provider);
|
|
709
|
+
}
|
|
710
|
+
function addPrompt(b, key, at, project, kind, reason, text) {
|
|
711
|
+
if (b.seenPrompts.has(key)) return;
|
|
712
|
+
b.seenPrompts.add(key);
|
|
713
|
+
if (kind === "human" && hasExclusionMarker(text)) b.facts.excluded = true;
|
|
714
|
+
const prompt = {
|
|
715
|
+
key,
|
|
716
|
+
at,
|
|
717
|
+
project,
|
|
718
|
+
kind,
|
|
719
|
+
reason,
|
|
720
|
+
text: kind === "human" ? clean(text.length > MAX_PROMPT_CHARS ? text.slice(0, MAX_PROMPT_CHARS) : text) : "",
|
|
721
|
+
before: b.currentModel,
|
|
722
|
+
beforeProvider: b.currentProvider,
|
|
723
|
+
after: null,
|
|
724
|
+
afterProvider: null
|
|
725
|
+
};
|
|
726
|
+
b.facts.prompts.push(prompt);
|
|
727
|
+
if (kind === "human") b.pending.push(prompt);
|
|
728
|
+
touch(b, at);
|
|
729
|
+
}
|
|
730
|
+
var SUBAGENT_DIR = `${path.sep}subagents${path.sep}`;
|
|
731
|
+
function claudeLine(b, line, subagent, sessions) {
|
|
732
|
+
const assistant = line.includes('"type":"assistant"');
|
|
733
|
+
if (assistant ? !line.includes('"usage"') : subagent || !line.includes('"type":"user"')) {
|
|
734
|
+
if (line.length > 4096 || !line.includes('-title"') && !line.includes('"type":"summary"')) return;
|
|
735
|
+
}
|
|
736
|
+
let r;
|
|
737
|
+
try {
|
|
738
|
+
r = JSON.parse(line.toString("utf8"));
|
|
739
|
+
} catch {
|
|
740
|
+
b.facts.malformed++;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (!isRec(r)) return;
|
|
744
|
+
const f = b.facts;
|
|
745
|
+
if (r.type === "summary" || r.type === "custom-title" || r.type === "ai-title") {
|
|
746
|
+
const title = str(r.customTitle) ?? str(r.aiTitle) ?? str(r.title) ?? str(r.summary);
|
|
747
|
+
if (title && (r.type !== "summary" || !f.title)) f.title = title;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (typeof r.cwd === "string" && !f.project) f.project = r.cwd;
|
|
751
|
+
const sid = str(r.sessionId);
|
|
752
|
+
if (sid) {
|
|
753
|
+
if (subagent) {
|
|
754
|
+
f.parentId = sid;
|
|
755
|
+
f.child = true;
|
|
756
|
+
f.sessionId = `${sid}:${path.basename(f.file, ".jsonl")}`;
|
|
757
|
+
} else if (!sessions.size) f.sessionId = sid;
|
|
758
|
+
}
|
|
759
|
+
const m = isRec(r.message) ? r.message : {};
|
|
760
|
+
const at = time(r.timestamp);
|
|
761
|
+
if (r.type === "assistant") {
|
|
762
|
+
const u = m.usage;
|
|
763
|
+
if (!isRec(u)) return;
|
|
764
|
+
const model = str(m.model);
|
|
765
|
+
if (model === "<synthetic>") return;
|
|
766
|
+
const cc = isRec(u.cache_creation) ? u.cache_creation : null;
|
|
767
|
+
const write = cc ? int(cc.ephemeral_5m_input_tokens) + int(cc.ephemeral_1h_input_tokens) : int(u.cache_creation_input_tokens);
|
|
768
|
+
const id = str(m.id), request = str(r.requestId), session = sid ?? f.file;
|
|
769
|
+
const fact = {
|
|
770
|
+
key: id ? request ? `c:${id}:${request}` : `c:${id}::${session}:${String(r.timestamp)}` : null,
|
|
771
|
+
at,
|
|
772
|
+
model,
|
|
773
|
+
provider: null,
|
|
774
|
+
input: int(u.input_tokens),
|
|
775
|
+
output: int(u.output_tokens),
|
|
776
|
+
cacheRead: int(u.cache_read_input_tokens),
|
|
777
|
+
cacheWrite: write,
|
|
778
|
+
cacheWrite1h: cc ? int(cc.ephemeral_1h_input_tokens) : 0,
|
|
779
|
+
reasoning: 0
|
|
780
|
+
};
|
|
781
|
+
if (r.isSidechain === true) fact.sidechain = true;
|
|
782
|
+
if (!request) fact.requestless = true;
|
|
783
|
+
if (id) fact.replayKey = `${id}:${session}`;
|
|
784
|
+
if (u.speed === "fast") fact.tier = "fast";
|
|
785
|
+
addUsage(b, fact);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (r.type !== "user" || r.isSidechain === true || subagent) return;
|
|
789
|
+
const content = m.content;
|
|
790
|
+
if (Array.isArray(content) && content.some((block) => isRec(block) && block.type === "tool_result")) return;
|
|
791
|
+
if (sid) sessions.add(sid);
|
|
792
|
+
const origin = isRec(r.origin) ? str(r.origin.kind) : null;
|
|
793
|
+
const flagged = r.isMeta === true ? "is_meta" : r.isCompactSummary === true ? "compaction_summary" : origin && origin !== "human" ? `origin_${origin}` : r.entrypoint === "sdk-cli" ? "headless_claude_p" : r.promptSource === "system" ? "prompt_source_system" : null;
|
|
794
|
+
let { kind, reason, text } = classifyText(textOf(content));
|
|
795
|
+
if (flagged) {
|
|
796
|
+
kind = "excluded";
|
|
797
|
+
reason = flagged;
|
|
798
|
+
} else if (reason === "empty" && origin === "human" && Array.isArray(content) && content.some((block) => isRec(block) && block.type === "image")) {
|
|
799
|
+
kind = "human";
|
|
800
|
+
reason = null;
|
|
801
|
+
text = "[image]";
|
|
802
|
+
}
|
|
803
|
+
addPrompt(b, str(r.uuid) ?? `${f.sessionId}:${String(r.timestamp)}`, at, str(r.cwd) ?? f.project, kind, reason, text);
|
|
804
|
+
}
|
|
805
|
+
var U_KEYS = ["input_tokens", "cached_input_tokens", "output_tokens", "reasoning_output_tokens", "total_tokens"];
|
|
806
|
+
var tuple = (u) => isRec(u) ? U_KEYS.map((k) => int(u[k])) : null;
|
|
807
|
+
var AUTO_REVIEW_MODELS = [
|
|
808
|
+
["2026-07-30", "gpt-5.6-luna"],
|
|
809
|
+
["2026-03-05", "gpt-5.4"],
|
|
810
|
+
["2026-02-05", "gpt-5.3-codex"],
|
|
811
|
+
["2025-12-11", "gpt-5.2-codex"],
|
|
812
|
+
["2025-11-13", "gpt-5.1-codex"],
|
|
813
|
+
["2025-09-15", "gpt-5-codex"]
|
|
814
|
+
];
|
|
815
|
+
function codexModel(model, timestamp) {
|
|
816
|
+
if (model === "codex-auto-review") {
|
|
817
|
+
const day = timestamp.slice(0, 10);
|
|
818
|
+
return { model: AUTO_REVIEW_MODELS.find(([since]) => day >= since)?.[1] ?? "gpt-5", inferred: true };
|
|
819
|
+
}
|
|
820
|
+
return model ? { model, inferred: false } : { model: "gpt-5", inferred: true };
|
|
821
|
+
}
|
|
822
|
+
function codexFact(b, s, at, timestamp, d, key) {
|
|
823
|
+
const input = d[0], output = d[2], reasoning = d[3], cached = Math.min(d[1], input);
|
|
824
|
+
const { model, inferred } = codexModel(b.currentModel, timestamp);
|
|
825
|
+
const fact = { key, at, model, provider: "openai", input: input - cached, output, cacheRead: cached, cacheWrite: 0, cacheWrite1h: 0, reasoning };
|
|
826
|
+
if (inferred) fact.inferredModel = true;
|
|
827
|
+
if (s.tier) fact.tier = s.tier;
|
|
828
|
+
return fact;
|
|
829
|
+
}
|
|
830
|
+
function codexWanted(head, lineNo) {
|
|
831
|
+
return lineNo === 0 && head.includes("session_meta") || head.includes('"token_count"') || head.includes('"turn_context"') || head.includes("thread_settings_applied") || head.includes('"task_started"') || head.includes('"token_usage_record"') || head.includes('"type":"compacted"') || head.includes('"UserMessage"') && head.includes("item_completed") || head.includes('"role":"user"') && head.includes('"response_item"') || head.includes('"event_msg"') && head.includes('"type":"user_message"');
|
|
832
|
+
}
|
|
833
|
+
function codexLine(b, line, s) {
|
|
834
|
+
const n = s.lineNo++;
|
|
835
|
+
const head = line.toString("latin1", 0, Math.min(line.length, 260));
|
|
836
|
+
const f = b.facts;
|
|
837
|
+
const parse = () => {
|
|
838
|
+
try {
|
|
839
|
+
return JSON.parse(line.toString("utf8"));
|
|
840
|
+
} catch {
|
|
841
|
+
f.malformed++;
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
if (n === 0 && head.includes("session_meta")) {
|
|
846
|
+
const r = parse();
|
|
847
|
+
if (!r) return;
|
|
848
|
+
const p = isRec(r.payload) ? r.payload : {};
|
|
849
|
+
f.sessionId = str(p.id) ?? str(p.session_id) ?? f.sessionId;
|
|
850
|
+
f.project = str(p.cwd);
|
|
851
|
+
f.startedAt = str(p.timestamp) ?? str(r.timestamp);
|
|
852
|
+
const source = p.source;
|
|
853
|
+
if (isRec(source) && "subagent" in source) {
|
|
854
|
+
f.child = true;
|
|
855
|
+
const sub = isRec(source.subagent) ? source.subagent : {};
|
|
856
|
+
const spawn = isRec(sub.thread_spawn) ? sub.thread_spawn : {};
|
|
857
|
+
f.parentId = str(spawn.parent_thread_id) ?? str(p.parent_thread_id) ?? str(p.forked_from_id);
|
|
858
|
+
}
|
|
859
|
+
if (source === "exec" || p.originator === "codex_exec" || p.originator === "codex_sdk_ts") f.automated = true;
|
|
860
|
+
touch(b, time(r.timestamp));
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (head.includes('"token_count"')) {
|
|
864
|
+
const r = parse();
|
|
865
|
+
const p = r && isRec(r.payload) ? r.payload : null;
|
|
866
|
+
const info = p && isRec(p.info) ? p.info : null;
|
|
867
|
+
if (!r || !info) return;
|
|
868
|
+
const total = tuple(info.total_token_usage), last = tuple(info.last_token_usage);
|
|
869
|
+
const totalSig = total ? total.join(",") : null;
|
|
870
|
+
const advanced = totalSig === null || totalSig !== s.previousTotal;
|
|
871
|
+
let d = null;
|
|
872
|
+
if (last && advanced) d = last;
|
|
873
|
+
else if (total) {
|
|
874
|
+
const prev = s.previousTotal ? s.previousTotal.split(",").map(Number) : [0, 0, 0, 0, 0];
|
|
875
|
+
d = total.map((v, i) => Math.max(0, v - prev[i]));
|
|
876
|
+
}
|
|
877
|
+
if (totalSig) s.previousTotal = totalSig;
|
|
878
|
+
if (!d || !(d[0] || d[1] || d[2] || d[3])) return;
|
|
879
|
+
s.deltas.add(d.join(","));
|
|
880
|
+
const key = totalSig ? `x:${totalSig}|${last ? last.join(",") : ""}` : null;
|
|
881
|
+
addUsage(b, codexFact(b, s, time(r.timestamp), String(r.timestamp ?? ""), d, key));
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (head.includes('"turn_context"')) {
|
|
885
|
+
const p = parse()?.payload;
|
|
886
|
+
if (isRec(p)) {
|
|
887
|
+
b.currentModel = str(p.model) ?? b.currentModel;
|
|
888
|
+
if (typeof p.cwd === "string") f.project = p.cwd;
|
|
889
|
+
}
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (head.includes("thread_settings_applied")) {
|
|
893
|
+
const p = parse()?.payload;
|
|
894
|
+
const settings = isRec(p) && isRec(p.thread_settings) ? p.thread_settings : null;
|
|
895
|
+
if (settings && "service_tier" in settings) {
|
|
896
|
+
const v = settings.service_tier;
|
|
897
|
+
s.tier = v === "priority" || v === "fast" ? "fast" : v === "default" || v === "standard" ? "standard" : void 0;
|
|
898
|
+
}
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (head.includes('"task_started"')) {
|
|
902
|
+
const p = parse()?.payload;
|
|
903
|
+
s.imported = isRec(p) && String(p.turn_id ?? "").startsWith("external-import");
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (head.includes('"UserMessage"') && head.includes("item_completed")) {
|
|
907
|
+
const r = parse();
|
|
908
|
+
const p = r && isRec(r.payload) ? r.payload : null;
|
|
909
|
+
if (!r || !p) return;
|
|
910
|
+
const item = isRec(p.item) ? p.item : {};
|
|
911
|
+
s.itemPrompts.push({
|
|
912
|
+
key: str(item.id),
|
|
913
|
+
at: time(r.timestamp),
|
|
914
|
+
text: textOf(item.content),
|
|
915
|
+
imported: s.imported || String(p.turn_id ?? "").startsWith("external-import"),
|
|
916
|
+
project: f.project
|
|
917
|
+
});
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (head.includes('"role":"user"') && head.includes('"response_item"')) {
|
|
921
|
+
const r = parse();
|
|
922
|
+
if (!r) return;
|
|
923
|
+
const p = isRec(r.payload) ? r.payload : {};
|
|
924
|
+
s.responsePrompts.push({ key: null, at: time(r.timestamp), text: textOf(p.content), imported: s.imported, project: f.project });
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (head.includes('"event_msg"') && line.subarray(0, 400).includes('"type":"user_message"')) {
|
|
928
|
+
const r = parse();
|
|
929
|
+
if (!r) return;
|
|
930
|
+
const p = isRec(r.payload) ? r.payload : {};
|
|
931
|
+
s.itemPrompts.push({ key: null, at: time(r.timestamp), text: typeof p.message === "string" ? p.message : "", imported: s.imported, project: f.project });
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (head.includes('"token_usage_record"')) {
|
|
935
|
+
const r = parse();
|
|
936
|
+
const p = r && isRec(r.payload) ? r.payload : null;
|
|
937
|
+
const id = p ? str(p.response_id) : null;
|
|
938
|
+
if (r && p && id && isRec(p.usage)) s.records.set(id, { at: time(r.timestamp), usage: p.usage });
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (head.includes('"type":"compacted"')) {
|
|
942
|
+
const at = line.lastIndexOf('"compaction_response_id":"');
|
|
943
|
+
if (at >= 0) {
|
|
944
|
+
const start = at + 26, end = line.indexOf(34, start);
|
|
945
|
+
if (end > start) s.compacted.add(line.toString("latin1", start, end));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function finishCodex(b, s) {
|
|
950
|
+
const f = b.facts;
|
|
951
|
+
for (const id of s.compacted) {
|
|
952
|
+
const record = s.records.get(id);
|
|
953
|
+
const d = record ? tuple(record.usage) : null;
|
|
954
|
+
if (!record || !d || !(d[0] || d[1] || d[2] || d[3]) || s.deltas.has(d.join(","))) continue;
|
|
955
|
+
const fact = codexFact(b, s, record.at, record.at ? new Date(record.at).toISOString() : "", d, `codex-compaction:${id}`);
|
|
956
|
+
fact.compaction = true;
|
|
957
|
+
f.usage.push(fact);
|
|
958
|
+
touch(b, record.at);
|
|
959
|
+
}
|
|
960
|
+
const list = s.itemPrompts.length ? s.itemPrompts : s.responsePrompts;
|
|
961
|
+
const reason = f.child ? "subagent_parent_written" : f.automated ? "programmatic_exec_session" : null;
|
|
962
|
+
const seen = /* @__PURE__ */ new Set();
|
|
963
|
+
list.forEach((p, i) => {
|
|
964
|
+
if (p.key) {
|
|
965
|
+
if (seen.has(p.key)) return;
|
|
966
|
+
seen.add(p.key);
|
|
967
|
+
}
|
|
968
|
+
const key = `codex:${f.sessionId}:${p.key ?? i}`;
|
|
969
|
+
if (reason) return addPrompt(b, key, p.at, p.project, "excluded", reason, "");
|
|
970
|
+
if (p.imported) return addPrompt(b, key, p.at, p.project, "excluded", "imported_from_claude_transcript", "");
|
|
971
|
+
const c = classifyText(cleanCodexText(p.text));
|
|
972
|
+
addPrompt(b, key, p.at, p.project, c.kind, c.reason, c.text);
|
|
973
|
+
});
|
|
974
|
+
const answers = f.usage.filter((u) => u.at !== null).sort((x, y) => x.at - y.at);
|
|
975
|
+
let j = 0, previous = null;
|
|
976
|
+
for (const p of [...f.prompts].sort((x, y) => (x.at ?? 0) - (y.at ?? 0))) {
|
|
977
|
+
while (j < answers.length && answers[j].at <= (p.at ?? 0)) previous = answers[j++].model;
|
|
978
|
+
p.before = previous;
|
|
979
|
+
p.beforeProvider = previous ? "openai" : null;
|
|
980
|
+
p.after = answers[j]?.model ?? null;
|
|
981
|
+
p.afterProvider = p.after ? "openai" : null;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function piLine(b, line) {
|
|
985
|
+
let r;
|
|
986
|
+
try {
|
|
987
|
+
r = JSON.parse(line.toString("utf8"));
|
|
988
|
+
} catch {
|
|
989
|
+
b.facts.malformed++;
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const f = b.facts, at = time(r.timestamp);
|
|
993
|
+
if (r.type === "session") {
|
|
994
|
+
f.sessionId = str(r.id) ?? f.sessionId;
|
|
995
|
+
f.project = str(r.cwd);
|
|
996
|
+
touch(b, at);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (r.type === "model_change") {
|
|
1000
|
+
b.currentModel = str(r.modelId) ?? b.currentModel;
|
|
1001
|
+
b.currentProvider = str(r.provider) ?? b.currentProvider;
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (r.type !== "message" || !isRec(r.message)) return;
|
|
1005
|
+
const m = r.message;
|
|
1006
|
+
if (m.role === "assistant" && isRec(m.usage)) {
|
|
1007
|
+
const u = m.usage;
|
|
1008
|
+
const fact = {
|
|
1009
|
+
key: `pi:${f.sessionId}:${String(r.id)}`,
|
|
1010
|
+
at,
|
|
1011
|
+
model: str(m.model) ?? b.currentModel,
|
|
1012
|
+
provider: str(m.provider) ?? b.currentProvider,
|
|
1013
|
+
input: int(u.input),
|
|
1014
|
+
output: int(u.output),
|
|
1015
|
+
cacheRead: int(u.cacheRead),
|
|
1016
|
+
cacheWrite: int(u.cacheWrite),
|
|
1017
|
+
cacheWrite1h: 0,
|
|
1018
|
+
reasoning: 0
|
|
1019
|
+
};
|
|
1020
|
+
if (fact.input + fact.output + fact.cacheRead + fact.cacheWrite > 0) addUsage(b, fact);
|
|
1021
|
+
else answered(b, fact.model, fact.provider);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (m.role === "user") {
|
|
1025
|
+
const c = classifyText(textOf(m.content));
|
|
1026
|
+
addPrompt(b, `pi:${f.sessionId}:${String(r.id)}`, at, f.project, c.kind, c.reason, c.text);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
var CHUNK = 4 * 1024 * 1024;
|
|
1030
|
+
var MAX_LINE = 256 * 1024 * 1024;
|
|
1031
|
+
var sharedBuffer = null;
|
|
1032
|
+
function extractFile(file, harness) {
|
|
1033
|
+
const fd = fs.openSync(file, "r");
|
|
1034
|
+
try {
|
|
1035
|
+
const stat = fs.fstatSync(fd);
|
|
1036
|
+
const b = makeBuilder(file, harness, stat.size, stat.mtimeMs);
|
|
1037
|
+
const codex = {
|
|
1038
|
+
lineNo: 0,
|
|
1039
|
+
previousTotal: null,
|
|
1040
|
+
tier: void 0,
|
|
1041
|
+
imported: false,
|
|
1042
|
+
records: /* @__PURE__ */ new Map(),
|
|
1043
|
+
compacted: /* @__PURE__ */ new Set(),
|
|
1044
|
+
deltas: /* @__PURE__ */ new Set(),
|
|
1045
|
+
itemPrompts: [],
|
|
1046
|
+
responsePrompts: []
|
|
1047
|
+
};
|
|
1048
|
+
const subagent = file.includes(SUBAGENT_DIR), sessions = /* @__PURE__ */ new Set();
|
|
1049
|
+
const onLine = harness === "claude" ? (line) => claudeLine(b, line, subagent, sessions) : harness === "codex" ? (line) => codexLine(b, line, codex) : (line) => piLine(b, line);
|
|
1050
|
+
let buffer = sharedBuffer ??= Buffer.allocUnsafe(CHUNK);
|
|
1051
|
+
let filled = 0, position = 0, skipping = false;
|
|
1052
|
+
for (; ; ) {
|
|
1053
|
+
if (filled === buffer.length) {
|
|
1054
|
+
if (harness === "codex" && !codexWanted(buffer.toString("latin1", 0, Math.min(filled, 400)), codex.lineNo)) {
|
|
1055
|
+
skipping = true;
|
|
1056
|
+
filled = 0;
|
|
1057
|
+
codex.lineNo++;
|
|
1058
|
+
} else if (buffer.length >= MAX_LINE) {
|
|
1059
|
+
skipping = true;
|
|
1060
|
+
filled = 0;
|
|
1061
|
+
b.facts.malformed++;
|
|
1062
|
+
} else {
|
|
1063
|
+
const bigger = Buffer.allocUnsafe(buffer.length * 2);
|
|
1064
|
+
buffer.copy(bigger, 0, 0, filled);
|
|
1065
|
+
buffer = bigger;
|
|
1066
|
+
if (bigger.length <= 4 * CHUNK) sharedBuffer = bigger;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
const n = fs.readSync(fd, buffer, filled, buffer.length - filled, position);
|
|
1070
|
+
position += n;
|
|
1071
|
+
const end = filled + n;
|
|
1072
|
+
let start = 0;
|
|
1073
|
+
for (; ; ) {
|
|
1074
|
+
const nl = buffer.indexOf(10, start);
|
|
1075
|
+
if (nl < 0 || nl >= end) break;
|
|
1076
|
+
if (skipping) skipping = false;
|
|
1077
|
+
else if (nl > start) onLine(buffer.subarray(start, nl));
|
|
1078
|
+
start = nl + 1;
|
|
1079
|
+
}
|
|
1080
|
+
if (n === 0) {
|
|
1081
|
+
if (!skipping && start < end) onLine(buffer.subarray(start, end));
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
buffer.copyWithin(0, start, end);
|
|
1085
|
+
filled = end - start;
|
|
1086
|
+
}
|
|
1087
|
+
if (harness === "codex") finishCodex(b, codex);
|
|
1088
|
+
if (harness === "claude" && !subagent && sessions.size) b.facts.sessionIds = [...sessions];
|
|
1089
|
+
if (b.facts.excluded) for (const p of b.facts.prompts) p.text = "";
|
|
1090
|
+
return b.facts;
|
|
1091
|
+
} finally {
|
|
1092
|
+
fs.closeSync(fd);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// packages/core/src/analytics/scan-pool.ts
|
|
1097
|
+
var errorCode = (error) => error instanceof Error && /^[a-z][a-z0-9_]{0,63}$/.test(error.message) ? error.message : "source_unreadable";
|
|
1098
|
+
function runScanWorker() {
|
|
1099
|
+
if (!parentPort) throw new Error("scan_worker_requires_parent");
|
|
1100
|
+
const port = parentPort;
|
|
1101
|
+
port.on("message", (job) => {
|
|
1102
|
+
try {
|
|
1103
|
+
port.postMessage({ facts: extractFile(job.file, job.harness) });
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
port.postMessage({ error: errorCode(error) });
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// packages/cli/src/audit-native-worker.ts
|
|
1111
|
+
runScanWorker();
|
|
1112
|
+
//# sourceMappingURL=audit-native-worker.js.map
|