everything-claude-code 1.4.3

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +739 -0
  3. package/README.zh-CN.md +523 -0
  4. package/crates/ecc-kernel/Cargo.lock +160 -0
  5. package/crates/ecc-kernel/Cargo.toml +15 -0
  6. package/crates/ecc-kernel/src/main.rs +710 -0
  7. package/docs/ecc.md +117 -0
  8. package/package.json +45 -0
  9. package/packs/blueprint.json +8 -0
  10. package/packs/forge.json +16 -0
  11. package/packs/instinct.json +16 -0
  12. package/packs/orchestra.json +15 -0
  13. package/packs/proof.json +8 -0
  14. package/packs/sentinel.json +8 -0
  15. package/prompts/ecc/patch.md +25 -0
  16. package/prompts/ecc/plan.md +28 -0
  17. package/schemas/ecc.apply.schema.json +35 -0
  18. package/schemas/ecc.config.schema.json +37 -0
  19. package/schemas/ecc.lock.schema.json +34 -0
  20. package/schemas/ecc.patch.schema.json +25 -0
  21. package/schemas/ecc.plan.schema.json +32 -0
  22. package/schemas/ecc.run.schema.json +67 -0
  23. package/schemas/ecc.verify.schema.json +27 -0
  24. package/schemas/hooks.schema.json +81 -0
  25. package/schemas/package-manager.schema.json +17 -0
  26. package/schemas/plugin.schema.json +13 -0
  27. package/scripts/ecc/catalog.js +82 -0
  28. package/scripts/ecc/config.js +43 -0
  29. package/scripts/ecc/diff.js +113 -0
  30. package/scripts/ecc/exec.js +121 -0
  31. package/scripts/ecc/fixtures/basic/patches/impl-core.diff +8 -0
  32. package/scripts/ecc/fixtures/basic/patches/tests.diff +8 -0
  33. package/scripts/ecc/fixtures/basic/plan.json +23 -0
  34. package/scripts/ecc/fixtures/unauthorized/patches/impl-core.diff +8 -0
  35. package/scripts/ecc/fixtures/unauthorized/plan.json +15 -0
  36. package/scripts/ecc/git.js +139 -0
  37. package/scripts/ecc/id.js +37 -0
  38. package/scripts/ecc/install-kernel.js +344 -0
  39. package/scripts/ecc/json-extract.js +301 -0
  40. package/scripts/ecc/json.js +26 -0
  41. package/scripts/ecc/kernel.js +144 -0
  42. package/scripts/ecc/lock.js +36 -0
  43. package/scripts/ecc/paths.js +28 -0
  44. package/scripts/ecc/plan.js +57 -0
  45. package/scripts/ecc/project.js +37 -0
  46. package/scripts/ecc/providers/codex.js +168 -0
  47. package/scripts/ecc/providers/index.js +23 -0
  48. package/scripts/ecc/providers/mock.js +49 -0
  49. package/scripts/ecc/report.js +127 -0
  50. package/scripts/ecc/run.js +105 -0
  51. package/scripts/ecc/validate.js +325 -0
  52. package/scripts/ecc/verify.js +125 -0
  53. package/scripts/ecc.js +532 -0
  54. package/scripts/lib/package-manager.js +390 -0
  55. package/scripts/lib/session-aliases.js +432 -0
  56. package/scripts/lib/session-manager.js +396 -0
  57. package/scripts/lib/utils.js +426 -0
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall helper: fetch prebuilt ecc-kernel from the GitHub release matching package.json version.
4
+ *
5
+ * Default behavior:
6
+ * - If ECC_KERNEL_INSTALL is unset and CI=true, skip (avoid noisy 404s in repo CI).
7
+ * - Otherwise attempt download; failures are non-fatal unless ECC_KERNEL_INSTALL=required.
8
+ *
9
+ * Env:
10
+ * - ECC_KERNEL_INSTALL=0|false|off disable download
11
+ * - ECC_KERNEL_INSTALL=required|force fail install if download fails
12
+ * - ECC_KERNEL_BASE_URL=... override base URL (defaults to GitHub releases URL)
13
+ * - ECC_KERNEL_DEBUG=1 verbose logs
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const https = require('https');
19
+ const http = require('http');
20
+ const crypto = require('crypto');
21
+ const { spawnSync } = require('child_process');
22
+ const { URL } = require('url');
23
+
24
+ function debugEnabled() {
25
+ return !!(process.env.ECC_KERNEL_DEBUG && String(process.env.ECC_KERNEL_DEBUG).trim());
26
+ }
27
+
28
+ function logDebug(msg) {
29
+ if (debugEnabled()) {
30
+ console.error(`[ecc-kernel] ${msg}`);
31
+ }
32
+ }
33
+
34
+ function parseInstallMode() {
35
+ const raw = process.env.ECC_KERNEL_INSTALL;
36
+ const envSet = raw !== undefined;
37
+ const s = raw ? String(raw).trim().toLowerCase() : '';
38
+ if (['0', 'false', 'no', 'off', 'disable', 'disabled'].includes(s)) return { mode: 'off', envSet };
39
+ if (['required', 'require', 'force'].includes(s)) return { mode: 'required', envSet };
40
+ return { mode: 'auto', envSet };
41
+ }
42
+
43
+ function binName() {
44
+ return process.platform === 'win32' ? 'ecc-kernel.exe' : 'ecc-kernel';
45
+ }
46
+
47
+ function platformArch() {
48
+ const platform = process.platform;
49
+ const arch = process.arch;
50
+
51
+ const os =
52
+ platform === 'darwin' ? 'darwin' :
53
+ platform === 'linux' ? 'linux' :
54
+ platform === 'win32' ? 'windows' :
55
+ null;
56
+
57
+ const cpu =
58
+ arch === 'x64' ? 'x64' :
59
+ arch === 'arm64' ? 'arm64' :
60
+ null;
61
+
62
+ if (!os || !cpu) return null;
63
+ return { os, cpu };
64
+ }
65
+
66
+ function readPackageJson() {
67
+ const root = path.resolve(__dirname, '..', '..');
68
+ const pkgPath = path.join(root, 'package.json');
69
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
70
+ return { root, pkg };
71
+ }
72
+
73
+ function normalizeRepoUrl(url) {
74
+ if (!url) return null;
75
+ let u = String(url).trim();
76
+ if (u.startsWith('git+')) u = u.slice('git+'.length);
77
+ if (u.endsWith('.git')) u = u.slice(0, -'.git'.length);
78
+ return u;
79
+ }
80
+
81
+ function defaultBaseUrl(pkgVersion, pkg) {
82
+ const envBase = process.env.ECC_KERNEL_BASE_URL ? String(process.env.ECC_KERNEL_BASE_URL).trim() : '';
83
+ if (envBase) return envBase.replace(/\/+$/, '');
84
+
85
+ const repo = pkg && pkg.repository ? pkg.repository : null;
86
+ const repoUrl = normalizeRepoUrl(typeof repo === 'string' ? repo : (repo && repo.url));
87
+ const m = repoUrl && repoUrl.match(/^https?:\/\/github\.com\/[^/]+\/[^/]+/);
88
+ const baseRepo = m ? m[0] : 'https://github.com/sumulige/everything-claude-code';
89
+ return `${baseRepo}/releases/download/v${pkgVersion}`;
90
+ }
91
+
92
+ function assetName({ os, cpu }) {
93
+ const ext = os === 'windows' ? '.exe' : '';
94
+ return `ecc-kernel-${os}-${cpu}${ext}`;
95
+ }
96
+
97
+ function joinUrl(base, file) {
98
+ const b = String(base || '').replace(/\/+$/, '');
99
+ return `${b}/${file}`;
100
+ }
101
+
102
+ function request(url, { headers, timeoutMs } = {}) {
103
+ return new Promise((resolve, reject) => {
104
+ const u = new URL(url);
105
+ const lib = u.protocol === 'http:' ? http : https;
106
+ const req = lib.request(
107
+ {
108
+ method: 'GET',
109
+ protocol: u.protocol,
110
+ hostname: u.hostname,
111
+ port: u.port,
112
+ path: u.pathname + u.search,
113
+ headers: {
114
+ 'User-Agent': 'ecc-kernel-installer',
115
+ ...(headers || {})
116
+ }
117
+ },
118
+ res => resolve(res)
119
+ );
120
+
121
+ req.on('error', reject);
122
+ req.setTimeout(timeoutMs || 10_000, () => {
123
+ req.destroy(new Error(`request timeout after ${timeoutMs || 10_000}ms`));
124
+ });
125
+ req.end();
126
+ });
127
+ }
128
+
129
+ async function downloadText(url, { headers, timeoutMs, maxBytes = 1024 * 1024 } = {}) {
130
+ let current = url;
131
+ for (let redirects = 0; redirects < 10; redirects++) {
132
+ const res = await request(current, { headers, timeoutMs });
133
+ const code = res.statusCode || 0;
134
+ const loc = res.headers && res.headers.location ? String(res.headers.location) : '';
135
+
136
+ if ([301, 302, 303, 307, 308].includes(code) && loc) {
137
+ current = new URL(loc, current).toString();
138
+ continue;
139
+ }
140
+
141
+ if (code !== 200) {
142
+ const chunks = [];
143
+ let size = 0;
144
+ for await (const chunk of res) {
145
+ size += chunk.length;
146
+ if (size > 4096) break;
147
+ chunks.push(chunk);
148
+ }
149
+ const body = Buffer.concat(chunks).toString('utf8');
150
+ const err = new Error(`HTTP ${code} for ${url}${body ? `: ${body.slice(0, 200)}` : ''}`);
151
+ err.statusCode = code;
152
+ throw err;
153
+ }
154
+
155
+ const chunks = [];
156
+ let size = 0;
157
+ for await (const chunk of res) {
158
+ size += chunk.length;
159
+ if (size > maxBytes) throw new Error(`response too large for ${url}`);
160
+ chunks.push(chunk);
161
+ }
162
+ return Buffer.concat(chunks).toString('utf8');
163
+ }
164
+ throw new Error(`too many redirects for ${url}`);
165
+ }
166
+
167
+ async function downloadToFile(url, destPath, { headers, timeoutMs } = {}) {
168
+ let current = url;
169
+ for (let redirects = 0; redirects < 10; redirects++) {
170
+ const res = await request(current, { headers, timeoutMs });
171
+ const code = res.statusCode || 0;
172
+ const loc = res.headers && res.headers.location ? String(res.headers.location) : '';
173
+
174
+ if ([301, 302, 303, 307, 308].includes(code) && loc) {
175
+ current = new URL(loc, current).toString();
176
+ continue;
177
+ }
178
+
179
+ if (code !== 200) {
180
+ const chunks = [];
181
+ let size = 0;
182
+ for await (const chunk of res) {
183
+ size += chunk.length;
184
+ if (size > 4096) break;
185
+ chunks.push(chunk);
186
+ }
187
+ const body = Buffer.concat(chunks).toString('utf8');
188
+ const err = new Error(`HTTP ${code} for ${url}${body ? `: ${body.slice(0, 200)}` : ''}`);
189
+ err.statusCode = code;
190
+ throw err;
191
+ }
192
+
193
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
194
+ const tmpPath = `${destPath}.tmp`;
195
+ try { fs.rmSync(tmpPath, { force: true }); } catch (_err) { /* ignore */ }
196
+
197
+ await new Promise((resolve, reject) => {
198
+ const out = fs.createWriteStream(tmpPath);
199
+ res.pipe(out);
200
+ res.on('error', reject);
201
+ out.on('error', reject);
202
+ out.on('finish', resolve);
203
+ });
204
+
205
+ fs.renameSync(tmpPath, destPath);
206
+ return;
207
+ }
208
+ throw new Error(`too many redirects for ${url}`);
209
+ }
210
+
211
+ function parseSha256(text) {
212
+ const m = String(text || '').trim().match(/([a-fA-F0-9]{64})/);
213
+ return m ? m[1].toLowerCase() : null;
214
+ }
215
+
216
+ function sha256FileSync(filePath) {
217
+ const h = crypto.createHash('sha256');
218
+ const fd = fs.openSync(filePath, 'r');
219
+ try {
220
+ const buf = Buffer.alloc(1024 * 1024);
221
+ while (true) {
222
+ const n = fs.readSync(fd, buf, 0, buf.length, null);
223
+ if (!n) break;
224
+ h.update(buf.subarray(0, n));
225
+ }
226
+ } finally {
227
+ fs.closeSync(fd);
228
+ }
229
+ return h.digest('hex');
230
+ }
231
+
232
+ function ensureExecutable(filePath) {
233
+ if (process.platform === 'win32') return;
234
+ try {
235
+ fs.chmodSync(filePath, 0o755);
236
+ } catch (_err) {
237
+ // ignore
238
+ }
239
+ }
240
+
241
+ function validateRuns(filePath) {
242
+ const res = spawnSync(filePath, ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
243
+ if (res.error) throw new Error(`kernel failed to run: ${res.error.message}`);
244
+ if (res.status !== 0) {
245
+ const stderr = (res.stderr || '').trim();
246
+ throw new Error(`kernel failed to run (exit ${res.status})${stderr ? `: ${stderr}` : ''}`);
247
+ }
248
+ }
249
+
250
+ async function main() {
251
+ const { mode, envSet } = parseInstallMode();
252
+ if (mode === 'off') return;
253
+
254
+ // Skip in CI unless explicitly enabled (avoid repeated 404s in repo CI).
255
+ if (!envSet && process.env.CI) {
256
+ logDebug('CI detected and ECC_KERNEL_INSTALL not set; skipping download');
257
+ return;
258
+ }
259
+
260
+ const target = platformArch();
261
+ if (!target) {
262
+ logDebug(`unsupported platform/arch: ${process.platform}/${process.arch}`);
263
+ return;
264
+ }
265
+
266
+ const { pkg } = readPackageJson();
267
+ const version = String(pkg.version || '').trim();
268
+ if (!version) throw new Error('package.json missing version');
269
+
270
+ const eccDir = path.resolve(__dirname);
271
+ const destDir = path.join(eccDir, 'bin', `${target.os}-${target.cpu}`);
272
+ const destBin = path.join(destDir, binName());
273
+
274
+ if (fs.existsSync(destBin)) {
275
+ logDebug(`kernel already present: ${destBin}`);
276
+ return;
277
+ }
278
+
279
+ const baseUrl = defaultBaseUrl(version, pkg);
280
+ const asset = assetName(target);
281
+ const urlBin = joinUrl(baseUrl, asset);
282
+ const urlSha = joinUrl(baseUrl, `${asset}.sha256`);
283
+
284
+ const headers = {};
285
+ if (process.env.ECC_KERNEL_AUTH_TOKEN) {
286
+ headers.Authorization = `token ${String(process.env.ECC_KERNEL_AUTH_TOKEN).trim()}`;
287
+ }
288
+
289
+ logDebug(`downloading ${urlBin}`);
290
+
291
+ let expectedSha = null;
292
+ try {
293
+ const shaText = await downloadText(urlSha, { headers, timeoutMs: 10_000, maxBytes: 64 * 1024 });
294
+ expectedSha = parseSha256(shaText);
295
+ if (expectedSha) logDebug(`checksum: ${expectedSha}`);
296
+ } catch (err) {
297
+ const code = err && typeof err.statusCode === 'number' ? err.statusCode : null;
298
+ if (code === 404) {
299
+ logDebug('checksum missing (404), continuing without verification');
300
+ } else {
301
+ logDebug(`checksum download failed, continuing without verification: ${err.message}`);
302
+ }
303
+ }
304
+
305
+ try {
306
+ await downloadToFile(urlBin, destBin, { headers, timeoutMs: 30_000 });
307
+ } catch (err) {
308
+ const code = err && typeof err.statusCode === 'number' ? err.statusCode : null;
309
+ if (mode === 'required') throw err;
310
+
311
+ if (code === 404) {
312
+ logDebug('no prebuilt kernel asset for this version/platform; skipping');
313
+ return;
314
+ }
315
+ logDebug(`kernel download failed, skipping: ${err.message}`);
316
+ return;
317
+ }
318
+
319
+ try {
320
+ ensureExecutable(destBin);
321
+
322
+ if (expectedSha) {
323
+ const actualSha = sha256FileSync(destBin);
324
+ if (actualSha !== expectedSha) {
325
+ throw new Error(`checksum mismatch: expected ${expectedSha}, got ${actualSha}`);
326
+ }
327
+ }
328
+
329
+ validateRuns(destBin);
330
+ } catch (err) {
331
+ try { fs.rmSync(destBin, { force: true }); } catch (_err) { /* ignore */ }
332
+ if (mode === 'required') throw err;
333
+ logDebug(`downloaded kernel invalid, removed: ${err.message}`);
334
+ return;
335
+ }
336
+
337
+ console.error(`ecc: installed ecc-kernel (${target.os}-${target.cpu})`);
338
+ }
339
+
340
+ main().catch(err => {
341
+ const msg = err && err.message ? err.message : String(err);
342
+ console.error(`ecc: kernel install failed: ${msg}`);
343
+ process.exit(1);
344
+ });
@@ -0,0 +1,301 @@
1
+ const fs = require('fs');
2
+ const { StringDecoder } = require('string_decoder');
3
+
4
+ function isHex(ch) {
5
+ return /^[0-9a-fA-F]$/.test(ch);
6
+ }
7
+
8
+ function extractJsonStringFieldToFileSync({ jsonPath, fieldName, outPath }) {
9
+ const fd = fs.openSync(jsonPath, 'r');
10
+ let outFd = null;
11
+
12
+ const decoder = new StringDecoder('utf8');
13
+ const buf = Buffer.alloc(64 * 1024);
14
+
15
+ // Parser state
16
+ let depth = 0;
17
+ let expectingKey = false;
18
+ let state = 'idle'; // idle|skipString|readKey|afterKey|seekValue|readValue
19
+
20
+ let lastKey = '';
21
+ let keyBuf = '';
22
+
23
+ let strEscape = false;
24
+ let unicodeDigitsLeft = 0;
25
+ let unicodeHex = '';
26
+
27
+ // Patch output state
28
+ let outBuf = '';
29
+ let found = false;
30
+ let endedWithNewline = false;
31
+ let pendingHighSurrogate = null;
32
+
33
+ function flushOut() {
34
+ if (!outBuf) return;
35
+ fs.writeSync(outFd, outBuf, null, 'utf8');
36
+ outBuf = '';
37
+ }
38
+
39
+ function writeOut(s) {
40
+ if (!s) return;
41
+ outBuf += s;
42
+ if (s.endsWith('\n')) endedWithNewline = true;
43
+ else endedWithNewline = false;
44
+ if (outBuf.length >= 16 * 1024) flushOut();
45
+ }
46
+
47
+ function resetStringState() {
48
+ strEscape = false;
49
+ unicodeDigitsLeft = 0;
50
+ unicodeHex = '';
51
+ }
52
+
53
+ function decodeUnicodeHex(hex) {
54
+ return String.fromCharCode(parseInt(hex, 16));
55
+ }
56
+
57
+ function handleDecodedCharForPatch(ch) {
58
+ // Handle surrogate pairs from \uXXXX.
59
+ if (pendingHighSurrogate !== null) {
60
+ const hi = pendingHighSurrogate;
61
+ pendingHighSurrogate = null;
62
+
63
+ const loCode = ch.charCodeAt(0);
64
+ if (loCode >= 0xdc00 && loCode <= 0xdfff) {
65
+ const hiCode = hi;
66
+ const codePoint = 0x10000 + ((hiCode - 0xd800) << 10) + (loCode - 0xdc00);
67
+ writeOut(String.fromCodePoint(codePoint));
68
+ return;
69
+ }
70
+
71
+ // Not a low surrogate; emit hi surrogate as-is then continue with current.
72
+ writeOut(String.fromCharCode(hi));
73
+ }
74
+
75
+ const code = ch.charCodeAt(0);
76
+ if (code >= 0xd800 && code <= 0xdbff) {
77
+ pendingHighSurrogate = code;
78
+ return;
79
+ }
80
+
81
+ writeOut(ch);
82
+ }
83
+
84
+ function finishPatch() {
85
+ if (pendingHighSurrogate !== null) {
86
+ writeOut(String.fromCharCode(pendingHighSurrogate));
87
+ pendingHighSurrogate = null;
88
+ }
89
+ if (!endedWithNewline) writeOut('\n');
90
+ flushOut();
91
+ found = true;
92
+ }
93
+
94
+ try {
95
+ function processChunkText(chunk) {
96
+ for (let i = 0; i < chunk.length; i++) {
97
+ const ch = chunk[i];
98
+
99
+ if (state === 'readValue') {
100
+ if (unicodeDigitsLeft > 0) {
101
+ if (!isHex(ch)) throw new Error('invalid unicode escape in patch string');
102
+ unicodeHex += ch;
103
+ unicodeDigitsLeft--;
104
+ if (unicodeDigitsLeft === 0) {
105
+ const decoded = decodeUnicodeHex(unicodeHex);
106
+ unicodeHex = '';
107
+ handleDecodedCharForPatch(decoded);
108
+ }
109
+ continue;
110
+ }
111
+
112
+ if (strEscape) {
113
+ strEscape = false;
114
+ if (ch === '"' || ch === '\\' || ch === '/') handleDecodedCharForPatch(ch);
115
+ else if (ch === 'b') handleDecodedCharForPatch('\b');
116
+ else if (ch === 'f') handleDecodedCharForPatch('\f');
117
+ else if (ch === 'n') handleDecodedCharForPatch('\n');
118
+ else if (ch === 'r') handleDecodedCharForPatch('\r');
119
+ else if (ch === 't') handleDecodedCharForPatch('\t');
120
+ else if (ch === 'u') {
121
+ unicodeDigitsLeft = 4;
122
+ unicodeHex = '';
123
+ } else {
124
+ throw new Error('invalid escape in patch string');
125
+ }
126
+ continue;
127
+ }
128
+
129
+ if (ch === '\\') {
130
+ strEscape = true;
131
+ continue;
132
+ }
133
+ if (ch === '"') {
134
+ finishPatch();
135
+ state = 'idle';
136
+ return true;
137
+ }
138
+
139
+ handleDecodedCharForPatch(ch);
140
+ continue;
141
+ }
142
+
143
+ if (state === 'readKey') {
144
+ if (unicodeDigitsLeft > 0) {
145
+ if (!isHex(ch)) throw new Error('invalid unicode escape in key string');
146
+ unicodeHex += ch;
147
+ unicodeDigitsLeft--;
148
+ if (unicodeDigitsLeft === 0) {
149
+ keyBuf += decodeUnicodeHex(unicodeHex);
150
+ unicodeHex = '';
151
+ }
152
+ continue;
153
+ }
154
+ if (strEscape) {
155
+ strEscape = false;
156
+ if (ch === '"' || ch === '\\' || ch === '/') keyBuf += ch;
157
+ else if (ch === 'b') keyBuf += '\b';
158
+ else if (ch === 'f') keyBuf += '\f';
159
+ else if (ch === 'n') keyBuf += '\n';
160
+ else if (ch === 'r') keyBuf += '\r';
161
+ else if (ch === 't') keyBuf += '\t';
162
+ else if (ch === 'u') {
163
+ unicodeDigitsLeft = 4;
164
+ unicodeHex = '';
165
+ } else {
166
+ throw new Error('invalid escape in key string');
167
+ }
168
+ continue;
169
+ }
170
+ if (ch === '\\') {
171
+ strEscape = true;
172
+ continue;
173
+ }
174
+ if (ch === '"') {
175
+ lastKey = keyBuf;
176
+ keyBuf = '';
177
+ resetStringState();
178
+ state = 'afterKey';
179
+ continue;
180
+ }
181
+ keyBuf += ch;
182
+ continue;
183
+ }
184
+
185
+ if (state === 'skipString') {
186
+ if (unicodeDigitsLeft > 0) {
187
+ if (!isHex(ch)) throw new Error('invalid unicode escape in string');
188
+ unicodeHex += ch;
189
+ unicodeDigitsLeft--;
190
+ if (unicodeDigitsLeft === 0) {
191
+ unicodeHex = '';
192
+ }
193
+ continue;
194
+ }
195
+ if (strEscape) {
196
+ strEscape = false;
197
+ if (ch === 'u') {
198
+ unicodeDigitsLeft = 4;
199
+ unicodeHex = '';
200
+ }
201
+ continue;
202
+ }
203
+ if (ch === '\\') {
204
+ strEscape = true;
205
+ continue;
206
+ }
207
+ if (ch === '"') {
208
+ resetStringState();
209
+ state = 'idle';
210
+ continue;
211
+ }
212
+ continue;
213
+ }
214
+
215
+ if (state === 'afterKey') {
216
+ if (/\s/.test(ch)) continue;
217
+ if (ch !== ':') throw new Error('malformed JSON: expected ":" after key');
218
+ if (depth === 1 && lastKey === fieldName) {
219
+ state = 'seekValue';
220
+ } else {
221
+ state = 'idle';
222
+ }
223
+ continue;
224
+ }
225
+
226
+ if (state === 'seekValue') {
227
+ if (/\s/.test(ch)) continue;
228
+ if (ch !== '"') throw new Error(`field "${fieldName}" is not a JSON string`);
229
+ // Begin patch value. Lazily open outFd here so failures don't create empty files.
230
+ if (!outFd) outFd = fs.openSync(outPath, 'w');
231
+ resetStringState();
232
+ endedWithNewline = false;
233
+ pendingHighSurrogate = null;
234
+ state = 'readValue';
235
+ continue;
236
+ }
237
+
238
+ // idle
239
+ if (ch === '"') {
240
+ resetStringState();
241
+ if (depth === 1 && expectingKey) {
242
+ expectingKey = false;
243
+ state = 'readKey';
244
+ } else {
245
+ state = 'skipString';
246
+ }
247
+ continue;
248
+ }
249
+
250
+ if (ch === '{' || ch === '[') {
251
+ depth++;
252
+ if (ch === '{' && depth === 1) expectingKey = true;
253
+ continue;
254
+ }
255
+ if (ch === '}' || ch === ']') {
256
+ depth = Math.max(0, depth - 1);
257
+ continue;
258
+ }
259
+ if (ch === ',' && depth === 1) {
260
+ expectingKey = true;
261
+ continue;
262
+ }
263
+ }
264
+ return false;
265
+ }
266
+
267
+ while (true) {
268
+ const bytes = fs.readSync(fd, buf, 0, buf.length, null);
269
+ if (bytes <= 0) break;
270
+ const chunk = decoder.write(buf.subarray(0, bytes));
271
+ if (!chunk) continue;
272
+
273
+ if (processChunkText(chunk)) break;
274
+ }
275
+
276
+ if (!found) {
277
+ const tail = decoder.end();
278
+ if (tail) processChunkText(tail);
279
+ }
280
+
281
+ if (!found) throw new Error(`field "${fieldName}" not found`);
282
+ } finally {
283
+ try {
284
+ if (outFd) {
285
+ flushOut();
286
+ fs.closeSync(outFd);
287
+ }
288
+ } catch (_err) {
289
+ // ignore
290
+ }
291
+ try {
292
+ fs.closeSync(fd);
293
+ } catch (_err) {
294
+ // ignore
295
+ }
296
+ }
297
+ }
298
+
299
+ module.exports = {
300
+ extractJsonStringFieldToFileSync
301
+ };
@@ -0,0 +1,26 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const { ensureDir } = require('../lib/utils');
5
+
6
+ function readJson(filePath) {
7
+ const raw = fs.readFileSync(filePath, 'utf8');
8
+ return JSON.parse(raw);
9
+ }
10
+
11
+ function writeJson(filePath, data) {
12
+ ensureDir(path.dirname(filePath));
13
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
14
+ }
15
+
16
+ function writeText(filePath, content) {
17
+ ensureDir(path.dirname(filePath));
18
+ fs.writeFileSync(filePath, content, 'utf8');
19
+ }
20
+
21
+ module.exports = {
22
+ readJson,
23
+ writeJson,
24
+ writeText
25
+ };
26
+