throughline 0.10.2 → 0.10.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/CHANGELOG.md +88 -27
- package/README.ja.md +83 -49
- package/README.md +106 -78
- package/bin/throughline.mjs +32 -13
- package/docs/00_overview.md +56 -42
- package/docs/01_l1_l2_l3_redesign.md +1 -1
- package/docs/02_clear_auto_handoff_plan.md +39 -333
- package/docs/04_public_release_plan.md +73 -190
- package/docs/05_codex_first_roadmap.md +4 -4
- package/docs/06_codex_trim_rollback_fix_plan.md +1 -1
- package/docs/08_codex_dual_support.md +1 -1
- package/docs/09_rollback_context_trim_insight.md +1 -1
- package/docs/12_desktop_clear_handoff_plan.md +6 -213
- package/docs/15_windows_ci_release_latency_plan.md +6 -87
- package/docs/16_readonly_handoff_context_plan.md +7 -38
- package/docs/adr/0005-observer-read-pagination.md +1 -1
- package/docs/adr/0014-two-phase-handoff-ghost-baton.md +1 -1
- package/docs/adr/0019-product-owned-database-migration-acceptance.md +1 -1
- package/docs/adr/0021-grok-host-capture.md +1 -1
- package/docs/adr/0022-cursor-host-capture.md +39 -0
- package/docs/archive/02_clear_auto_handoff_plan.md +350 -0
- package/docs/{03_inheritance_on_clear_only.md → archive/03_inheritance_on_clear_only.md} +22 -22
- package/docs/{07_codex_trim_implementation_plan.md → archive/07_codex_trim_implementation_plan.md} +8 -8
- package/docs/{10_transcript_injection_plan.md → archive/10_transcript_injection_plan.md} +12 -12
- package/docs/archive/12_desktop_clear_handoff_plan.md +218 -0
- package/docs/{14_observer_completed_turn_feed_plan.md → archive/14_observer_completed_turn_feed_plan.md} +7 -7
- package/docs/archive/15_windows_ci_release_latency_plan.md +89 -0
- package/docs/archive/16_readonly_handoff_context_plan.md +40 -0
- package/docs/archive/README.md +28 -15
- package/docs/archive/plan_grok-successor-launch.md +99 -0
- package/docs/archive/room-log_throughline_20260830-155052.md +285 -0
- package/docs/plan_grok-successor-launch.md +6 -97
- package/package.json +19 -11
- package/rag/INDEX.md +2 -2
- package/src/baton.mjs +11 -9
- package/src/cli/handoff-context.test.mjs +36 -0
- package/src/cli/help.test.mjs +5 -0
- package/src/cli/install.mjs +91 -0
- package/src/cli/install.test.mjs +57 -0
- package/src/cli/runtime-errors.mjs +9 -3
- package/src/cli/runtime-errors.test.mjs +13 -13
- package/src/cli/self-update.mjs +402 -0
- package/src/cli/self-update.test.mjs +525 -0
- package/src/db.mjs +1 -1
- package/src/docs-contract.test.mjs +153 -0
- package/src/hosts/claude.mjs +1 -0
- package/src/hosts/codex.mjs +1 -0
- package/src/hosts/cursor.mjs +128 -0
- package/src/hosts/cursor.test.mjs +104 -0
- package/src/hosts/grok.mjs +1 -0
- package/src/hosts/identity.mjs +19 -2
- package/src/hosts/identity.test.mjs +27 -4
- package/src/hosts/index.mjs +11 -2
- package/src/product-ci-contract.test.mjs +14 -0
- package/src/prompt-submit.mjs +8 -10
- package/src/resume-context.mjs +4 -4
- package/src/runtime-error-hook.test.mjs +4 -6
- package/src/runtime-error-store.mjs +40 -18
- package/src/runtime-error-store.test.mjs +53 -26
- package/src/session-merger.mjs +16 -7
- package/src/session-merger.test.mjs +27 -0
- package/src/session-start.mjs +24 -1
- package/src/spike-transcript-writer.mjs +1 -1
- package/src/transcript-reader-cursor.test.mjs +43 -0
- package/src/transcript-reader.mjs +14 -7
- /package/docs/{11_codex_monitor_implementation_plan.md → archive/11_codex_monitor_implementation_plan.md} +0 -0
- /package/docs/{13_native_factory_diagnostics_plan.md → archive/13_native_factory_diagnostics_plan.md} +0 -0
- /package/docs/{BUGHUB_RUNTIME_ERROR_STORE_PLAN.md → archive/BUGHUB_RUNTIME_ERROR_STORE_PLAN.md} +0 -0
package/src/cli/install.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* script path で登録する。
|
|
12
12
|
* Grok-facing hook も Desktop の GUI PATH に throughline が無いため、同じ絶対
|
|
13
13
|
* node + CLI script path で ~/.grok/hooks/throughline.json に書く。
|
|
14
|
+
* Cursor-facing hook は ~/.cursor/hooks.json へ upsert する(工場 hook は残す)。
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, copyFileSync, unlinkSync, rmSync, realpathSync } from 'node:fs';
|
|
@@ -27,6 +28,7 @@ const CODEX_SKILL_NAMES = ['throughline'];
|
|
|
27
28
|
const CODEX_HOOKS_RELATIVE_PATH = ['.codex', 'hooks.json'];
|
|
28
29
|
const CODEX_CONFIG_RELATIVE_PATH = ['.codex', 'config.toml'];
|
|
29
30
|
const GROK_HOOKS_RELATIVE_PATH = ['.grok', 'hooks', 'throughline.json'];
|
|
31
|
+
const CURSOR_HOOKS_RELATIVE_PATH = ['.cursor', 'hooks.json'];
|
|
30
32
|
|
|
31
33
|
// Throughline が管理する hook コマンド一覧
|
|
32
34
|
// schema v4 以降: PostToolUse (capture-tool) は廃止。Stop 内で L2/L3 を一括処理する。
|
|
@@ -297,6 +299,16 @@ export function buildGrokHookCommand(subcommand, {
|
|
|
297
299
|
return `${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} ${subcommand}`;
|
|
298
300
|
}
|
|
299
301
|
|
|
302
|
+
export function buildCursorHookCommand(subcommand, options = {}) {
|
|
303
|
+
return buildGrokHookCommand(subcommand, options);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function isThroughlineCursorHookCommand(command) {
|
|
307
|
+
return typeof command === 'string'
|
|
308
|
+
&& command.includes('throughline.mjs')
|
|
309
|
+
&& /\b(session-start|prompt-submit|process-turn)\b/.test(command);
|
|
310
|
+
}
|
|
311
|
+
|
|
300
312
|
export function createGrokHooksFile(options = {}) {
|
|
301
313
|
return {
|
|
302
314
|
hooks: {
|
|
@@ -334,6 +346,74 @@ function uninstallGrokHooks() {
|
|
|
334
346
|
return { hooksPath, removed: 1 };
|
|
335
347
|
}
|
|
336
348
|
|
|
349
|
+
function resolveCursorHooksPath() {
|
|
350
|
+
return join(homedir(), ...CURSOR_HOOKS_RELATIVE_PATH);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function createCursorHookEntries(options = {}) {
|
|
354
|
+
return {
|
|
355
|
+
sessionStart: [
|
|
356
|
+
{ command: buildCursorHookCommand('session-start', options), timeout: 10 },
|
|
357
|
+
],
|
|
358
|
+
beforeSubmitPrompt: [
|
|
359
|
+
{ command: buildCursorHookCommand('prompt-submit', options), timeout: 30 },
|
|
360
|
+
],
|
|
361
|
+
stop: [
|
|
362
|
+
{ command: buildCursorHookCommand('process-turn', options), timeout: 300 },
|
|
363
|
+
],
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function readCursorHooksFile(hooksPath) {
|
|
368
|
+
if (!existsSync(hooksPath)) return { version: 1, hooks: {} };
|
|
369
|
+
const parsed = JSON.parse(readFileSync(hooksPath, 'utf8'));
|
|
370
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
371
|
+
throw new Error(`${hooksPath} は object である必要があります`);
|
|
372
|
+
}
|
|
373
|
+
if (parsed.hooks == null) parsed.hooks = {};
|
|
374
|
+
if (typeof parsed.hooks !== 'object' || Array.isArray(parsed.hooks)) {
|
|
375
|
+
throw new Error(`${hooksPath} の hooks は object である必要があります`);
|
|
376
|
+
}
|
|
377
|
+
return parsed;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function installCursorHooks() {
|
|
381
|
+
const hooksPath = resolveCursorHooksPath();
|
|
382
|
+
mkdirSync(dirname(hooksPath), { recursive: true });
|
|
383
|
+
const current = readCursorHooksFile(hooksPath);
|
|
384
|
+
current.version = 1;
|
|
385
|
+
const wanted = createCursorHookEntries();
|
|
386
|
+
for (const [event, entries] of Object.entries(wanted)) {
|
|
387
|
+
const existing = Array.isArray(current.hooks[event]) ? current.hooks[event] : [];
|
|
388
|
+
const kept = existing.filter((entry) => !isThroughlineCursorHookCommand(entry?.command));
|
|
389
|
+
current.hooks[event] = [...kept, ...entries];
|
|
390
|
+
}
|
|
391
|
+
writeFileSync(hooksPath, `${JSON.stringify(current, null, 2)}\n`);
|
|
392
|
+
return { hooksPath };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function uninstallCursorHooks() {
|
|
396
|
+
const hooksPath = resolveCursorHooksPath();
|
|
397
|
+
if (!existsSync(hooksPath)) return { hooksPath, removed: 0 };
|
|
398
|
+
const current = readCursorHooksFile(hooksPath);
|
|
399
|
+
let removed = 0;
|
|
400
|
+
for (const [event, list] of Object.entries(current.hooks ?? {})) {
|
|
401
|
+
if (!Array.isArray(list)) continue;
|
|
402
|
+
const kept = list.filter((entry) => {
|
|
403
|
+
if (isThroughlineCursorHookCommand(entry?.command)) {
|
|
404
|
+
removed += 1;
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
return true;
|
|
408
|
+
});
|
|
409
|
+
if (kept.length > 0) current.hooks[event] = kept;
|
|
410
|
+
else delete current.hooks[event];
|
|
411
|
+
}
|
|
412
|
+
if (removed === 0) return { hooksPath, removed: 0 };
|
|
413
|
+
writeFileSync(hooksPath, `${JSON.stringify(current, null, 2)}\n`);
|
|
414
|
+
return { hooksPath, removed };
|
|
415
|
+
}
|
|
416
|
+
|
|
337
417
|
function installSlashCommands(commandsDir) {
|
|
338
418
|
if (!existsSync(SLASH_COMMANDS_SRC)) {
|
|
339
419
|
return { installed: [], skipped: 'source-missing' };
|
|
@@ -589,6 +669,7 @@ export async function run(args = []) {
|
|
|
589
669
|
const removedCommands = uninstallSlashCommands(commandsDir);
|
|
590
670
|
const codex = args.includes('--project') ? null : uninstallCodexHooks();
|
|
591
671
|
const grok = args.includes('--project') ? null : uninstallGrokHooks();
|
|
672
|
+
const cursor = args.includes('--project') ? null : uninstallCursorHooks();
|
|
592
673
|
const removedCodexSkills = args.includes('--project') ? [] : uninstallCodexSkills(codexSkillsDir);
|
|
593
674
|
console.log('Throughline hooks を削除しました。');
|
|
594
675
|
console.log(` ${settingsPath}`);
|
|
@@ -601,6 +682,9 @@ export async function run(args = []) {
|
|
|
601
682
|
if (grok?.removed > 0) {
|
|
602
683
|
console.log(` Grok hooks 削除: ${grok.removed} (${grok.hooksPath})`);
|
|
603
684
|
}
|
|
685
|
+
if (cursor?.removed > 0) {
|
|
686
|
+
console.log(` Cursor hooks 削除: ${cursor.removed} (${cursor.hooksPath})`);
|
|
687
|
+
}
|
|
604
688
|
if (removedCodexSkills.length > 0) {
|
|
605
689
|
console.log(` Codex skills 削除: ${removedCodexSkills.join(', ')} (${codexSkillsDir})`);
|
|
606
690
|
}
|
|
@@ -624,6 +708,7 @@ export async function run(args = []) {
|
|
|
624
708
|
const { installed: installedCommands, skipped } = installSlashCommands(commandsDir);
|
|
625
709
|
const codex = args.includes('--project') ? null : installCodexHooks();
|
|
626
710
|
const grok = args.includes('--project') ? null : installGrokHooks();
|
|
711
|
+
const cursor = args.includes('--project') ? null : installCursorHooks();
|
|
627
712
|
const codexSkills = args.includes('--project') ? { installed: [], skipped: null } : installCodexSkills(codexSkillsDir);
|
|
628
713
|
const monitorTask = ensureMonitorTaskFile({
|
|
629
714
|
cwd: process.cwd(),
|
|
@@ -643,6 +728,9 @@ export async function run(args = []) {
|
|
|
643
728
|
if (grok) {
|
|
644
729
|
console.log(` ${grok.hooksPath}`);
|
|
645
730
|
}
|
|
731
|
+
if (cursor) {
|
|
732
|
+
console.log(` ${cursor.hooksPath}`);
|
|
733
|
+
}
|
|
646
734
|
console.log('');
|
|
647
735
|
console.log('有効な hooks:');
|
|
648
736
|
console.log(' SessionStart → throughline session-start (セッション記録・バトン消費・引き継ぎ注入)');
|
|
@@ -656,6 +744,9 @@ export async function run(args = []) {
|
|
|
656
744
|
if (grok) {
|
|
657
745
|
console.log(' Grok SessionStart / UserPromptSubmit / Stop → ~/.grok/hooks/throughline.json');
|
|
658
746
|
}
|
|
747
|
+
if (cursor) {
|
|
748
|
+
console.log(' Cursor sessionStart / beforeSubmitPrompt / stop → ~/.cursor/hooks.json(工場hookは残す)');
|
|
749
|
+
}
|
|
659
750
|
console.log('');
|
|
660
751
|
if (installedCommands.length > 0) {
|
|
661
752
|
console.log(`slash commands を配置しました: ${installedCommands.map(n => '/' + n.replace(/\.md$/, '')).join(', ')}`);
|
package/src/cli/install.test.mjs
CHANGED
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
buildCodexUserPromptSubmitHookCommand,
|
|
11
11
|
buildGrokHookCommand,
|
|
12
12
|
createGrokHooksFile,
|
|
13
|
+
createCursorHookEntries,
|
|
13
14
|
isEquivalentCodexHookCommand,
|
|
14
15
|
isThroughlineCodexHookCommand,
|
|
16
|
+
isThroughlineCursorHookCommand,
|
|
15
17
|
parseCodexHookCommand,
|
|
16
18
|
resolveCodexHookNodePath,
|
|
17
19
|
run,
|
|
@@ -92,6 +94,28 @@ test('global install copies Throughline slash commands to ~/.claude/commands/',
|
|
|
92
94
|
assert.match(command, /throughline\.mjs/);
|
|
93
95
|
assert.doesNotMatch(command, /^throughline /);
|
|
94
96
|
}
|
|
97
|
+
const cursorHooks = JSON.parse(readFileSync(join(home.dir, '.cursor', 'hooks.json'), 'utf8'));
|
|
98
|
+
assert.equal(cursorHooks.version, 1);
|
|
99
|
+
assert.equal(
|
|
100
|
+
cursorHooks.hooks.sessionStart[0].command,
|
|
101
|
+
createCursorHookEntries().sessionStart[0].command,
|
|
102
|
+
);
|
|
103
|
+
assert.equal(
|
|
104
|
+
cursorHooks.hooks.beforeSubmitPrompt[0].command,
|
|
105
|
+
createCursorHookEntries().beforeSubmitPrompt[0].command,
|
|
106
|
+
);
|
|
107
|
+
assert.equal(
|
|
108
|
+
cursorHooks.hooks.stop[0].command,
|
|
109
|
+
createCursorHookEntries().stop[0].command,
|
|
110
|
+
);
|
|
111
|
+
for (const command of [
|
|
112
|
+
cursorHooks.hooks.sessionStart[0].command,
|
|
113
|
+
cursorHooks.hooks.beforeSubmitPrompt[0].command,
|
|
114
|
+
cursorHooks.hooks.stop[0].command,
|
|
115
|
+
]) {
|
|
116
|
+
assert.equal(isThroughlineCursorHookCommand(command), true);
|
|
117
|
+
assert.doesNotMatch(command, /^throughline /);
|
|
118
|
+
}
|
|
95
119
|
} finally {
|
|
96
120
|
unsilence();
|
|
97
121
|
home.restore();
|
|
@@ -192,6 +216,39 @@ test('Grok hook commands are absolute node + throughline.mjs on every platform',
|
|
|
192
216
|
assert.equal(file.hooks.Stop[0].hooks[0].async, true);
|
|
193
217
|
});
|
|
194
218
|
|
|
219
|
+
test('Cursor install upserts product hooks and keeps factory commands', async () => {
|
|
220
|
+
const home = makeTempHome();
|
|
221
|
+
if (home.resolved !== home.dir) {
|
|
222
|
+
home.restore();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const unsilence = silence();
|
|
226
|
+
try {
|
|
227
|
+
mkdirSync(join(home.dir, '.cursor'), { recursive: true });
|
|
228
|
+
writeFileSync(join(home.dir, '.cursor', 'hooks.json'), `${JSON.stringify({
|
|
229
|
+
version: 1,
|
|
230
|
+
hooks: {
|
|
231
|
+
sessionStart: [{ command: '/tmp/cursor-todo-gate-hook session-start', timeout: 10 }],
|
|
232
|
+
},
|
|
233
|
+
}, null, 2)}\n`);
|
|
234
|
+
await run([]);
|
|
235
|
+
const installed = JSON.parse(readFileSync(join(home.dir, '.cursor', 'hooks.json'), 'utf8'));
|
|
236
|
+
assert.equal(installed.hooks.sessionStart[0].command, '/tmp/cursor-todo-gate-hook session-start');
|
|
237
|
+
assert.equal(isThroughlineCursorHookCommand(installed.hooks.sessionStart[1].command), true);
|
|
238
|
+
assert.equal(isThroughlineCursorHookCommand(installed.hooks.beforeSubmitPrompt[0].command), true);
|
|
239
|
+
assert.equal(isThroughlineCursorHookCommand(installed.hooks.stop[0].command), true);
|
|
240
|
+
await run(['--uninstall']);
|
|
241
|
+
const after = JSON.parse(readFileSync(join(home.dir, '.cursor', 'hooks.json'), 'utf8'));
|
|
242
|
+
assert.equal(after.hooks.sessionStart.length, 1);
|
|
243
|
+
assert.equal(after.hooks.sessionStart[0].command, '/tmp/cursor-todo-gate-hook session-start');
|
|
244
|
+
assert.equal(after.hooks.beforeSubmitPrompt, undefined);
|
|
245
|
+
assert.equal(after.hooks.stop, undefined);
|
|
246
|
+
} finally {
|
|
247
|
+
unsilence();
|
|
248
|
+
home.restore();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
195
252
|
test('Codex hook builders use the PowerShell call operator on Windows only', () => {
|
|
196
253
|
const options = {
|
|
197
254
|
nodePath: String.raw`C:\Program Files\nodejs\node.exe`,
|
|
@@ -5,13 +5,14 @@ import {
|
|
|
5
5
|
readRuntimeErrorSnapshot,
|
|
6
6
|
reopenRuntimeError,
|
|
7
7
|
resolveRuntimeError,
|
|
8
|
+
setRuntimeErrorCollectionEnabled,
|
|
8
9
|
} from '../runtime-error-store.mjs';
|
|
9
10
|
|
|
10
|
-
const USAGE = 'usage: throughline runtime-errors <snapshot|diagnostics|ack|resolve|reopen|compact> [arguments] --json';
|
|
11
|
+
const USAGE = 'usage: throughline runtime-errors <enable|disable|snapshot|diagnostics|ack|resolve|reopen|compact> [arguments] --json';
|
|
11
12
|
|
|
12
13
|
export function parseArgs(argv = []) {
|
|
13
14
|
const command = argv[0];
|
|
14
|
-
if (!['snapshot', 'diagnostics', 'ack', 'resolve', 'reopen', 'compact'].includes(command)) {
|
|
15
|
+
if (!['enable', 'disable', 'snapshot', 'diagnostics', 'ack', 'resolve', 'reopen', 'compact'].includes(command)) {
|
|
15
16
|
throw new TypeError(USAGE);
|
|
16
17
|
}
|
|
17
18
|
const options = { command, json: false, afterCursor: 0, limit: 256, value: null };
|
|
@@ -47,7 +48,12 @@ export function run(argv = [], dependencies = {}) {
|
|
|
47
48
|
try {
|
|
48
49
|
const env = dependencies.env ?? process.env;
|
|
49
50
|
let result;
|
|
50
|
-
if (options.command === '
|
|
51
|
+
if (options.command === 'enable' || options.command === 'disable') {
|
|
52
|
+
result = (dependencies.configure ?? setRuntimeErrorCollectionEnabled)(
|
|
53
|
+
options.command === 'enable',
|
|
54
|
+
{ env },
|
|
55
|
+
);
|
|
56
|
+
} else if (options.command === 'snapshot') {
|
|
51
57
|
result = (dependencies.readSnapshot ?? readRuntimeErrorSnapshot)({
|
|
52
58
|
env,
|
|
53
59
|
afterCursor: options.afterCursor,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import {
|
|
4
|
+
import { mkdtempSync } from 'node:fs';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
|
-
import {
|
|
6
|
+
import { join } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
|
|
9
9
|
import { parseArgs, run } from './runtime-errors.mjs';
|
|
10
|
-
import { defaultFactoryReporterConfigPath } from '../runtime-error-store.mjs';
|
|
11
|
-
import { applyWindowsPrivateAcl } from '../os/windows-acl-test-helper.mjs';
|
|
12
10
|
|
|
13
11
|
test('runtime-errors CLI: strict command surface accepts no raw payload options', () => {
|
|
12
|
+
assert.equal(parseArgs(['enable', '--json']).command, 'enable');
|
|
13
|
+
assert.equal(parseArgs(['disable', '--json']).command, 'disable');
|
|
14
14
|
assert.deepEqual(parseArgs(['snapshot', '--after-cursor', '2', '--limit', '3', '--json']), {
|
|
15
15
|
command: 'snapshot', json: true, afterCursor: 2, limit: 3, value: null,
|
|
16
16
|
});
|
|
@@ -52,16 +52,16 @@ test('runtime-errors CLI: snapshot and diagnostics are JSON-only and contain no
|
|
|
52
52
|
XDG_CONFIG_HOME: join(root, 'config'),
|
|
53
53
|
XDG_STATE_HOME: join(root, 'state'),
|
|
54
54
|
};
|
|
55
|
-
const configPath = defaultFactoryReporterConfigPath(env);
|
|
56
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
57
|
-
writeFileSync(configPath, JSON.stringify({
|
|
58
|
-
schema_version: '1.0',
|
|
59
|
-
host: { id: 'test-host', profile: process.platform === 'win32' ? 'windows-native' : 'mac' },
|
|
60
|
-
collection: { enabled: true },
|
|
61
|
-
reporting: { enabled: false },
|
|
62
|
-
}));
|
|
63
|
-
applyWindowsPrivateAcl(configPath);
|
|
64
55
|
const bin = new URL('../../bin/throughline.mjs', import.meta.url);
|
|
56
|
+
const enabled = spawnSync(process.execPath, [fileURLToPath(bin), 'runtime-errors', 'enable', '--json'], {
|
|
57
|
+
env,
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
});
|
|
60
|
+
assert.equal(enabled.status, 0, enabled.stderr);
|
|
61
|
+
assert.deepEqual(JSON.parse(enabled.stdout), {
|
|
62
|
+
schema: 'throughline.runtime_error_config.v1',
|
|
63
|
+
collection: { enabled: true },
|
|
64
|
+
});
|
|
65
65
|
for (const args of [
|
|
66
66
|
['runtime-errors', 'snapshot', '--json'],
|
|
67
67
|
['runtime-errors', 'diagnostics', '--json'],
|