@kibitzsh/kibitz 0.0.3 → 0.0.4

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.
@@ -1,376 +1,14 @@
1
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.
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommentaryEngine = void 0;
4
+ exports.buildCommentaryAssessment = buildCommentaryAssessment;
5
+ exports.applyAssessmentSignals = applyAssessmentSignals;
6
+ exports.sanitizeGeneratedCommentary = sanitizeGeneratedCommentary;
7
+ const events_1 = require("events");
8
+ const types_1 = require("./types");
9
+ const anthropic_1 = require("./providers/anthropic");
10
+ const openai_1 = require("./providers/openai");
11
+ const SYSTEM_PROMPT = `You oversee AI coding agents. Summarize what they did in plain language anyone can understand.
374
12
 
375
13
  Rules:
376
14
  - Plain language. "Fixed the login page" not "Edited auth middleware".
@@ -385,38 +23,39 @@ Rules:
385
23
  - Never mention session IDs, logs, traces, prompts, JSONL files, or internal tooling.
386
24
  - Never write in first person ("I", "I'll", "we") or future tense plans.
387
25
  - 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.
26
+ // Format templates — one is selected per commentary from the enabled style set.
27
+ const FORMAT_TEMPLATES = {
28
+ bullets: `Use bullet points. Each bullet = one thing done or one observation.
390
29
  Close with a sentence on direction and momentum.
391
30
  Example:
392
31
  - Researched how the feature works
393
32
  - Rewrote the page with new layout
394
- - Shipped without **tests** \u2014 RISKY
33
+ - Shipped without **tests** RISKY
395
34
  - Checked for errors before pushing`,
396
- "one-liner": `Write a single sentence. Punchy, complete, under 20 words.
35
+ 'one-liner': `Write a single sentence. Punchy, complete, under 20 words.
397
36
  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.
37
+ Example: Agent investigated the bug, found the root cause, and fixed it NICE WORK.
38
+ Example: Still reading code after 30 actions hasn't changed anything yet.`,
39
+ 'headline-context': `Start with a short UPPER CASE reaction (2-4 words), then one sentence of context.
401
40
  Close with a read on momentum.
402
41
  Example:
403
42
  SOLID APPROACH. Agent read the dependencies first, then made targeted changes across three files.
404
43
  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.
44
+ NOT GREAT. Pushed to **production** without running any tests hope nothing breaks.`,
45
+ 'numbered-progress': `Use numbered steps showing what the agent did in order. Finish with a momentum line.
407
46
  Example:
408
47
  1. Read the existing code
409
48
  2. Made changes to the login flow
410
49
  3. Tested locally
411
50
  4. Pushed to **production**
412
51
  Proper process, nothing to flag.`,
413
- "short-question": `Summarize in one sentence, then ask a pointed rhetorical question.
52
+ 'short-question': `Summarize in one sentence, then ask a pointed rhetorical question.
414
53
  End with a one-sentence read on direction.
415
54
  Example:
416
55
  Agent rewrote the entire settings page and shipped it immediately. Did they test this at all?
417
56
  Example:
418
57
  Third time reading the same code. Lost, or just being thorough?`,
419
- table: `Use a compact markdown table with columns: Action | Why it mattered.
58
+ table: `Use a compact markdown table with columns: Action | Why it mattered.
420
59
  Include 3-5 rows max, then one sentence on momentum.
421
60
  Example:
422
61
  | Action | Why it mattered |
@@ -424,618 +63,657 @@ Example:
424
63
  | Ran tests | Verified changes before shipping |
425
64
  | Skipped lint | Could hide style regressions |
426
65
  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.
66
+ 'emoji-bullets': `Use bullet points with emoji tags to signal risk/quality.
67
+ Use only these tags: ✅, ⚠️, 🔥, ❄️.
429
68
  Close with a direction sentence.
430
69
  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:
70
+ - Fixed the failing migration script
71
+ - ⚠️ Pushed without rerunning integration tests
72
+ - 🔥 Found the root cause in auth token parsing`,
73
+ scoreboard: `Use a scoreboard format with these labels:
435
74
  Wins:
436
75
  Risks:
437
76
  Next:
438
- Each label should have 1-3 short bullets, then one line on where things stand.`
77
+ Each label should have 1-3 short bullets, then one line on where things stand.`,
439
78
  };
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."
79
+ const DEFAULT_FORMAT_STYLE_IDS = types_1.COMMENTARY_STYLE_OPTIONS.map((option) => option.id);
80
+ const PRESET_INSTRUCTIONS = {
81
+ auto: '',
82
+ 'critical-coder': 'Be a VERY CRITICAL coder using code terminology. Call out architectural or process flaws directly and sharply.',
83
+ 'precise-short': 'Be precise and short. Keep the output compact and information-dense with minimal words.',
84
+ emotional: 'Be emotional and expressive. React strongly to good or risky behavior while staying factual.',
85
+ newbie: 'Explain for non-developers. Avoid jargon or explain it in plain words with simple cause/effect.',
447
86
  };
448
87
  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();
88
+ const requested = new Set(styleIds.map((styleId) => String(styleId || '').trim()));
89
+ const normalized = types_1.COMMENTARY_STYLE_OPTIONS
90
+ .map((option) => option.id)
91
+ .filter((styleId) => requested.has(styleId));
92
+ return normalized.length > 0 ? normalized : DEFAULT_FORMAT_STYLE_IDS.slice();
452
93
  }
453
94
  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;
95
+ if (a.length !== b.length)
96
+ return false;
97
+ for (let i = 0; i < a.length; i += 1) {
98
+ if (a[i] !== b[i])
99
+ return false;
100
+ }
101
+ return true;
459
102
  }
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
103
+ const SESSION_ID_PATTERNS = [
104
+ /\b[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\b/gi,
105
+ /\brollout-\d{4}-\d{2}-\d{2}t\d{2}[-:]\d{2}[-:]\d{2}[-a-z0-9]+(?:\.jsonl)?\b/gi,
106
+ /\bsession[\s:_-]*[0-9a-f]{8,}\b/gi,
107
+ /\bturn[\s:_-]*[0-9a-f]{8,}\b/gi,
465
108
  ];
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
109
+ // Adaptive batching
110
+ const MIN_BATCH_SIZE = 1;
111
+ const MAX_BATCH_SIZE = 40;
112
+ const IDLE_TIMEOUT_MS = 5000;
113
+ const MAX_WAIT_MS = 15000;
114
+ const TEST_COMMAND_PATTERNS = [
115
+ /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|test:[\w:-]+)\b/i,
116
+ /\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
117
  ];
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
118
+ const DEPLOY_COMMAND_PATTERNS = [
119
+ /\b(?:deploy|release|publish)\b/i,
120
+ /\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
121
  ];
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
- }
122
+ const ERROR_SIGNAL_PATTERN = /\b(?:error|failed|failure|exception|traceback|panic|denied|timeout|cannot|can't|unable)\b/i;
123
+ const SECURITY_COMMAND_RULES = [
124
+ {
125
+ severity: 'high',
126
+ signal: 'Remote script piped directly into a shell',
127
+ pattern: /\b(?:curl|wget)\b[^\n|]{0,300}\|\s*(?:bash|sh|zsh|pwsh|powershell)\b/i,
128
+ },
129
+ {
130
+ severity: 'high',
131
+ signal: 'TLS verification explicitly disabled',
132
+ pattern: /\bNODE_TLS_REJECT_UNAUTHORIZED\s*=\s*0\b/i,
133
+ },
134
+ {
135
+ severity: 'high',
136
+ signal: 'Destructive root-level delete command',
137
+ pattern: /\brm\s+-rf\s+\/(?:\s|$)/i,
138
+ },
139
+ {
140
+ severity: 'medium',
141
+ signal: 'Insecure transport flag used in command',
142
+ pattern: /\bcurl\b[^\n]*\s(?:--insecure|-k)\b/i,
143
+ },
144
+ {
145
+ severity: 'medium',
146
+ signal: 'Git hooks bypassed with --no-verify',
147
+ pattern: /\bgit\b[^\n]*\s--no-verify\b/i,
148
+ },
149
+ {
150
+ severity: 'medium',
151
+ signal: 'SSH host key verification disabled',
152
+ pattern: /\bStrictHostKeyChecking\s*=\s*no\b/i,
153
+ },
154
+ {
155
+ severity: 'medium',
156
+ signal: 'Overly broad file permissions (chmod 777)',
157
+ pattern: /\bchmod\s+777\b/i,
158
+ },
159
+ {
160
+ severity: 'medium',
161
+ signal: 'Potential secret exposed in command arguments',
162
+ pattern: /\b(?:api[_-]?key|token|password)\s*=\s*\S+/i,
163
+ },
520
164
  ];
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
- }
165
+ const SECURITY_PATH_RULES = [
166
+ {
167
+ severity: 'medium',
168
+ signal: 'Secret-related file touched',
169
+ pattern: /(?:^|[\\/])(?:\.env(?:\.[^\\/]+)?|id_rsa|id_dsa|authorized_keys|known_hosts|credentials?|secrets?)(?:$|[\\/])/i,
170
+ },
171
+ {
172
+ severity: 'medium',
173
+ signal: 'Sensitive key/cert file touched',
174
+ pattern: /\.(?:pem|key|p12|pfx)$/i,
175
+ },
532
176
  ];
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"
177
+ const JUDGMENT_PHRASES = [
178
+ 'NOT IDEAL',
179
+ 'NOT GREAT',
180
+ 'WATCH OUT',
181
+ 'RISK ALERT',
182
+ 'GREAT FIND',
183
+ 'NICE MOVE',
184
+ 'SOLID CHECK',
541
185
  ];
542
186
  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 "";
187
+ for (const key of keys) {
188
+ const value = details[key];
189
+ if (typeof value === 'string' && value.trim())
190
+ return value.trim();
191
+ }
192
+ return '';
548
193
  }
549
194
  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");
195
+ if (event.type !== 'tool_call')
196
+ return '';
197
+ const details = event.details || {};
198
+ const input = typeof details.input === 'object' && details.input
199
+ ? details.input
200
+ : {};
201
+ const direct = getDetailValue(details, 'command', 'cmd');
202
+ if (direct)
203
+ return direct;
204
+ return getDetailValue(input, 'command', 'cmd');
556
205
  }
557
206
  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");
207
+ if (event.type !== 'tool_call')
208
+ return '';
209
+ const details = event.details || {};
210
+ const input = typeof details.input === 'object' && details.input
211
+ ? details.input
212
+ : {};
213
+ const direct = getDetailValue(details, 'path', 'file_path');
214
+ if (direct)
215
+ return direct;
216
+ return getDetailValue(input, 'path', 'file_path');
564
217
  }
565
218
  function extractToolName(event) {
566
- const details = event.details || {};
567
- const value = details.tool;
568
- return typeof value === "string" ? value.toLowerCase() : "";
219
+ const details = event.details || {};
220
+ const value = details.tool;
221
+ return typeof value === 'string' ? value.toLowerCase() : '';
569
222
  }
570
223
  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);
224
+ const normalized = sanitizePromptText(value);
225
+ if (!normalized)
226
+ return;
227
+ if (list.includes(normalized))
228
+ return;
229
+ if (list.length < maxItems)
230
+ list.push(normalized);
575
231
  }
576
232
  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);
233
+ const key = `${finding.severity}:${finding.signal}:${finding.evidence}`;
234
+ const exists = findings.some((item) => `${item.severity}:${item.signal}:${item.evidence}` === key);
235
+ if (!exists)
236
+ findings.push(finding);
580
237
  }
581
238
  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
- }
239
+ const evidence = sanitizePromptText(sourceText).slice(0, 140);
240
+ if (!evidence)
241
+ return;
242
+ for (const rule of rules) {
243
+ if (!rule.pattern.test(sourceText))
244
+ continue;
245
+ pushSecurityFinding(findings, {
246
+ severity: rule.severity,
247
+ signal: rule.signal,
248
+ evidence,
249
+ });
250
+ }
592
251
  }
593
252
  function hasPattern(text, patterns) {
594
- return patterns.some((pattern) => pattern.test(text));
253
+ return patterns.some((pattern) => pattern.test(text));
595
254
  }
596
255
  function summarizeActivity(reads, writes, searches, commands, tests, errors) {
597
- return `reads=${reads}, writes=${writes}, searches=${searches}, commands=${commands}, tests=${tests}, errors=${errors}`;
256
+ return `reads=${reads}, writes=${writes}, searches=${searches}, commands=${commands}, tests=${tests}, errors=${errors}`;
598
257
  }
599
258
  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;
259
+ if (assessment.progressSignals.length > 0)
260
+ return assessment.progressSignals[0];
261
+ if (assessment.driftSignals.length > 0)
262
+ return assessment.driftSignals[0];
263
+ return assessment.activitySummary;
603
264
  }
604
265
  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}`;
266
+ const evidence = summarizeClosingEvidence(assessment);
267
+ const dir = assessment.direction === 'on-track' ? 'on track' : assessment.direction;
268
+ const sec = assessment.security !== 'clean' ? ` Security is ${assessment.security}.` : '';
269
+ return `${evidence} looks ${dir} with ${assessment.confidence} confidence.${sec}`;
609
270
  }
610
271
  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);
272
+ const found = new Set();
273
+ for (const text of recentCommentary.slice(-5)) {
274
+ const upper = String(text || '').toUpperCase();
275
+ for (const phrase of JUDGMENT_PHRASES) {
276
+ if (upper.includes(phrase))
277
+ found.add(phrase);
278
+ }
616
279
  }
617
- }
618
- return Array.from(found).slice(0, 4);
280
+ return Array.from(found).slice(0, 4);
619
281
  }
620
282
  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
- }
283
+ let reads = 0;
284
+ let writes = 0;
285
+ let searches = 0;
286
+ let commands = 0;
287
+ let tests = 0;
288
+ let deploys = 0;
289
+ let errors = 0;
290
+ const touchedFiles = [];
291
+ const progressSignals = [];
292
+ const driftSignals = [];
293
+ const securityFindings = [];
294
+ for (const event of events) {
295
+ const summary = sanitizePromptText(event.summary);
296
+ const summaryLower = summary.toLowerCase();
297
+ const toolName = extractToolName(event);
298
+ const command = extractCommand(event);
299
+ const touchedPath = extractTouchedPath(event);
300
+ if (event.type === 'tool_call') {
301
+ if (summaryLower.startsWith('reading ') || toolName.includes('read'))
302
+ reads += 1;
303
+ if (summaryLower.startsWith('writing ')
304
+ || summaryLower.startsWith('editing ')
305
+ || toolName.includes('write')
306
+ || toolName.includes('edit')
307
+ || toolName.includes('apply_diff')) {
308
+ writes += 1;
309
+ }
310
+ if (summaryLower.startsWith('searching ')
311
+ || summaryLower.startsWith('finding files')
312
+ || toolName.includes('grep')
313
+ || toolName.includes('glob')) {
314
+ searches += 1;
315
+ }
316
+ if (command) {
317
+ commands += 1;
318
+ if (hasPattern(command, TEST_COMMAND_PATTERNS))
319
+ tests += 1;
320
+ if (hasPattern(command, DEPLOY_COMMAND_PATTERNS))
321
+ deploys += 1;
322
+ detectSecurityFindings(securityFindings, command, SECURITY_COMMAND_RULES);
323
+ }
324
+ if (touchedPath) {
325
+ pushUnique(touchedFiles, touchedPath, 3);
326
+ detectSecurityFindings(securityFindings, touchedPath, SECURITY_PATH_RULES);
327
+ }
328
+ }
329
+ const output = typeof event.details?.output === 'string' ? event.details.output : '';
330
+ const combinedText = `${summary}\n${output}`;
331
+ if ((event.type === 'message' || event.type === 'tool_result') && ERROR_SIGNAL_PATTERN.test(combinedText)) {
332
+ errors += 1;
333
+ }
334
+ }
335
+ if (writes > 0)
336
+ pushUnique(progressSignals, `${writes} write/edit actions executed`);
337
+ if (tests > 0)
338
+ pushUnique(progressSignals, `${tests} test commands detected`);
339
+ if (deploys > 0)
340
+ pushUnique(progressSignals, `${deploys} deploy/release commands detected`);
341
+ if (touchedFiles.length > 0)
342
+ pushUnique(progressSignals, `touched files: ${touchedFiles.join(', ')}`);
343
+ if (reads >= 4 && writes === 0)
344
+ pushUnique(driftSignals, `${reads} reads with no code edits`);
345
+ if (commands >= 6 && writes === 0 && tests === 0) {
346
+ pushUnique(driftSignals, `${commands} commands executed without visible code/test progress`);
347
+ }
348
+ if (errors >= 2)
349
+ pushUnique(driftSignals, `${errors} error/failure signals seen in outputs`);
350
+ if (deploys > 0 && tests === 0)
351
+ pushUnique(driftSignals, 'deploy/release command seen without test evidence');
352
+ if (progressSignals.length === 0 && events.length >= 3) {
353
+ pushUnique(driftSignals, 'no concrete progress signal captured in this batch');
354
+ }
355
+ let direction = 'on-track';
356
+ if (errors >= 3 && writes === 0 && tests === 0) {
357
+ direction = 'blocked';
358
+ }
359
+ else if (driftSignals.length > 0 && writes === 0 && tests === 0) {
360
+ direction = 'drifting';
656
361
  }
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;
362
+ else if (progressSignals.length === 0) {
363
+ direction = 'drifting';
662
364
  }
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
- };
365
+ const evidenceScore = writes + tests + deploys + (errors > 0 ? 1 : 0);
366
+ const confidence = events.length >= 8 || evidenceScore >= 3
367
+ ? 'high'
368
+ : events.length >= 4 || evidenceScore >= 1
369
+ ? 'medium'
370
+ : 'low';
371
+ const security = securityFindings.some((finding) => finding.severity === 'high')
372
+ ? 'alert'
373
+ : securityFindings.length > 0
374
+ ? 'watch'
375
+ : 'clean';
376
+ if (sessionTitle) {
377
+ pushUnique(progressSignals, `session goal/title: ${sessionTitle}`, 5);
378
+ }
379
+ return {
380
+ direction,
381
+ confidence,
382
+ security,
383
+ activitySummary: summarizeActivity(reads, writes, searches, commands, tests, errors),
384
+ progressSignals,
385
+ driftSignals,
386
+ securityFindings: securityFindings.slice(0, 3),
387
+ };
700
388
  }
701
389
  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;
390
+ let out = String(commentary || '').trim();
391
+ if (!out) {
392
+ out = 'Agent completed actions in this session.';
393
+ }
394
+ const hasSecuritySignal = /\bsecurity\b|\bsecurity alert\b|\bsecurity watch\b/i.test(out);
395
+ if (!hasSecuritySignal && assessment.security !== 'clean') {
396
+ const top = assessment.securityFindings[0];
397
+ const label = assessment.security === 'alert' ? 'SECURITY ALERT' : 'SECURITY WATCH';
398
+ const reason = top ? `${top.signal} (${top.evidence})` : 'Risky behavior detected in this batch.';
399
+ out = `${label}: ${reason}\n${out}`;
400
+ }
401
+ const hasClosingLine = /\bverdict\b|\bon.?track\b|\bdrifting\b|\bblocked\b|\bmomentum\b/i.test(out);
402
+ if (!hasClosingLine) {
403
+ out = `${out}\n${createClosingLine(assessment)}`;
404
+ }
405
+ return out;
720
406
  }
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;
407
+ class CommentaryEngine extends events_1.EventEmitter {
408
+ sessions = new Map();
409
+ flushQueue = [];
410
+ queuedSessions = new Set();
411
+ generating = false;
412
+ model = types_1.DEFAULT_MODEL;
413
+ userFocus = '';
414
+ preset = 'auto';
415
+ providers = {
416
+ anthropic: new anthropic_1.AnthropicProvider(),
417
+ openai: new openai_1.OpenAIProvider(),
418
+ };
419
+ keyResolver;
420
+ paused = false;
421
+ enabledFormatStyleIds = DEFAULT_FORMAT_STYLE_IDS.slice();
422
+ lastFormatStyleId = null;
423
+ constructor(keyResolver) {
424
+ super();
425
+ this.keyResolver = keyResolver;
768
426
  }
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;
427
+ setModel(model) {
428
+ this.model = model;
429
+ this.emit('model-changed', model);
792
430
  }
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);
431
+ getModel() {
432
+ return this.model;
797
433
  }
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
- });
434
+ setFocus(focus) {
435
+ this.userFocus = focus;
810
436
  }
811
- return this.sessions.get(key);
812
- }
813
- clearSessionTimers(state) {
814
- if (state.idleTimer) {
815
- clearTimeout(state.idleTimer);
816
- state.idleTimer = null;
437
+ getFocus() {
438
+ return this.userFocus;
817
439
  }
818
- if (state.maxTimer) {
819
- clearTimeout(state.maxTimer);
820
- state.maxTimer = null;
440
+ setPreset(preset) {
441
+ const next = Object.prototype.hasOwnProperty.call(PRESET_INSTRUCTIONS, preset)
442
+ ? preset
443
+ : 'auto';
444
+ this.preset = next;
445
+ this.emit('preset-changed', this.preset);
821
446
  }
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;
447
+ getPreset() {
448
+ return this.preset;
831
449
  }
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);
450
+ setFormatStyles(styleIds) {
451
+ const next = normalizeFormatStyles(Array.isArray(styleIds) ? styleIds : []);
452
+ if (sameFormatStyles(next, this.enabledFormatStyleIds))
453
+ return;
454
+ this.enabledFormatStyleIds = next;
455
+ if (this.lastFormatStyleId && !this.enabledFormatStyleIds.includes(this.lastFormatStyleId)) {
456
+ this.lastFormatStyleId = null;
861
457
  }
862
- }
458
+ this.emit('format-styles-changed', this.enabledFormatStyleIds.slice());
863
459
  }
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;
460
+ getFormatStyles() {
461
+ return this.enabledFormatStyleIds.slice();
878
462
  }
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;
463
+ pause() {
464
+ this.paused = true;
465
+ }
466
+ resume() {
467
+ this.paused = false;
468
+ }
469
+ isPaused() {
470
+ return this.paused;
471
+ }
472
+ addEvent(event) {
473
+ if (this.paused)
474
+ return;
475
+ // Skip noisy internal events
476
+ if (event.type === 'tool_result' || event.type === 'meta')
477
+ return;
478
+ const key = this.sessionKey(event);
479
+ const state = this.ensureSessionState(key);
480
+ state.events.push(event);
481
+ // Hard cap — flush immediately
482
+ if (state.events.length >= MAX_BATCH_SIZE) {
483
+ this.requestFlush(key, true);
484
+ return;
485
+ }
486
+ // Reset idle timer on each new event (wait for activity to settle)
487
+ if (state.idleTimer)
488
+ clearTimeout(state.idleTimer);
489
+ state.idleTimer = setTimeout(() => this.requestFlush(key, false), IDLE_TIMEOUT_MS);
490
+ // Start max-wait timer on first event in batch
491
+ if (!state.maxTimer) {
492
+ state.maxTimer = setTimeout(() => this.requestFlush(key, true), MAX_WAIT_MS);
905
493
  }
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
494
  }
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]}`;
495
+ sessionKey(event) {
496
+ return `${event.agent}:${event.sessionId}`;
938
497
  }
939
- if (this.userFocus.trim()) {
940
- prompt += `
941
-
942
- Additional user instruction: ${this.userFocus}`;
498
+ ensureSessionState(key) {
499
+ if (!this.sessions.has(key)) {
500
+ this.sessions.set(key, {
501
+ events: [],
502
+ idleTimer: null,
503
+ maxTimer: null,
504
+ recentCommentary: [],
505
+ });
506
+ }
507
+ return this.sessions.get(key);
943
508
  }
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
- `;
509
+ clearSessionTimers(state) {
510
+ if (state.idleTimer) {
511
+ clearTimeout(state.idleTimer);
512
+ state.idleTimer = null;
513
+ }
514
+ if (state.maxTimer) {
515
+ clearTimeout(state.maxTimer);
516
+ state.maxTimer = null;
517
+ }
954
518
  }
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
- `;
519
+ requestFlush(key, force) {
520
+ const state = this.sessions.get(key);
521
+ if (!state)
522
+ return;
523
+ this.clearSessionTimers(state);
524
+ if (state.events.length === 0)
525
+ return;
526
+ // Need minimum events for meaningful commentary unless force-flushing.
527
+ if (!force && state.events.length < MIN_BATCH_SIZE) {
528
+ state.idleTimer = setTimeout(() => this.requestFlush(key, true), IDLE_TIMEOUT_MS);
529
+ return;
530
+ }
531
+ this.enqueueFlush(key);
532
+ }
533
+ enqueueFlush(key) {
534
+ if (this.queuedSessions.has(key))
535
+ return;
536
+ this.queuedSessions.add(key);
537
+ this.flushQueue.push(key);
538
+ this.processFlushQueue();
539
+ }
540
+ async processFlushQueue() {
541
+ if (this.generating)
542
+ return;
543
+ while (this.flushQueue.length > 0) {
544
+ const key = this.flushQueue.shift();
545
+ this.queuedSessions.delete(key);
546
+ const state = this.sessions.get(key);
547
+ if (!state || state.events.length === 0)
548
+ continue;
549
+ const batch = state.events.splice(0);
550
+ this.generating = true;
551
+ try {
552
+ await this.generateCommentary(batch, state);
553
+ }
554
+ catch (err) {
555
+ this.emit('error', err);
556
+ }
557
+ finally {
558
+ this.generating = false;
559
+ }
560
+ // Events may have arrived while we were generating.
561
+ if (state.events.length > 0) {
562
+ if (state.events.length >= MIN_BATCH_SIZE) {
563
+ this.enqueueFlush(key);
564
+ }
565
+ else if (!state.idleTimer) {
566
+ state.idleTimer = setTimeout(() => this.requestFlush(key, true), IDLE_TIMEOUT_MS);
567
+ }
568
+ }
569
+ }
966
570
  }
967
- if (assessment.driftSignals.length > 0) {
968
- prompt += `- Drift/block signals: ${assessment.driftSignals.join(" | ")}
969
- `;
571
+ pickFormat() {
572
+ const enabled = this.enabledFormatStyleIds.length > 0
573
+ ? this.enabledFormatStyleIds
574
+ : DEFAULT_FORMAT_STYLE_IDS;
575
+ const pool = this.lastFormatStyleId && enabled.length > 1
576
+ ? enabled.filter((styleId) => styleId !== this.lastFormatStyleId)
577
+ : enabled;
578
+ const pickedStyleId = pool[Math.floor(Math.random() * pool.length)];
579
+ this.lastFormatStyleId = pickedStyleId;
580
+ return FORMAT_TEMPLATES[pickedStyleId];
970
581
  }
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
- `;
582
+ async generateCommentary(events, state) {
583
+ if (events.length === 0)
584
+ return;
585
+ const modelConfig = types_1.MODELS.find(m => m.id === this.model);
586
+ if (!modelConfig) {
587
+ this.emit('error', new Error(`Unknown model: ${this.model}`));
588
+ return;
589
+ }
590
+ const apiKey = '';
591
+ const systemPrompt = this.buildSystemPrompt();
592
+ const latest = events[events.length - 1];
593
+ const assessment = buildCommentaryAssessment(events, latest.sessionTitle);
594
+ const userPrompt = this.buildUserPrompt(events, state.recentCommentary, assessment);
595
+ const provider = this.providers[modelConfig.provider];
596
+ const entry = {
597
+ timestamp: Date.now(),
598
+ sessionId: latest.sessionId,
599
+ projectName: latest.projectName,
600
+ sessionTitle: latest.sessionTitle,
601
+ agent: latest.agent,
602
+ source: latest.source,
603
+ eventSummary: events.map(e => sanitizePromptText(e.summary)).join(' → '),
604
+ commentary: '',
605
+ };
606
+ this.emit('commentary-start', entry);
607
+ try {
608
+ let streamedRaw = '';
609
+ const full = await provider.generate(systemPrompt, userPrompt, apiKey || '', this.model, (chunk) => {
610
+ streamedRaw += chunk;
611
+ });
612
+ const rawOutput = full || streamedRaw;
613
+ entry.commentary = applyAssessmentSignals(sanitizeGeneratedCommentary(rawOutput), assessment);
614
+ if (entry.commentary) {
615
+ this.emit('commentary-chunk', { entry, chunk: entry.commentary });
616
+ }
617
+ this.emit('commentary-done', entry);
618
+ // Track recent commentary per session to avoid repetition.
619
+ state.recentCommentary.push(entry.commentary);
620
+ if (state.recentCommentary.length > 5) {
621
+ state.recentCommentary.shift();
622
+ }
623
+ }
624
+ catch (err) {
625
+ entry.commentary = 'Commentary unavailable right now.';
626
+ this.emit('commentary-chunk', { entry, chunk: entry.commentary });
627
+ this.emit('commentary-done', entry);
628
+ this.emit('error', err);
629
+ }
984
630
  }
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.`;
631
+ buildSystemPrompt() {
632
+ let prompt = SYSTEM_PROMPT;
633
+ // Add random format template
634
+ prompt += `\n\nFormat for this message:\n${this.pickFormat()}`;
635
+ if (this.preset !== 'auto') {
636
+ prompt += `\n\nTone preset:\n${PRESET_INSTRUCTIONS[this.preset]}`;
637
+ }
638
+ if (this.userFocus.trim()) {
639
+ prompt += `\n\nAdditional user instruction: ${this.userFocus}`;
640
+ }
641
+ return prompt;
993
642
  }
994
- const recentlyUsed = extractRecentlyUsedJudgments(recentCommentary);
995
- if (recentlyUsed.length > 0) {
996
- prompt += `
997
- Avoid reusing these reaction phrases: ${recentlyUsed.join(", ")}.`;
643
+ buildUserPrompt(events, recentCommentary, assessment) {
644
+ const latest = events[events.length - 1];
645
+ const lines = events.map(e => ` ${e.type}: ${sanitizePromptText(e.summary)}`);
646
+ let prompt = `Session: ${latest.agent}/${latest.projectName}\n`;
647
+ if (latest.sessionTitle) {
648
+ prompt += `Session title: ${sanitizePromptText(latest.sessionTitle)}\n`;
649
+ }
650
+ prompt += `Actions (${events.length}):\n${lines.join('\n')}`;
651
+ prompt += `\n\nDirection context:\n`;
652
+ prompt += `- Activity snapshot: ${assessment.activitySummary}\n`;
653
+ if (assessment.progressSignals.length > 0) {
654
+ prompt += `- Progress signals: ${assessment.progressSignals.join(' | ')}\n`;
655
+ }
656
+ if (assessment.driftSignals.length > 0) {
657
+ prompt += `- Drift/block signals: ${assessment.driftSignals.join(' | ')}\n`;
658
+ }
659
+ prompt += `- Initial direction estimate from raw signals: ${assessment.direction} (${assessment.confidence} confidence)\n`;
660
+ prompt += `\nSecurity auto-check:\n`;
661
+ prompt += `- Security state: ${assessment.security}\n`;
662
+ if (assessment.securityFindings.length > 0) {
663
+ prompt += assessment.securityFindings
664
+ .map((finding) => `- ${finding.severity.toUpperCase()}: ${finding.signal} (${finding.evidence})`)
665
+ .join('\n');
666
+ prompt += '\n';
667
+ }
668
+ else {
669
+ prompt += `- No explicit security flags in this batch.\n`;
670
+ }
671
+ // Include recent commentary so the LLM doesn't repeat itself
672
+ if (recentCommentary.length > 0) {
673
+ prompt += `\n\nPrevious commentary (don't repeat):\n`;
674
+ prompt += recentCommentary
675
+ .slice(-3)
676
+ .map(c => `- ${sanitizePromptText(c).slice(0, 80)}...`)
677
+ .join('\n');
678
+ prompt += `\nUse different judgment wording than these previous lines.`;
679
+ }
680
+ const recentlyUsed = extractRecentlyUsedJudgments(recentCommentary);
681
+ if (recentlyUsed.length > 0) {
682
+ prompt += `\nAvoid reusing these reaction phrases: ${recentlyUsed.join(', ')}.`;
683
+ }
684
+ prompt += `\n\nSummarize only this session's actions. Plain language, with your reaction.`;
685
+ prompt += `\nClose with one sentence about direction and momentum — weave in confidence and security naturally.`;
686
+ prompt += `\nNever mention IDs/logs/prompts/traces or internal data-collection steps.`;
687
+ prompt += `\nNever say "I", "I'll", "we", or any future plan.`;
688
+ return prompt;
998
689
  }
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
- };
690
+ }
691
+ exports.CommentaryEngine = CommentaryEngine;
1011
692
  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;
693
+ let out = text;
694
+ for (const pattern of SESSION_ID_PATTERNS) {
695
+ out = out.replace(pattern, '[session]');
696
+ }
697
+ return out;
1017
698
  }
1018
699
  function sanitizePromptText(text) {
1019
- return redactSessionIdentifiers(String(text || "")).replace(/\s+/g, " ").trim();
700
+ return redactSessionIdentifiers(String(text || ''))
701
+ .replace(/\s+/g, ' ')
702
+ .trim();
1020
703
  }
1021
704
  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;
705
+ let out = redactSessionIdentifiers(String(text || ''));
706
+ out = out.replace(/\blocal session logs?\b/gi, 'recent actions');
707
+ out = out.replace(/\bsession logs?\b/gi, 'actions');
708
+ out = out.replace(/\bsession id\b/gi, 'session');
709
+ // Strip any "Verdict:" label the LLM generates anywhere in the text — keep the text after it
710
+ out = out.replace(/\bverdict\s*:\s*/gi, '');
711
+ 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;
712
+ const keptLines = out.split('\n').filter(line => !blockedLine.test(line.trim()));
713
+ out = keptLines.join('\n').trim();
714
+ if (!out) {
715
+ return 'Agent completed actions in this session.';
716
+ }
717
+ return out;
1034
718
  }
1035
- // Annotate the CommonJS export names for ESM import in node:
1036
- 0 && (module.exports = {
1037
- CommentaryEngine,
1038
- applyAssessmentSignals,
1039
- buildCommentaryAssessment,
1040
- sanitizeGeneratedCommentary
1041
- });
719
+ //# sourceMappingURL=commentary.js.map