@webpresso/agent-kit 3.1.28 → 3.1.30

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 (98) hide show
  1. package/catalog/AGENTS.md.tpl +4 -4
  2. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  3. package/catalog/agent/rules/ci-test-perf.md +5 -1
  4. package/catalog/agent/rules/cmd-execution.md +9 -0
  5. package/catalog/agent/rules/pre-implementation.md +2 -1
  6. package/catalog/agent/rules/test-scan-perf.md +80 -0
  7. package/catalog/agent/rules/workflow-skills-routing.md +4 -2
  8. package/catalog/agent/skills/fix/SKILL.md +3 -0
  9. package/catalog/agent/skills/verify/SKILL.md +18 -0
  10. package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
  11. package/dist/esm/audit/registry.d.ts +4 -4
  12. package/dist/esm/audit/registry.js +3 -0
  13. package/dist/esm/audit/roadmap-links.js +6 -0
  14. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  15. package/dist/esm/audit/test-scan-perf.js +275 -0
  16. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  17. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  18. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  19. package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
  20. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  21. package/dist/esm/ci/act-replay.js +110 -7
  22. package/dist/esm/ci/act-runner.d.ts +4 -0
  23. package/dist/esm/ci/act-runner.js +85 -26
  24. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  25. package/dist/esm/ci/act-worktree-git.js +219 -0
  26. package/dist/esm/cli/commands/audit.js +3 -0
  27. package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
  28. package/dist/esm/cli/commands/blueprint/router.js +27 -2
  29. package/dist/esm/cli/commands/ci.d.ts +8 -1
  30. package/dist/esm/cli/commands/ci.js +80 -21
  31. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  32. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  33. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  34. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  35. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  36. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  37. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  38. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  39. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  40. package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
  41. package/dist/esm/cli/commands/dash/plan-once.js +41 -4
  42. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  43. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  44. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  45. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  46. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  47. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  48. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  49. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  50. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  51. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  52. package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
  53. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  54. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  55. package/dist/esm/cli/commands/format.js +0 -2
  56. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  57. package/dist/esm/cli/commands/init/config.js +1 -4
  58. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  59. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  60. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  61. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  62. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  63. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  64. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
  65. package/dist/esm/cli/commands/logs.d.ts +15 -0
  66. package/dist/esm/cli/commands/logs.js +192 -3
  67. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  68. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  69. package/dist/esm/cli/commands/review.js +5 -8
  70. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  71. package/dist/esm/cli/commands/secrets.js +4 -1
  72. package/dist/esm/cli/direct-provider-launch.js +25 -1
  73. package/dist/esm/hooks/doctor.d.ts +15 -0
  74. package/dist/esm/hooks/doctor.js +56 -0
  75. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
  76. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  77. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  78. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  79. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  80. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  81. package/dist/esm/mcp/tools/audit.d.ts +2 -1
  82. package/dist/esm/mcp/tools/audit.js +10 -0
  83. package/dist/esm/mcp/tools/audits.d.ts +1 -0
  84. package/dist/esm/mcp/tools/ci-act.js +9 -11
  85. package/dist/esm/paths/state-root.js +3 -0
  86. package/dist/esm/review/authority.js +17 -2
  87. package/dist/esm/review/availability.js +18 -5
  88. package/dist/esm/review/subject.d.ts +15 -0
  89. package/dist/esm/review/subject.js +105 -10
  90. package/dist/esm/runtime/executor.d.ts +1 -0
  91. package/dist/esm/runtime/executor.js +2 -1
  92. package/dist/esm/secret-gate/runner.d.ts +1 -0
  93. package/dist/esm/secret-gate/runner.js +4 -1
  94. package/dist/esm/worktrees/current.d.ts +1 -0
  95. package/dist/esm/worktrees/current.js +1 -0
  96. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  97. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  98. package/package.json +14 -14
@@ -23,6 +23,7 @@ import { accessSync, constants, existsSync, mkdirSync, readFileSync, statSync }
23
23
  import { homedir } from "node:os";
24
24
  import { dirname, join } from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
+ import lockfile from "proper-lockfile";
26
27
  import { isJsonRecord, isUnsafeRuntimeLauncher, parseJson, parseProjectMcpServers, parseTomlString, } from "#hooks/host-config-contract.js";
27
28
  import { appendGlobalCapableVpArgs, resolveGlobalCapableVpCommand, } from "#cli/global-vp.js";
28
29
  import { pathCandidates } from "#runtime/command-exists.js";
@@ -41,6 +42,57 @@ const PLAYWRIGHT_STDIO_MCP_DEFINITION = {
41
42
  command: "vp",
42
43
  args: ["dlx", PLAYWRIGHT_MCP_PACKAGE, ...PLAYWRIGHT_MCP_ARGS],
43
44
  };
45
+ const CODEX_CONFIG_LOCK_TIMEOUT_MS = 5_000;
46
+ const CODEX_CONFIG_LOCK_POLL_MS = 25;
47
+ const CODEX_CONFIG_LOCK_STALE_MS = 30_000;
48
+ function sleepSync(ms) {
49
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
50
+ }
51
+ function acquireCodexConfigLock(configPath) {
52
+ mkdirSync(dirname(configPath), { recursive: true });
53
+ const startedAt = Date.now();
54
+ while (true) {
55
+ try {
56
+ return lockfile.lockSync(configPath, {
57
+ realpath: false,
58
+ stale: CODEX_CONFIG_LOCK_STALE_MS,
59
+ update: CODEX_CONFIG_LOCK_STALE_MS / 2,
60
+ });
61
+ }
62
+ catch (error) {
63
+ if (error.code !== "ELOCKED")
64
+ throw error;
65
+ if (Date.now() - startedAt >= CODEX_CONFIG_LOCK_TIMEOUT_MS) {
66
+ throw new Error(`Timed out waiting for Codex config lock: ${configPath}`, {
67
+ cause: error,
68
+ });
69
+ }
70
+ sleepSync(CODEX_CONFIG_LOCK_POLL_MS);
71
+ }
72
+ }
73
+ }
74
+ function withCodexConfigLock(configPath, action) {
75
+ const release = acquireCodexConfigLock(configPath);
76
+ try {
77
+ return action();
78
+ }
79
+ finally {
80
+ release();
81
+ }
82
+ }
83
+ function readCodexConfig(configPath) {
84
+ return existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
85
+ }
86
+ function updateCodexConfig(configPath, transform) {
87
+ return withCodexConfigLock(configPath, () => {
88
+ const existing = readCodexConfig(configPath);
89
+ const next = transform(existing);
90
+ if (next === existing)
91
+ return "unchanged";
92
+ writeFileAtomic(configPath, next, "utf8");
93
+ return "written";
94
+ });
95
+ }
44
96
  function tomlString(value) {
45
97
  return JSON.stringify(value);
46
98
  }
@@ -271,13 +323,8 @@ export function ensurePinnedCodexMcp(input, definition) {
271
323
  reason: provisioned.reason,
272
324
  };
273
325
  }
274
- const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
275
- const next = upsertCommandMcpServer(existing, provisioned.definition);
276
- if (next === existing)
277
- return { kind: "unchanged", path: configPath };
278
- mkdirSync(dirname(configPath), { recursive: true });
279
- writeFileAtomic(configPath, next, "utf8");
280
- return { kind: "written", path: configPath };
326
+ const result = updateCodexConfig(configPath, (existing) => upsertCommandMcpServer(existing, provisioned.definition));
327
+ return { kind: result === "written" ? "written" : "unchanged", path: configPath };
281
328
  }
282
329
  function removeUnsafePackageManagerMcpBlocks(raw) {
283
330
  const lines = raw.split(/\r?\n/u);
@@ -329,15 +376,11 @@ function removeUnsafePackageManagerMcpBlocks(raw) {
329
376
  export function ensureCodexProjectMcpServers(input) {
330
377
  const configPath = input.configPath ?? defaultConfigPath();
331
378
  const sourcePath = input.sourcePath ?? join(input.repoRoot, ".mcp.json");
332
- const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
333
- const withoutUnsafeLaunchers = removeUnsafePackageManagerMcpBlocks(existing);
334
- const cleanedUnsafeLaunchers = withoutUnsafeLaunchers !== existing;
335
379
  const persistSafetyCleanup = () => {
336
- if (!cleanedUnsafeLaunchers || input.options.dryRun)
380
+ if (input.options.dryRun)
337
381
  return undefined;
338
- mkdirSync(dirname(configPath), { recursive: true });
339
- writeFileAtomic(configPath, withoutUnsafeLaunchers, "utf8");
340
- return true;
382
+ const result = updateCodexConfig(configPath, removeUnsafePackageManagerMcpBlocks);
383
+ return result === "written" ? true : undefined;
341
384
  };
342
385
  const parsed = existsSync(sourcePath)
343
386
  ? parseProjectMcpServers(readFileSync(sourcePath, "utf8"))
@@ -368,12 +411,10 @@ export function ensureCodexProjectMcpServers(input) {
368
411
  }
369
412
  provisioned.push(result.definition);
370
413
  }
371
- const next = provisioned.reduce(upsertCommandMcpServer, withoutUnsafeLaunchers);
372
- if (next === existing) {
414
+ const result = updateCodexConfig(configPath, (existing) => provisioned.reduce(upsertCommandMcpServer, removeUnsafePackageManagerMcpBlocks(existing)));
415
+ if (result === "unchanged") {
373
416
  return { kind: "codex-project-mcp-unchanged", path: configPath, servers };
374
417
  }
375
- mkdirSync(dirname(configPath), { recursive: true });
376
- writeFileAtomic(configPath, next, "utf8");
377
418
  return { kind: "codex-project-mcp-written", path: configPath, servers };
378
419
  }
379
420
  function claudePlaywrightServer() {
@@ -561,12 +602,9 @@ export function ensureCodexWebpressoMcp(input) {
561
602
  const checked = resolution?.checked ?? [];
562
603
  return { kind: "codex-webpresso-mcp-not-installed", path: configPath, checked };
563
604
  }
564
- const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
565
- const next = upsertWebpressoMcpServer(existing, entryPath);
566
- if (next === existing) {
605
+ const result = updateCodexConfig(configPath, (existing) => upsertWebpressoMcpServer(existing, entryPath));
606
+ if (result === "unchanged") {
567
607
  return { kind: "codex-webpresso-mcp-unchanged", path: configPath, entryPath };
568
608
  }
569
- mkdirSync(dirname(configPath), { recursive: true });
570
- writeFileAtomic(configPath, next, "utf8");
571
609
  return { kind: "codex-webpresso-mcp-written", path: configPath, entryPath };
572
610
  }
@@ -1,3 +1,18 @@
1
1
  import type { CAC } from "cac";
2
+ interface LogsCommandOptions {
3
+ readonly follow?: boolean;
4
+ readonly tail?: string | number | boolean;
5
+ readonly watch?: boolean;
6
+ }
7
+ interface FollowLogFileOptions {
8
+ readonly pollIntervalMs?: number;
9
+ readonly signal?: AbortSignal;
10
+ readonly write?: (chunk: string) => void;
11
+ }
2
12
  export declare const LOGS_COMMAND_HELP: string;
3
13
  export declare function registerLogsCommand(cli: CAC): void;
14
+ export declare function parseTailLineCount(value: LogsCommandOptions["tail"]): number | null;
15
+ export declare function tailLogContent(content: string, lineCount: number): string;
16
+ export declare function readTailLogContent(logPath: string, lineCount: number, chunkSize?: number): string;
17
+ export declare function followLogFile(logPath: string, startOffset: number, options?: FollowLogFileOptions): Promise<number>;
18
+ export {};
@@ -1,16 +1,22 @@
1
- import { readFileSync } from "node:fs";
1
+ import { closeSync, createReadStream, openSync, readFileSync, readSync, statSync, unwatchFile, watchFile, } from "node:fs";
2
2
  import { CLI_LOG_COMMANDS, isCliLogCommandName, readCliLogEntry } from "./quality-log-store.js";
3
+ const TAIL_READ_CHUNK_BYTES = 64 * 1024;
3
4
  export const LOGS_COMMAND_HELP = [
4
5
  "Print persisted raw output for a recent summary-first quality command run.",
5
6
  "",
6
7
  "Examples:",
7
8
  " wp logs test",
8
9
  " wp logs qa 2",
10
+ " wp logs test --tail 200",
11
+ " wp logs test --tail 200 --follow",
9
12
  ].join("\n");
10
13
  export function registerLogsCommand(cli) {
11
14
  cli
12
15
  .command("logs <command> [n]", LOGS_COMMAND_HELP)
13
- .action((command, n) => {
16
+ .option("--follow", "Keep printing appended output from the selected log")
17
+ .option("--tail <lines>", "Print only the last N lines from the selected log")
18
+ .option("--watch", "Alias for --follow")
19
+ .action((command, n, options = {}) => {
14
20
  if (!isCliLogCommandName(command)) {
15
21
  console.error(`Unknown logs command: ${command}. Expected one of: ${CLI_LOG_COMMANDS.join(", ")}`);
16
22
  return 1;
@@ -20,12 +26,195 @@ export function registerLogsCommand(cli) {
20
26
  console.error("Usage: wp logs <command> [n]\n`n` must be an integer in the range 1..10.");
21
27
  return 1;
22
28
  }
29
+ const tailLines = parseTailLineCount(options.tail);
30
+ if (tailLines !== null && tailLines < 1) {
31
+ console.error("Usage: wp logs <command> [n] [--tail <lines>]\n`tail` must be a positive integer.");
32
+ return 1;
33
+ }
23
34
  const entry = readCliLogEntry(command, ordinal);
24
35
  if (!entry) {
25
36
  console.log(`No logs yet for ${command}.`);
26
37
  return 0;
27
38
  }
28
- process.stdout.write(readFileSync(entry.logPath, "utf8"));
39
+ const snapshot = tailLines === null
40
+ ? readFullLogSnapshot(entry.logPath)
41
+ : readTailLogSnapshot(entry.logPath, tailLines);
42
+ const printed = snapshot.content;
43
+ process.stdout.write(printed);
44
+ if (options.follow === true || options.watch === true) {
45
+ return followLogFile(entry.logPath, snapshot.offset);
46
+ }
29
47
  return 0;
30
48
  });
31
49
  }
50
+ export function parseTailLineCount(value) {
51
+ if (value === undefined || value === null || value === false)
52
+ return null;
53
+ if (value === true)
54
+ return 0;
55
+ const count = typeof value === "number" ? value : Number(value);
56
+ if (!Number.isInteger(count) || count < 1)
57
+ return 0;
58
+ return count;
59
+ }
60
+ export function tailLogContent(content, lineCount) {
61
+ const lines = content.split("\n");
62
+ const trailingNewline = lines.at(-1) === "";
63
+ const body = trailingNewline ? lines.slice(0, -1) : lines;
64
+ const selected = body.slice(-lineCount);
65
+ if (selected.length === 0)
66
+ return "";
67
+ return `${selected.join("\n")}${trailingNewline ? "\n" : ""}`;
68
+ }
69
+ export function readTailLogContent(logPath, lineCount, chunkSize = TAIL_READ_CHUNK_BYTES) {
70
+ return readTailLogSnapshot(logPath, lineCount, chunkSize).content;
71
+ }
72
+ function readFullLogSnapshot(logPath) {
73
+ const content = readFileSync(logPath, "utf8");
74
+ return { content, offset: Buffer.byteLength(content) };
75
+ }
76
+ function readTailLogSnapshot(logPath, lineCount, chunkSize = TAIL_READ_CHUNK_BYTES) {
77
+ const fileSize = statSync(logPath).size;
78
+ if (fileSize === 0 || lineCount < 1)
79
+ return { content: "", offset: fileSize };
80
+ const fd = openSync(logPath, "r");
81
+ try {
82
+ const hasTrailingNewline = readLastByte(fd, fileSize) === 0x0a;
83
+ const targetNewlines = hasTrailingNewline ? lineCount + 1 : lineCount;
84
+ const chunks = [];
85
+ let position = fileSize;
86
+ let newlineCount = 0;
87
+ while (position > 0 && newlineCount < targetNewlines) {
88
+ const bytesToRead = Math.min(chunkSize, position);
89
+ position -= bytesToRead;
90
+ const buffer = Buffer.allocUnsafe(bytesToRead);
91
+ const bytesRead = readSync(fd, buffer, 0, bytesToRead, position);
92
+ const chunk = buffer.subarray(0, bytesRead);
93
+ chunks.unshift(chunk);
94
+ newlineCount += countNewlines(chunk);
95
+ }
96
+ return {
97
+ content: tailLogContent(Buffer.concat(chunks).toString("utf8"), lineCount),
98
+ offset: fileSize,
99
+ };
100
+ }
101
+ finally {
102
+ closeSync(fd);
103
+ }
104
+ }
105
+ function readLastByte(fd, fileSize) {
106
+ if (fileSize < 1)
107
+ return null;
108
+ const buffer = Buffer.allocUnsafe(1);
109
+ const bytesRead = readSync(fd, buffer, 0, 1, fileSize - 1);
110
+ return bytesRead === 1 ? buffer.readUInt8(0) : null;
111
+ }
112
+ function countNewlines(buffer) {
113
+ let count = 0;
114
+ for (const byte of buffer) {
115
+ if (byte === 0x0a)
116
+ count++;
117
+ }
118
+ return count;
119
+ }
120
+ export function followLogFile(logPath, startOffset, options = {}) {
121
+ if (options.signal?.aborted === true)
122
+ return Promise.resolve(0);
123
+ let offset = startOffset;
124
+ let pendingSize = startOffset;
125
+ let reading = false;
126
+ let readGeneration = 0;
127
+ let settled = false;
128
+ let activeStream = null;
129
+ let poll = null;
130
+ const write = options.write ??
131
+ ((chunk) => {
132
+ process.stdout.write(chunk);
133
+ });
134
+ let resolvePromise = null;
135
+ const settle = () => {
136
+ if (settled)
137
+ return;
138
+ settled = true;
139
+ unwatchFile(logPath, onChange);
140
+ activeStream?.destroy();
141
+ if (poll !== null)
142
+ clearInterval(poll);
143
+ process.off("SIGINT", onSignal);
144
+ process.off("SIGTERM", onSignal);
145
+ options.signal?.removeEventListener("abort", onSignal);
146
+ resolvePromise?.(0);
147
+ };
148
+ const onSignal = () => {
149
+ settle();
150
+ };
151
+ const drain = () => {
152
+ if (settled || reading || pendingSize <= offset)
153
+ return;
154
+ reading = true;
155
+ const nextOffset = pendingSize;
156
+ const generation = readGeneration;
157
+ const stream = createReadStream(logPath, {
158
+ encoding: "utf8",
159
+ start: offset,
160
+ end: nextOffset - 1,
161
+ });
162
+ activeStream = stream;
163
+ stream.on("data", (chunk) => {
164
+ if (settled)
165
+ return;
166
+ write(String(chunk));
167
+ });
168
+ stream.on("close", () => {
169
+ if (activeStream === stream)
170
+ activeStream = null;
171
+ if (settled || generation !== readGeneration)
172
+ return;
173
+ offset = nextOffset;
174
+ reading = false;
175
+ drain();
176
+ });
177
+ stream.on("error", onSignal);
178
+ };
179
+ const onChange = (current) => {
180
+ if (settled)
181
+ return;
182
+ if (current.size < pendingSize) {
183
+ readGeneration++;
184
+ activeStream?.destroy();
185
+ activeStream = null;
186
+ reading = false;
187
+ offset = 0;
188
+ pendingSize = current.size;
189
+ drain();
190
+ return;
191
+ }
192
+ if (current.size <= pendingSize)
193
+ return;
194
+ pendingSize = current.size;
195
+ drain();
196
+ };
197
+ return new Promise((resolve) => {
198
+ resolvePromise = resolve;
199
+ process.once("SIGINT", onSignal);
200
+ process.once("SIGTERM", onSignal);
201
+ options.signal?.addEventListener("abort", onSignal, { once: true });
202
+ watchFile(logPath, { interval: options.pollIntervalMs ?? 250 }, onChange);
203
+ poll = setInterval(() => {
204
+ if (settled)
205
+ return;
206
+ try {
207
+ onChange(statSync(logPath));
208
+ }
209
+ catch (error) {
210
+ if (isMissingFileError(error))
211
+ return;
212
+ onSignal();
213
+ }
214
+ }, options.pollIntervalMs ?? 250);
215
+ onChange(statSync(logPath));
216
+ });
217
+ }
218
+ function isMissingFileError(error) {
219
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
220
+ }
@@ -6,6 +6,7 @@ export interface CliLogEntry {
6
6
  readonly timestamp: string;
7
7
  readonly exitCode: number;
8
8
  readonly logPath: string;
9
+ readonly active?: boolean;
9
10
  readonly options?: Record<string, unknown>;
10
11
  readonly summary?: string;
11
12
  }