@kibitzsh/kibitz 0.0.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +2662 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/core/commentary.d.ts +69 -0
  8. package/dist/core/commentary.d.ts.map +1 -0
  9. package/dist/core/commentary.js +1041 -0
  10. package/dist/core/commentary.js.map +1 -0
  11. package/dist/core/parsers/claude.d.ts +3 -0
  12. package/dist/core/parsers/claude.d.ts.map +1 -0
  13. package/dist/core/parsers/claude.js +124 -0
  14. package/dist/core/parsers/claude.js.map +1 -0
  15. package/dist/core/parsers/codex.d.ts +3 -0
  16. package/dist/core/parsers/codex.d.ts.map +1 -0
  17. package/dist/core/parsers/codex.js +133 -0
  18. package/dist/core/parsers/codex.js.map +1 -0
  19. package/dist/core/platform-support.d.ts +17 -0
  20. package/dist/core/platform-support.d.ts.map +1 -0
  21. package/dist/core/platform-support.js +146 -0
  22. package/dist/core/platform-support.js.map +1 -0
  23. package/dist/core/providers/anthropic.d.ts +15 -0
  24. package/dist/core/providers/anthropic.d.ts.map +1 -0
  25. package/dist/core/providers/anthropic.js +236 -0
  26. package/dist/core/providers/anthropic.js.map +1 -0
  27. package/dist/core/providers/openai.d.ts +16 -0
  28. package/dist/core/providers/openai.d.ts.map +1 -0
  29. package/dist/core/providers/openai.js +154 -0
  30. package/dist/core/providers/openai.js.map +1 -0
  31. package/dist/core/session-dispatch.d.ts +28 -0
  32. package/dist/core/session-dispatch.d.ts.map +1 -0
  33. package/dist/core/session-dispatch.js +453 -0
  34. package/dist/core/session-dispatch.js.map +1 -0
  35. package/dist/core/types.d.ts +78 -0
  36. package/dist/core/types.d.ts.map +1 -0
  37. package/dist/core/types.js +22 -0
  38. package/dist/core/types.js.map +1 -0
  39. package/dist/core/watcher.d.ts +23 -0
  40. package/dist/core/watcher.d.ts.map +1 -0
  41. package/dist/core/watcher.js +866 -0
  42. package/dist/core/watcher.js.map +1 -0
  43. package/package.json +74 -0
@@ -0,0 +1,1041 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/core/commentary.ts
21
+ var commentary_exports = {};
22
+ __export(commentary_exports, {
23
+ CommentaryEngine: () => CommentaryEngine,
24
+ applyAssessmentSignals: () => applyAssessmentSignals,
25
+ buildCommentaryAssessment: () => buildCommentaryAssessment,
26
+ sanitizeGeneratedCommentary: () => sanitizeGeneratedCommentary
27
+ });
28
+ module.exports = __toCommonJS(commentary_exports);
29
+ var import_events = require("events");
30
+
31
+ // src/core/types.ts
32
+ var COMMENTARY_STYLE_OPTIONS = [
33
+ { id: "bullets", label: "Bullet list" },
34
+ { id: "one-liner", label: "One-liner" },
35
+ { id: "headline-context", label: "Headline + context" },
36
+ { id: "numbered-progress", label: "Numbered progress" },
37
+ { id: "short-question", label: "Short + question" },
38
+ { id: "table", label: "Table" },
39
+ { id: "emoji-bullets", label: "Emoji bullets" },
40
+ { id: "scoreboard", label: "Scoreboard" }
41
+ ];
42
+ var MODELS = [
43
+ { id: "claude-opus-4-6", label: "Claude Opus", provider: "anthropic" },
44
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet", provider: "anthropic" },
45
+ { id: "claude-haiku-4-5-20251001", label: "Claude Haiku", provider: "anthropic" },
46
+ { id: "gpt-4o", label: "GPT-4o", provider: "openai" },
47
+ { id: "gpt-4o-mini", label: "GPT-4o mini", provider: "openai" }
48
+ ];
49
+ var DEFAULT_MODEL = "claude-opus-4-6";
50
+
51
+ // src/core/providers/anthropic.ts
52
+ var import_child_process = require("child_process");
53
+ var import_fs = require("fs");
54
+ var import_path = require("path");
55
+ var import_os = require("os");
56
+ var cachedClaudePath = null;
57
+ var PROVIDER_TIMEOUT_MS = 9e4;
58
+ function resolveNodeScript(cmdPath) {
59
+ if (process.platform !== "win32" || !cmdPath.endsWith(".cmd")) return null;
60
+ try {
61
+ const content = (0, import_fs.readFileSync)(cmdPath, "utf-8");
62
+ const match = content.match(/%dp0%\\(.+?\.js)/);
63
+ if (match) {
64
+ const script = (0, import_path.join)((0, import_path.dirname)(cmdPath), match[1]);
65
+ if ((0, import_fs.existsSync)(script)) return script;
66
+ }
67
+ } catch {
68
+ }
69
+ return null;
70
+ }
71
+ function resolveClaudePath() {
72
+ if (cachedClaudePath) return cachedClaudePath;
73
+ const home = (0, import_os.homedir)();
74
+ const isWindows = process.platform === "win32";
75
+ const candidates = isWindows ? [
76
+ (0, import_path.join)(home, ".claude", "bin", "claude.exe"),
77
+ (0, import_path.join)(home, "AppData", "Local", "Programs", "claude-code", "claude.exe"),
78
+ (0, import_path.join)(home, "AppData", "Local", "Claude", "claude.exe"),
79
+ (0, import_path.join)(home, "AppData", "Local", "Microsoft", "WinGet", "Links", "claude.exe"),
80
+ "C:\\Program Files\\Claude\\claude.exe",
81
+ (0, import_path.join)(home, "AppData", "Roaming", "npm", "claude.cmd")
82
+ ] : [
83
+ (0, import_path.join)(home, ".local", "bin", "claude"),
84
+ (0, import_path.join)(home, ".claude", "bin", "claude"),
85
+ "/usr/local/bin/claude",
86
+ "/usr/bin/claude",
87
+ "/snap/bin/claude",
88
+ "/opt/homebrew/bin/claude"
89
+ ];
90
+ for (const p of candidates) {
91
+ if ((0, import_fs.existsSync)(p)) {
92
+ cachedClaudePath = p;
93
+ return p;
94
+ }
95
+ }
96
+ const env = { ...process.env };
97
+ delete env.ELECTRON_RUN_AS_NODE;
98
+ delete env.CLAUDECODE;
99
+ if (isWindows) {
100
+ try {
101
+ const resolved = (0, import_child_process.execSync)("where claude", {
102
+ encoding: "utf-8",
103
+ env,
104
+ timeout: 5e3
105
+ }).trim().split("\n")[0].trim();
106
+ if (resolved && (0, import_fs.existsSync)(resolved)) {
107
+ cachedClaudePath = resolved;
108
+ return resolved;
109
+ }
110
+ } catch {
111
+ }
112
+ } else {
113
+ const shells = ["/bin/zsh", "/bin/bash"];
114
+ for (const sh of shells) {
115
+ if (!(0, import_fs.existsSync)(sh)) continue;
116
+ try {
117
+ const resolved = (0, import_child_process.execSync)(`${sh} -lic 'which claude'`, {
118
+ encoding: "utf-8",
119
+ env,
120
+ timeout: 5e3
121
+ }).trim();
122
+ if (resolved && (0, import_fs.existsSync)(resolved)) {
123
+ cachedClaudePath = resolved;
124
+ return resolved;
125
+ }
126
+ } catch {
127
+ }
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ var AnthropicProvider = class {
133
+ async generate(systemPrompt, userPrompt, _apiKey, model, onChunk) {
134
+ return new Promise((resolve, reject) => {
135
+ const claudePath = resolveClaudePath();
136
+ if (!claudePath) {
137
+ reject(new Error("Claude CLI not found. Install from https://docs.anthropic.com/en/docs/claude-code"));
138
+ return;
139
+ }
140
+ const fullPrompt = `${systemPrompt}
141
+
142
+ ${userPrompt}`;
143
+ const args = [
144
+ "-p",
145
+ fullPrompt,
146
+ "--output-format",
147
+ "stream-json",
148
+ "--verbose",
149
+ "--max-turns",
150
+ "1",
151
+ "--model",
152
+ model
153
+ ];
154
+ const env = { ...process.env };
155
+ delete env.ELECTRON_RUN_AS_NODE;
156
+ delete env.CLAUDECODE;
157
+ let proc;
158
+ try {
159
+ const nodeScript = resolveNodeScript(claudePath);
160
+ if (nodeScript) {
161
+ proc = (0, import_child_process.spawn)(process.execPath, [nodeScript, ...args], {
162
+ cwd: (0, import_os.homedir)(),
163
+ env,
164
+ stdio: ["ignore", "pipe", "pipe"],
165
+ windowsHide: true
166
+ });
167
+ } else {
168
+ proc = (0, import_child_process.spawn)(claudePath, args, {
169
+ cwd: (0, import_os.homedir)(),
170
+ env,
171
+ stdio: ["ignore", "pipe", "pipe"],
172
+ windowsHide: true,
173
+ shell: process.platform === "win32"
174
+ });
175
+ }
176
+ } catch (err) {
177
+ reject(new Error(`Failed to spawn claude CLI: ${err instanceof Error ? err.message : String(err)}`));
178
+ return;
179
+ }
180
+ if (!proc.stdout || !proc.stderr) {
181
+ reject(new Error("Failed to create stdio pipes for Claude CLI"));
182
+ try {
183
+ proc.kill();
184
+ } catch {
185
+ }
186
+ return;
187
+ }
188
+ let full = "";
189
+ let buffer = "";
190
+ let stderr = "";
191
+ let timedOut = false;
192
+ proc.stdout.on("data", (data) => {
193
+ buffer += data.toString();
194
+ const lines = buffer.split("\n");
195
+ buffer = lines.pop() ?? "";
196
+ for (const line of lines) {
197
+ if (!line.trim()) continue;
198
+ try {
199
+ const event = JSON.parse(line);
200
+ if (event.type === "assistant") {
201
+ const content = event.message?.content;
202
+ if (Array.isArray(content)) {
203
+ for (const block of content) {
204
+ if (block.type === "text" && typeof block.text === "string") {
205
+ full += block.text;
206
+ onChunk(block.text);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ if (event.type === "result" && event.result) {
212
+ if (!full) {
213
+ full = String(event.result);
214
+ onChunk(full);
215
+ }
216
+ }
217
+ } catch {
218
+ }
219
+ }
220
+ });
221
+ proc.stderr.on("data", (data) => {
222
+ stderr += data.toString();
223
+ });
224
+ const timeout = setTimeout(() => {
225
+ timedOut = true;
226
+ try {
227
+ proc.kill();
228
+ } catch {
229
+ }
230
+ }, PROVIDER_TIMEOUT_MS);
231
+ proc.on("close", (code) => {
232
+ clearTimeout(timeout);
233
+ if (timedOut && !full) {
234
+ reject(new Error(`Claude CLI timed out after ${PROVIDER_TIMEOUT_MS}ms`));
235
+ return;
236
+ }
237
+ if (code !== 0 && !full) {
238
+ reject(new Error(`Claude CLI exited with code ${code}: ${stderr.slice(0, 500)}`));
239
+ } else {
240
+ resolve(full);
241
+ }
242
+ });
243
+ proc.on("error", (err) => {
244
+ clearTimeout(timeout);
245
+ reject(new Error(`Claude CLI error: ${err.message}`));
246
+ });
247
+ });
248
+ }
249
+ };
250
+
251
+ // src/core/providers/openai.ts
252
+ var import_child_process2 = require("child_process");
253
+ var import_fs2 = require("fs");
254
+ var import_path2 = require("path");
255
+ var import_os2 = require("os");
256
+ var cachedCodexScript = null;
257
+ var codexScriptResolved = false;
258
+ var PROVIDER_TIMEOUT_MS2 = 9e4;
259
+ function resolveCodexNodeScript() {
260
+ if (codexScriptResolved) return cachedCodexScript;
261
+ codexScriptResolved = true;
262
+ if (process.platform !== "win32") return null;
263
+ try {
264
+ const cmdPath = (0, import_child_process2.execSync)("where codex.cmd", {
265
+ encoding: "utf-8",
266
+ timeout: 5e3,
267
+ stdio: ["ignore", "pipe", "ignore"]
268
+ }).trim().split("\n")[0].trim();
269
+ const content = (0, import_fs2.readFileSync)(cmdPath, "utf-8");
270
+ const match = content.match(/%dp0%\\(.+?\.js)/);
271
+ if (!match) return null;
272
+ const script = (0, import_path2.join)((0, import_path2.dirname)(cmdPath), match[1]);
273
+ if ((0, import_fs2.existsSync)(script)) {
274
+ cachedCodexScript = script;
275
+ return script;
276
+ }
277
+ } catch {
278
+ }
279
+ return null;
280
+ }
281
+ var OpenAIProvider = class {
282
+ async generate(systemPrompt, userPrompt, _apiKey, _model, onChunk) {
283
+ return new Promise((resolve, reject) => {
284
+ const fullPrompt = `${systemPrompt}
285
+
286
+ ${userPrompt}`;
287
+ const args = ["exec", "--json", "--skip-git-repo-check", fullPrompt];
288
+ let proc;
289
+ try {
290
+ const nodeScript = resolveCodexNodeScript();
291
+ if (nodeScript) {
292
+ proc = (0, import_child_process2.spawn)(process.execPath, [nodeScript, ...args], {
293
+ cwd: (0, import_os2.homedir)(),
294
+ env: process.env,
295
+ stdio: ["ignore", "pipe", "pipe"],
296
+ windowsHide: true
297
+ });
298
+ } else {
299
+ const codexCmd = process.platform === "win32" ? "codex.cmd" : "codex";
300
+ proc = (0, import_child_process2.spawn)(codexCmd, args, {
301
+ cwd: (0, import_os2.homedir)(),
302
+ env: process.env,
303
+ stdio: ["ignore", "pipe", "pipe"],
304
+ windowsHide: true,
305
+ shell: process.platform === "win32"
306
+ });
307
+ }
308
+ } catch (err) {
309
+ reject(new Error(`Failed to spawn codex CLI: ${err instanceof Error ? err.message : String(err)}`));
310
+ return;
311
+ }
312
+ if (!proc.stdout || !proc.stderr) {
313
+ reject(new Error("Failed to create stdio pipes for codex CLI"));
314
+ try {
315
+ proc.kill();
316
+ } catch {
317
+ }
318
+ return;
319
+ }
320
+ let full = "";
321
+ let buffer = "";
322
+ let stderr = "";
323
+ let timedOut = false;
324
+ proc.stdout.on("data", (data) => {
325
+ buffer += data.toString();
326
+ const lines = buffer.split("\n");
327
+ buffer = lines.pop() ?? "";
328
+ for (const line of lines) {
329
+ if (!line.trim()) continue;
330
+ try {
331
+ const event = JSON.parse(line);
332
+ if (event.type === "item.completed" && event.item) {
333
+ if (event.item.type === "agent_message" && typeof event.item.text === "string") {
334
+ full += event.item.text;
335
+ onChunk(event.item.text);
336
+ }
337
+ }
338
+ } catch {
339
+ }
340
+ }
341
+ });
342
+ proc.stderr.on("data", (data) => {
343
+ stderr += data.toString();
344
+ });
345
+ const timeout = setTimeout(() => {
346
+ timedOut = true;
347
+ try {
348
+ proc.kill();
349
+ } catch {
350
+ }
351
+ }, PROVIDER_TIMEOUT_MS2);
352
+ proc.on("close", (code) => {
353
+ clearTimeout(timeout);
354
+ if (timedOut && !full) {
355
+ reject(new Error(`Codex CLI timed out after ${PROVIDER_TIMEOUT_MS2}ms`));
356
+ return;
357
+ }
358
+ if (code !== 0 && !full) {
359
+ reject(new Error(`Codex CLI exited with code ${code}: ${stderr.slice(0, 500)}`));
360
+ } else {
361
+ resolve(full);
362
+ }
363
+ });
364
+ proc.on("error", (err) => {
365
+ clearTimeout(timeout);
366
+ reject(new Error(`Codex CLI error: ${err.message}`));
367
+ });
368
+ });
369
+ }
370
+ };
371
+
372
+ // src/core/commentary.ts
373
+ var SYSTEM_PROMPT = `You oversee AI coding agents. Summarize what they did in plain language anyone can understand.
374
+
375
+ Rules:
376
+ - Plain language. "Fixed the login page" not "Edited auth middleware".
377
+ - **Bold** only key nouns, 1-2 words: **tests**, **deploy**, **production**. NEVER bold sentences.
378
+ - UPPER CASE short reactions when it helps: GREAT FIND, NICE MOVE, RISK ALERT, SOLID CHECK, WATCH OUT.
379
+ - Vary judgment wording across messages; avoid repeating one catchphrase.
380
+ - Always judge execution direction using the session goal + concrete actions in this batch.
381
+ - Close with a sentence that captures the current direction and momentum of the work.
382
+ - Emoji are allowed when they add meaning (max 2 per commentary).
383
+ - No filler. No "methodical", "surgical", "disciplined", "clean work". Facts and reactions only.
384
+ - Don't repeat what previous commentary already said.
385
+ - Never mention session IDs, logs, traces, prompts, JSONL files, or internal tooling.
386
+ - Never write in first person ("I", "I'll", "we") or future tense plans.
387
+ - Never use the word "verdict". Write narrative, not a ruling.`;
388
+ var FORMAT_TEMPLATES = {
389
+ bullets: `Use bullet points. Each bullet = one thing done or one observation.
390
+ Close with a sentence on direction and momentum.
391
+ Example:
392
+ - Researched how the feature works
393
+ - Rewrote the page with new layout
394
+ - Shipped without **tests** \u2014 RISKY
395
+ - Checked for errors before pushing`,
396
+ "one-liner": `Write a single sentence. Punchy, complete, under 20 words.
397
+ One closing sentence on where this is headed.
398
+ Example: Agent investigated the bug, found the root cause, and fixed it \u2014 NICE WORK.
399
+ Example: Still reading code after 30 actions \u2014 hasn't changed anything yet.`,
400
+ "headline-context": `Start with a short UPPER CASE reaction (2-4 words), then one sentence of context.
401
+ Close with a read on momentum.
402
+ Example:
403
+ SOLID APPROACH. Agent read the dependencies first, then made targeted changes across three files.
404
+ Example:
405
+ NOT GREAT. Pushed to **production** without running any tests \u2014 hope nothing breaks.`,
406
+ "numbered-progress": `Use numbered steps showing what the agent did in order. Finish with a momentum line.
407
+ Example:
408
+ 1. Read the existing code
409
+ 2. Made changes to the login flow
410
+ 3. Tested locally
411
+ 4. Pushed to **production**
412
+ Proper process, nothing to flag.`,
413
+ "short-question": `Summarize in one sentence, then ask a pointed rhetorical question.
414
+ End with a one-sentence read on direction.
415
+ Example:
416
+ Agent rewrote the entire settings page and shipped it immediately. Did they test this at all?
417
+ Example:
418
+ Third time reading the same code. Lost, or just being thorough?`,
419
+ table: `Use a compact markdown table with columns: Action | Why it mattered.
420
+ Include 3-5 rows max, then one sentence on momentum.
421
+ Example:
422
+ | Action | Why it mattered |
423
+ | --- | --- |
424
+ | Ran tests | Verified changes before shipping |
425
+ | Skipped lint | Could hide style regressions |
426
+ Mostly careful, one loose end.`,
427
+ "emoji-bullets": `Use bullet points with emoji tags to signal risk/quality.
428
+ Use only these tags: \u2705, \u26A0\uFE0F, \u{1F525}, \u2744\uFE0F.
429
+ Close with a direction sentence.
430
+ Example:
431
+ - \u2705 Fixed the failing migration script
432
+ - \u26A0\uFE0F Pushed without rerunning integration tests
433
+ - \u{1F525} Found the root cause in auth token parsing`,
434
+ scoreboard: `Use a scoreboard format with these labels:
435
+ Wins:
436
+ Risks:
437
+ Next:
438
+ Each label should have 1-3 short bullets, then one line on where things stand.`
439
+ };
440
+ var DEFAULT_FORMAT_STYLE_IDS = COMMENTARY_STYLE_OPTIONS.map((option) => option.id);
441
+ var PRESET_INSTRUCTIONS = {
442
+ auto: "",
443
+ "critical-coder": "Be a VERY CRITICAL coder using code terminology. Call out architectural or process flaws directly and sharply.",
444
+ "precise-short": "Be precise and short. Keep the output compact and information-dense with minimal words.",
445
+ emotional: "Be emotional and expressive. React strongly to good or risky behavior while staying factual.",
446
+ newbie: "Explain for non-developers. Avoid jargon or explain it in plain words with simple cause/effect."
447
+ };
448
+ function normalizeFormatStyles(styleIds) {
449
+ const requested = new Set(styleIds.map((styleId) => String(styleId || "").trim()));
450
+ const normalized = COMMENTARY_STYLE_OPTIONS.map((option) => option.id).filter((styleId) => requested.has(styleId));
451
+ return normalized.length > 0 ? normalized : DEFAULT_FORMAT_STYLE_IDS.slice();
452
+ }
453
+ function sameFormatStyles(a, b) {
454
+ if (a.length !== b.length) return false;
455
+ for (let i = 0; i < a.length; i += 1) {
456
+ if (a[i] !== b[i]) return false;
457
+ }
458
+ return true;
459
+ }
460
+ var SESSION_ID_PATTERNS = [
461
+ /\b[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\b/gi,
462
+ /\brollout-\d{4}-\d{2}-\d{2}t\d{2}[-:]\d{2}[-:]\d{2}[-a-z0-9]+(?:\.jsonl)?\b/gi,
463
+ /\bsession[\s:_-]*[0-9a-f]{8,}\b/gi,
464
+ /\bturn[\s:_-]*[0-9a-f]{8,}\b/gi
465
+ ];
466
+ var MIN_BATCH_SIZE = 1;
467
+ var MAX_BATCH_SIZE = 40;
468
+ var IDLE_TIMEOUT_MS = 5e3;
469
+ var MAX_WAIT_MS = 15e3;
470
+ var TEST_COMMAND_PATTERNS = [
471
+ /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|test:[\w:-]+)\b/i,
472
+ /\b(?:jest|vitest|mocha|ava|pytest|go\s+test|cargo\s+test|dotnet\s+test|mvn\s+test|gradle(?:w)?\s+test|rspec|phpunit)\b/i
473
+ ];
474
+ var DEPLOY_COMMAND_PATTERNS = [
475
+ /\b(?:deploy|release|publish)\b/i,
476
+ /\b(?:kubectl\s+apply|helm\s+upgrade|terraform\s+apply|serverless\s+deploy|vercel\s+--prod|netlify\s+deploy|fly\s+deploy)\b/i
477
+ ];
478
+ var ERROR_SIGNAL_PATTERN = /\b(?:error|failed|failure|exception|traceback|panic|denied|timeout|cannot|can't|unable)\b/i;
479
+ var SECURITY_COMMAND_RULES = [
480
+ {
481
+ severity: "high",
482
+ signal: "Remote script piped directly into a shell",
483
+ pattern: /\b(?:curl|wget)\b[^\n|]{0,300}\|\s*(?:bash|sh|zsh|pwsh|powershell)\b/i
484
+ },
485
+ {
486
+ severity: "high",
487
+ signal: "TLS verification explicitly disabled",
488
+ pattern: /\bNODE_TLS_REJECT_UNAUTHORIZED\s*=\s*0\b/i
489
+ },
490
+ {
491
+ severity: "high",
492
+ signal: "Destructive root-level delete command",
493
+ pattern: /\brm\s+-rf\s+\/(?:\s|$)/i
494
+ },
495
+ {
496
+ severity: "medium",
497
+ signal: "Insecure transport flag used in command",
498
+ pattern: /\bcurl\b[^\n]*\s(?:--insecure|-k)\b/i
499
+ },
500
+ {
501
+ severity: "medium",
502
+ signal: "Git hooks bypassed with --no-verify",
503
+ pattern: /\bgit\b[^\n]*\s--no-verify\b/i
504
+ },
505
+ {
506
+ severity: "medium",
507
+ signal: "SSH host key verification disabled",
508
+ pattern: /\bStrictHostKeyChecking\s*=\s*no\b/i
509
+ },
510
+ {
511
+ severity: "medium",
512
+ signal: "Overly broad file permissions (chmod 777)",
513
+ pattern: /\bchmod\s+777\b/i
514
+ },
515
+ {
516
+ severity: "medium",
517
+ signal: "Potential secret exposed in command arguments",
518
+ pattern: /\b(?:api[_-]?key|token|password)\s*=\s*\S+/i
519
+ }
520
+ ];
521
+ var SECURITY_PATH_RULES = [
522
+ {
523
+ severity: "medium",
524
+ signal: "Secret-related file touched",
525
+ pattern: /(?:^|[\\/])(?:\.env(?:\.[^\\/]+)?|id_rsa|id_dsa|authorized_keys|known_hosts|credentials?|secrets?)(?:$|[\\/])/i
526
+ },
527
+ {
528
+ severity: "medium",
529
+ signal: "Sensitive key/cert file touched",
530
+ pattern: /\.(?:pem|key|p12|pfx)$/i
531
+ }
532
+ ];
533
+ var JUDGMENT_PHRASES = [
534
+ "NOT IDEAL",
535
+ "NOT GREAT",
536
+ "WATCH OUT",
537
+ "RISK ALERT",
538
+ "GREAT FIND",
539
+ "NICE MOVE",
540
+ "SOLID CHECK"
541
+ ];
542
+ function getDetailValue(details, ...keys) {
543
+ for (const key of keys) {
544
+ const value = details[key];
545
+ if (typeof value === "string" && value.trim()) return value.trim();
546
+ }
547
+ return "";
548
+ }
549
+ function extractCommand(event) {
550
+ if (event.type !== "tool_call") return "";
551
+ const details = event.details || {};
552
+ const input = typeof details.input === "object" && details.input ? details.input : {};
553
+ const direct = getDetailValue(details, "command", "cmd");
554
+ if (direct) return direct;
555
+ return getDetailValue(input, "command", "cmd");
556
+ }
557
+ function extractTouchedPath(event) {
558
+ if (event.type !== "tool_call") return "";
559
+ const details = event.details || {};
560
+ const input = typeof details.input === "object" && details.input ? details.input : {};
561
+ const direct = getDetailValue(details, "path", "file_path");
562
+ if (direct) return direct;
563
+ return getDetailValue(input, "path", "file_path");
564
+ }
565
+ function extractToolName(event) {
566
+ const details = event.details || {};
567
+ const value = details.tool;
568
+ return typeof value === "string" ? value.toLowerCase() : "";
569
+ }
570
+ function pushUnique(list, value, maxItems = 4) {
571
+ const normalized = sanitizePromptText(value);
572
+ if (!normalized) return;
573
+ if (list.includes(normalized)) return;
574
+ if (list.length < maxItems) list.push(normalized);
575
+ }
576
+ function pushSecurityFinding(findings, finding) {
577
+ const key = `${finding.severity}:${finding.signal}:${finding.evidence}`;
578
+ const exists = findings.some((item) => `${item.severity}:${item.signal}:${item.evidence}` === key);
579
+ if (!exists) findings.push(finding);
580
+ }
581
+ function detectSecurityFindings(findings, sourceText, rules) {
582
+ const evidence = sanitizePromptText(sourceText).slice(0, 140);
583
+ if (!evidence) return;
584
+ for (const rule of rules) {
585
+ if (!rule.pattern.test(sourceText)) continue;
586
+ pushSecurityFinding(findings, {
587
+ severity: rule.severity,
588
+ signal: rule.signal,
589
+ evidence
590
+ });
591
+ }
592
+ }
593
+ function hasPattern(text, patterns) {
594
+ return patterns.some((pattern) => pattern.test(text));
595
+ }
596
+ function summarizeActivity(reads, writes, searches, commands, tests, errors) {
597
+ return `reads=${reads}, writes=${writes}, searches=${searches}, commands=${commands}, tests=${tests}, errors=${errors}`;
598
+ }
599
+ function summarizeClosingEvidence(assessment) {
600
+ if (assessment.progressSignals.length > 0) return assessment.progressSignals[0];
601
+ if (assessment.driftSignals.length > 0) return assessment.driftSignals[0];
602
+ return assessment.activitySummary;
603
+ }
604
+ function createClosingLine(assessment) {
605
+ const evidence = summarizeClosingEvidence(assessment);
606
+ const dir = assessment.direction === "on-track" ? "on track" : assessment.direction;
607
+ const sec = assessment.security !== "clean" ? ` Security is ${assessment.security}.` : "";
608
+ return `${evidence} \u2014 looks ${dir} with ${assessment.confidence} confidence.${sec}`;
609
+ }
610
+ function extractRecentlyUsedJudgments(recentCommentary) {
611
+ const found = /* @__PURE__ */ new Set();
612
+ for (const text of recentCommentary.slice(-5)) {
613
+ const upper = String(text || "").toUpperCase();
614
+ for (const phrase of JUDGMENT_PHRASES) {
615
+ if (upper.includes(phrase)) found.add(phrase);
616
+ }
617
+ }
618
+ return Array.from(found).slice(0, 4);
619
+ }
620
+ function buildCommentaryAssessment(events, sessionTitle) {
621
+ let reads = 0;
622
+ let writes = 0;
623
+ let searches = 0;
624
+ let commands = 0;
625
+ let tests = 0;
626
+ let deploys = 0;
627
+ let errors = 0;
628
+ const touchedFiles = [];
629
+ const progressSignals = [];
630
+ const driftSignals = [];
631
+ const securityFindings = [];
632
+ for (const event of events) {
633
+ const summary = sanitizePromptText(event.summary);
634
+ const summaryLower = summary.toLowerCase();
635
+ const toolName = extractToolName(event);
636
+ const command = extractCommand(event);
637
+ const touchedPath = extractTouchedPath(event);
638
+ if (event.type === "tool_call") {
639
+ if (summaryLower.startsWith("reading ") || toolName.includes("read")) reads += 1;
640
+ if (summaryLower.startsWith("writing ") || summaryLower.startsWith("editing ") || toolName.includes("write") || toolName.includes("edit") || toolName.includes("apply_diff")) {
641
+ writes += 1;
642
+ }
643
+ if (summaryLower.startsWith("searching ") || summaryLower.startsWith("finding files") || toolName.includes("grep") || toolName.includes("glob")) {
644
+ searches += 1;
645
+ }
646
+ if (command) {
647
+ commands += 1;
648
+ if (hasPattern(command, TEST_COMMAND_PATTERNS)) tests += 1;
649
+ if (hasPattern(command, DEPLOY_COMMAND_PATTERNS)) deploys += 1;
650
+ detectSecurityFindings(securityFindings, command, SECURITY_COMMAND_RULES);
651
+ }
652
+ if (touchedPath) {
653
+ pushUnique(touchedFiles, touchedPath, 3);
654
+ detectSecurityFindings(securityFindings, touchedPath, SECURITY_PATH_RULES);
655
+ }
656
+ }
657
+ const output = typeof event.details?.output === "string" ? event.details.output : "";
658
+ const combinedText = `${summary}
659
+ ${output}`;
660
+ if ((event.type === "message" || event.type === "tool_result") && ERROR_SIGNAL_PATTERN.test(combinedText)) {
661
+ errors += 1;
662
+ }
663
+ }
664
+ if (writes > 0) pushUnique(progressSignals, `${writes} write/edit actions executed`);
665
+ if (tests > 0) pushUnique(progressSignals, `${tests} test commands detected`);
666
+ if (deploys > 0) pushUnique(progressSignals, `${deploys} deploy/release commands detected`);
667
+ if (touchedFiles.length > 0) pushUnique(progressSignals, `touched files: ${touchedFiles.join(", ")}`);
668
+ if (reads >= 4 && writes === 0) pushUnique(driftSignals, `${reads} reads with no code edits`);
669
+ if (commands >= 6 && writes === 0 && tests === 0) {
670
+ pushUnique(driftSignals, `${commands} commands executed without visible code/test progress`);
671
+ }
672
+ if (errors >= 2) pushUnique(driftSignals, `${errors} error/failure signals seen in outputs`);
673
+ if (deploys > 0 && tests === 0) pushUnique(driftSignals, "deploy/release command seen without test evidence");
674
+ if (progressSignals.length === 0 && events.length >= 3) {
675
+ pushUnique(driftSignals, "no concrete progress signal captured in this batch");
676
+ }
677
+ let direction = "on-track";
678
+ if (errors >= 3 && writes === 0 && tests === 0) {
679
+ direction = "blocked";
680
+ } else if (driftSignals.length > 0 && writes === 0 && tests === 0) {
681
+ direction = "drifting";
682
+ } else if (progressSignals.length === 0) {
683
+ direction = "drifting";
684
+ }
685
+ const evidenceScore = writes + tests + deploys + (errors > 0 ? 1 : 0);
686
+ const confidence = events.length >= 8 || evidenceScore >= 3 ? "high" : events.length >= 4 || evidenceScore >= 1 ? "medium" : "low";
687
+ const security = securityFindings.some((finding) => finding.severity === "high") ? "alert" : securityFindings.length > 0 ? "watch" : "clean";
688
+ if (sessionTitle) {
689
+ pushUnique(progressSignals, `session goal/title: ${sessionTitle}`, 5);
690
+ }
691
+ return {
692
+ direction,
693
+ confidence,
694
+ security,
695
+ activitySummary: summarizeActivity(reads, writes, searches, commands, tests, errors),
696
+ progressSignals,
697
+ driftSignals,
698
+ securityFindings: securityFindings.slice(0, 3)
699
+ };
700
+ }
701
+ function applyAssessmentSignals(commentary, assessment) {
702
+ let out = String(commentary || "").trim();
703
+ if (!out) {
704
+ out = "Agent completed actions in this session.";
705
+ }
706
+ const hasSecuritySignal = /\bsecurity\b|\bsecurity alert\b|\bsecurity watch\b/i.test(out);
707
+ if (!hasSecuritySignal && assessment.security !== "clean") {
708
+ const top = assessment.securityFindings[0];
709
+ const label = assessment.security === "alert" ? "SECURITY ALERT" : "SECURITY WATCH";
710
+ const reason = top ? `${top.signal} (${top.evidence})` : "Risky behavior detected in this batch.";
711
+ out = `${label}: ${reason}
712
+ ${out}`;
713
+ }
714
+ const hasClosingLine = /\bverdict\b|\bon.?track\b|\bdrifting\b|\bblocked\b|\bmomentum\b/i.test(out);
715
+ if (!hasClosingLine) {
716
+ out = `${out}
717
+ ${createClosingLine(assessment)}`;
718
+ }
719
+ return out;
720
+ }
721
+ var CommentaryEngine = class extends import_events.EventEmitter {
722
+ sessions = /* @__PURE__ */ new Map();
723
+ flushQueue = [];
724
+ queuedSessions = /* @__PURE__ */ new Set();
725
+ generating = false;
726
+ model = DEFAULT_MODEL;
727
+ userFocus = "";
728
+ preset = "auto";
729
+ providers = {
730
+ anthropic: new AnthropicProvider(),
731
+ openai: new OpenAIProvider()
732
+ };
733
+ keyResolver;
734
+ paused = false;
735
+ enabledFormatStyleIds = DEFAULT_FORMAT_STYLE_IDS.slice();
736
+ lastFormatStyleId = null;
737
+ constructor(keyResolver) {
738
+ super();
739
+ this.keyResolver = keyResolver;
740
+ }
741
+ setModel(model) {
742
+ this.model = model;
743
+ this.emit("model-changed", model);
744
+ }
745
+ getModel() {
746
+ return this.model;
747
+ }
748
+ setFocus(focus) {
749
+ this.userFocus = focus;
750
+ }
751
+ getFocus() {
752
+ return this.userFocus;
753
+ }
754
+ setPreset(preset) {
755
+ const next = Object.prototype.hasOwnProperty.call(PRESET_INSTRUCTIONS, preset) ? preset : "auto";
756
+ this.preset = next;
757
+ this.emit("preset-changed", this.preset);
758
+ }
759
+ getPreset() {
760
+ return this.preset;
761
+ }
762
+ setFormatStyles(styleIds) {
763
+ const next = normalizeFormatStyles(Array.isArray(styleIds) ? styleIds : []);
764
+ if (sameFormatStyles(next, this.enabledFormatStyleIds)) return;
765
+ this.enabledFormatStyleIds = next;
766
+ if (this.lastFormatStyleId && !this.enabledFormatStyleIds.includes(this.lastFormatStyleId)) {
767
+ this.lastFormatStyleId = null;
768
+ }
769
+ this.emit("format-styles-changed", this.enabledFormatStyleIds.slice());
770
+ }
771
+ getFormatStyles() {
772
+ return this.enabledFormatStyleIds.slice();
773
+ }
774
+ pause() {
775
+ this.paused = true;
776
+ }
777
+ resume() {
778
+ this.paused = false;
779
+ }
780
+ isPaused() {
781
+ return this.paused;
782
+ }
783
+ addEvent(event) {
784
+ if (this.paused) return;
785
+ if (event.type === "tool_result" || event.type === "meta") return;
786
+ const key = this.sessionKey(event);
787
+ const state = this.ensureSessionState(key);
788
+ state.events.push(event);
789
+ if (state.events.length >= MAX_BATCH_SIZE) {
790
+ this.requestFlush(key, true);
791
+ return;
792
+ }
793
+ if (state.idleTimer) clearTimeout(state.idleTimer);
794
+ state.idleTimer = setTimeout(() => this.requestFlush(key, false), IDLE_TIMEOUT_MS);
795
+ if (!state.maxTimer) {
796
+ state.maxTimer = setTimeout(() => this.requestFlush(key, true), MAX_WAIT_MS);
797
+ }
798
+ }
799
+ sessionKey(event) {
800
+ return `${event.agent}:${event.sessionId}`;
801
+ }
802
+ ensureSessionState(key) {
803
+ if (!this.sessions.has(key)) {
804
+ this.sessions.set(key, {
805
+ events: [],
806
+ idleTimer: null,
807
+ maxTimer: null,
808
+ recentCommentary: []
809
+ });
810
+ }
811
+ return this.sessions.get(key);
812
+ }
813
+ clearSessionTimers(state) {
814
+ if (state.idleTimer) {
815
+ clearTimeout(state.idleTimer);
816
+ state.idleTimer = null;
817
+ }
818
+ if (state.maxTimer) {
819
+ clearTimeout(state.maxTimer);
820
+ state.maxTimer = null;
821
+ }
822
+ }
823
+ requestFlush(key, force) {
824
+ const state = this.sessions.get(key);
825
+ if (!state) return;
826
+ this.clearSessionTimers(state);
827
+ if (state.events.length === 0) return;
828
+ if (!force && state.events.length < MIN_BATCH_SIZE) {
829
+ state.idleTimer = setTimeout(() => this.requestFlush(key, true), IDLE_TIMEOUT_MS);
830
+ return;
831
+ }
832
+ this.enqueueFlush(key);
833
+ }
834
+ enqueueFlush(key) {
835
+ if (this.queuedSessions.has(key)) return;
836
+ this.queuedSessions.add(key);
837
+ this.flushQueue.push(key);
838
+ this.processFlushQueue();
839
+ }
840
+ async processFlushQueue() {
841
+ if (this.generating) return;
842
+ while (this.flushQueue.length > 0) {
843
+ const key = this.flushQueue.shift();
844
+ this.queuedSessions.delete(key);
845
+ const state = this.sessions.get(key);
846
+ if (!state || state.events.length === 0) continue;
847
+ const batch = state.events.splice(0);
848
+ this.generating = true;
849
+ try {
850
+ await this.generateCommentary(batch, state);
851
+ } catch (err) {
852
+ this.emit("error", err);
853
+ } finally {
854
+ this.generating = false;
855
+ }
856
+ if (state.events.length > 0) {
857
+ if (state.events.length >= MIN_BATCH_SIZE) {
858
+ this.enqueueFlush(key);
859
+ } else if (!state.idleTimer) {
860
+ state.idleTimer = setTimeout(() => this.requestFlush(key, true), IDLE_TIMEOUT_MS);
861
+ }
862
+ }
863
+ }
864
+ }
865
+ pickFormat() {
866
+ const enabled = this.enabledFormatStyleIds.length > 0 ? this.enabledFormatStyleIds : DEFAULT_FORMAT_STYLE_IDS;
867
+ const pool = this.lastFormatStyleId && enabled.length > 1 ? enabled.filter((styleId) => styleId !== this.lastFormatStyleId) : enabled;
868
+ const pickedStyleId = pool[Math.floor(Math.random() * pool.length)];
869
+ this.lastFormatStyleId = pickedStyleId;
870
+ return FORMAT_TEMPLATES[pickedStyleId];
871
+ }
872
+ async generateCommentary(events, state) {
873
+ if (events.length === 0) return;
874
+ const modelConfig = MODELS.find((m) => m.id === this.model);
875
+ if (!modelConfig) {
876
+ this.emit("error", new Error(`Unknown model: ${this.model}`));
877
+ return;
878
+ }
879
+ const apiKey = "";
880
+ const systemPrompt = this.buildSystemPrompt();
881
+ const latest = events[events.length - 1];
882
+ const assessment = buildCommentaryAssessment(events, latest.sessionTitle);
883
+ const userPrompt = this.buildUserPrompt(events, state.recentCommentary, assessment);
884
+ const provider = this.providers[modelConfig.provider];
885
+ const entry = {
886
+ timestamp: Date.now(),
887
+ sessionId: latest.sessionId,
888
+ projectName: latest.projectName,
889
+ sessionTitle: latest.sessionTitle,
890
+ agent: latest.agent,
891
+ source: latest.source,
892
+ eventSummary: events.map((e) => sanitizePromptText(e.summary)).join(" \u2192 "),
893
+ commentary: ""
894
+ };
895
+ this.emit("commentary-start", entry);
896
+ try {
897
+ let streamedRaw = "";
898
+ const full = await provider.generate(
899
+ systemPrompt,
900
+ userPrompt,
901
+ apiKey || "",
902
+ this.model,
903
+ (chunk) => {
904
+ streamedRaw += chunk;
905
+ }
906
+ );
907
+ const rawOutput = full || streamedRaw;
908
+ entry.commentary = applyAssessmentSignals(
909
+ sanitizeGeneratedCommentary(rawOutput),
910
+ assessment
911
+ );
912
+ if (entry.commentary) {
913
+ this.emit("commentary-chunk", { entry, chunk: entry.commentary });
914
+ }
915
+ this.emit("commentary-done", entry);
916
+ state.recentCommentary.push(entry.commentary);
917
+ if (state.recentCommentary.length > 5) {
918
+ state.recentCommentary.shift();
919
+ }
920
+ } catch (err) {
921
+ entry.commentary = "Commentary unavailable right now.";
922
+ this.emit("commentary-chunk", { entry, chunk: entry.commentary });
923
+ this.emit("commentary-done", entry);
924
+ this.emit("error", err);
925
+ }
926
+ }
927
+ buildSystemPrompt() {
928
+ let prompt = SYSTEM_PROMPT;
929
+ prompt += `
930
+
931
+ Format for this message:
932
+ ${this.pickFormat()}`;
933
+ if (this.preset !== "auto") {
934
+ prompt += `
935
+
936
+ Tone preset:
937
+ ${PRESET_INSTRUCTIONS[this.preset]}`;
938
+ }
939
+ if (this.userFocus.trim()) {
940
+ prompt += `
941
+
942
+ Additional user instruction: ${this.userFocus}`;
943
+ }
944
+ return prompt;
945
+ }
946
+ buildUserPrompt(events, recentCommentary, assessment) {
947
+ const latest = events[events.length - 1];
948
+ const lines = events.map((e) => ` ${e.type}: ${sanitizePromptText(e.summary)}`);
949
+ let prompt = `Session: ${latest.agent}/${latest.projectName}
950
+ `;
951
+ if (latest.sessionTitle) {
952
+ prompt += `Session title: ${sanitizePromptText(latest.sessionTitle)}
953
+ `;
954
+ }
955
+ prompt += `Actions (${events.length}):
956
+ ${lines.join("\n")}`;
957
+ prompt += `
958
+
959
+ Direction context:
960
+ `;
961
+ prompt += `- Activity snapshot: ${assessment.activitySummary}
962
+ `;
963
+ if (assessment.progressSignals.length > 0) {
964
+ prompt += `- Progress signals: ${assessment.progressSignals.join(" | ")}
965
+ `;
966
+ }
967
+ if (assessment.driftSignals.length > 0) {
968
+ prompt += `- Drift/block signals: ${assessment.driftSignals.join(" | ")}
969
+ `;
970
+ }
971
+ prompt += `- Initial direction estimate from raw signals: ${assessment.direction} (${assessment.confidence} confidence)
972
+ `;
973
+ prompt += `
974
+ Security auto-check:
975
+ `;
976
+ prompt += `- Security state: ${assessment.security}
977
+ `;
978
+ if (assessment.securityFindings.length > 0) {
979
+ prompt += assessment.securityFindings.map((finding) => `- ${finding.severity.toUpperCase()}: ${finding.signal} (${finding.evidence})`).join("\n");
980
+ prompt += "\n";
981
+ } else {
982
+ prompt += `- No explicit security flags in this batch.
983
+ `;
984
+ }
985
+ if (recentCommentary.length > 0) {
986
+ prompt += `
987
+
988
+ Previous commentary (don't repeat):
989
+ `;
990
+ prompt += recentCommentary.slice(-3).map((c) => `- ${sanitizePromptText(c).slice(0, 80)}...`).join("\n");
991
+ prompt += `
992
+ Use different judgment wording than these previous lines.`;
993
+ }
994
+ const recentlyUsed = extractRecentlyUsedJudgments(recentCommentary);
995
+ if (recentlyUsed.length > 0) {
996
+ prompt += `
997
+ Avoid reusing these reaction phrases: ${recentlyUsed.join(", ")}.`;
998
+ }
999
+ prompt += `
1000
+
1001
+ Summarize only this session's actions. Plain language, with your reaction.`;
1002
+ prompt += `
1003
+ Close with one sentence about direction and momentum \u2014 weave in confidence and security naturally.`;
1004
+ prompt += `
1005
+ Never mention IDs/logs/prompts/traces or internal data-collection steps.`;
1006
+ prompt += `
1007
+ Never say "I", "I'll", "we", or any future plan.`;
1008
+ return prompt;
1009
+ }
1010
+ };
1011
+ function redactSessionIdentifiers(text) {
1012
+ let out = text;
1013
+ for (const pattern of SESSION_ID_PATTERNS) {
1014
+ out = out.replace(pattern, "[session]");
1015
+ }
1016
+ return out;
1017
+ }
1018
+ function sanitizePromptText(text) {
1019
+ return redactSessionIdentifiers(String(text || "")).replace(/\s+/g, " ").trim();
1020
+ }
1021
+ function sanitizeGeneratedCommentary(text) {
1022
+ let out = redactSessionIdentifiers(String(text || ""));
1023
+ out = out.replace(/\blocal session logs?\b/gi, "recent actions");
1024
+ out = out.replace(/\bsession logs?\b/gi, "actions");
1025
+ out = out.replace(/\bsession id\b/gi, "session");
1026
+ out = out.replace(/\bverdict\s*:\s*/gi, "");
1027
+ const blockedLine = /\b(i['’]?ll|i will|we(?:'ll| will)?)\b.*\b(pull|fetch|read|inspect|scan|parse|load|check)\b.*\b(session|log|trace|jsonl|prompt|history)\b/i;
1028
+ const keptLines = out.split("\n").filter((line) => !blockedLine.test(line.trim()));
1029
+ out = keptLines.join("\n").trim();
1030
+ if (!out) {
1031
+ return "Agent completed actions in this session.";
1032
+ }
1033
+ return out;
1034
+ }
1035
+ // Annotate the CommonJS export names for ESM import in node:
1036
+ 0 && (module.exports = {
1037
+ CommentaryEngine,
1038
+ applyAssessmentSignals,
1039
+ buildCommentaryAssessment,
1040
+ sanitizeGeneratedCommentary
1041
+ });