groove-dev 0.26.32 → 0.26.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/node_modules/@groove-dev/daemon/src/api.js +3 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +13 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +146 -28
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -2
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +3 -3
- package/node_modules/@groove-dev/gui/dist/assets/index-86xvrzfI.js +638 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CEFKgLGB.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +11 -8
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +511 -0
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +3 -1
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +26 -4
- package/package.json +1 -1
- package/packages/daemon/src/api.js +3 -0
- package/packages/daemon/src/introducer.js +13 -0
- package/packages/daemon/src/journalist.js +146 -28
- package/packages/daemon/src/providers/claude-code.js +4 -2
- package/packages/gui/dist/assets/index-86xvrzfI.js +638 -0
- package/packages/gui/dist/assets/index-CEFKgLGB.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +11 -8
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +511 -0
- package/packages/gui/src/views/dashboard.jsx +3 -1
- package/packages/gui/src/views/marketplace.jsx +26 -4
- package/node_modules/@groove-dev/gui/dist/assets/index-BnNZzcsd.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-CwUZRfEx.js +0 -638
- package/packages/gui/dist/assets/index-BnNZzcsd.css +0 -1
- package/packages/gui/dist/assets/index-CwUZRfEx.js +0 -638
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.26.33 — Self-building pipeline, team persistence, agent quality overhaul (2026-04-11)
|
|
4
|
+
|
|
5
|
+
Major release: Groove now builds itself. Full team lifecycle, intelligent context synthesis, and agent quality improvements across the board.
|
|
6
|
+
|
|
7
|
+
**Team Persistence and Agent Reuse**
|
|
8
|
+
- Teams are persistent — agents stay across tasks instead of respawning every time
|
|
9
|
+
- Launch flow reuses existing agents by role: planner delegates, existing frontend/backend resume with new task and full context
|
|
10
|
+
- Auto-delegate: when all required roles exist in the team, planner skips the Launch modal and routes work directly (toast notification)
|
|
11
|
+
- QC lifecycle: spawns idle when no work, auto-triggers with teammate context when agents complete real work
|
|
12
|
+
- Cross-scope handoffs: agents write `.groove/handoffs/{role}.md`, system auto-routes to the owning agent
|
|
13
|
+
|
|
14
|
+
**Planner Intelligence**
|
|
15
|
+
- Mode 1 (team creation): full codebase exploration, detailed team structure
|
|
16
|
+
- Mode 2 (task routing): detects existing team via AGENTS_REGISTRY.md, reads only relevant files, routes to existing agents — fast, under 5 tool calls
|
|
17
|
+
- Always writes recommended-team.json, even for team-building-only mode (empty prompts = agents await instructions)
|
|
18
|
+
|
|
19
|
+
**Agent Quality**
|
|
20
|
+
- Role prompts for all 16 agent roles — frontend gets full design system (colors, CSS variables, fonts, components), backend gets ESM conventions and compliance rules
|
|
21
|
+
- All roles default to Heavy tier (Opus) — intelligence out of the box, users opt into cheaper models
|
|
22
|
+
- Permission and scope changes now persist (added to registry SAFE_FIELDS)
|
|
23
|
+
|
|
24
|
+
**Journalist Overhaul**
|
|
25
|
+
- Dropped exploration noise: Read/Glob/Grep tool calls no longer clutter synthesis
|
|
26
|
+
- Edit diffs captured: `"bg-[#3e4451]" -> "HEX.accent"` shows in project map
|
|
27
|
+
- Bash output captured: `npm test -> 141 passed` gives agents build/test context
|
|
28
|
+
- Thinking threshold raised (50 -> 200 chars) — only real decisions survive
|
|
29
|
+
- Transient stderr errors dropped — no more phantom error degradation
|
|
30
|
+
- User feedback tracking: messages to agents recorded and injected into future agent context, persisted to disk
|
|
31
|
+
- Decisions log deduplication: >60% word overlap with previous entry = skip
|
|
32
|
+
- Completed agents persist in project map for 30 minutes after finishing
|
|
33
|
+
- Decisions log capped at 20 entries
|
|
34
|
+
|
|
35
|
+
**Promote Pipeline**
|
|
36
|
+
- `./promote.sh`: build GUI -> run tests -> staging daemon on :31416 -> verify -> publish to npm + push to GitHub
|
|
37
|
+
- Staging uses isolated `.groove-staging/` directory — doesn't kill running daemon
|
|
38
|
+
- npm registry retry with version verification and cache clear
|
|
39
|
+
- Explicit file staging (no `git add -A`) for safety
|
|
40
|
+
|
|
41
|
+
**UX Improvements**
|
|
42
|
+
- Thinking indicator: rich animated phases (Analyzing, Reasoning, Planning...), elapsed timer, shimmer sweep, fires immediately on all launch paths
|
|
43
|
+
- Chat message dedup: Claude Code assistant + result events no longer double up
|
|
44
|
+
- Chat input persists across tab switches (stored in Zustand per agent)
|
|
45
|
+
- Agent tree: debounced fitView prevents jitter on team launch, node positions saved by name (stable across resumes)
|
|
46
|
+
- Edge handles recalculated from actual node positions on every render
|
|
47
|
+
- HTML preview in editor: Code/Preview toggle for .html files via sandboxed iframe
|
|
48
|
+
- Context bars use teal accent color (agent nodes + fleet panel)
|
|
49
|
+
|
|
50
|
+
**Infrastructure**
|
|
51
|
+
- Terminal PTY uses crypto.randomUUID() instead of predictable counters
|
|
52
|
+
- Codex agents skip PM gate (sandboxed providers can't reach localhost)
|
|
53
|
+
- Skill marketplace: Pull Latest, Uninstall, and Update flow
|
|
54
|
+
- Auth logs scrubbed — username only, no PII
|
|
55
|
+
- GC: immediate cleanup on team delete, orphaned logs removed instantly
|
|
56
|
+
- QC agents verify builds (`npm run build`), never start long-running dev servers
|
|
57
|
+
|
|
3
58
|
## v0.20.0 — Settings page, Ollama setup, GUI v2 rebuild, full system overhaul (2026-04-08)
|
|
4
59
|
|
|
5
60
|
Major release: complete GUI rebuild + Settings page + Ollama setup + tech debt cleanup.
|
|
@@ -484,6 +484,9 @@ export function createApi(app, daemon) {
|
|
|
484
484
|
const agent = daemon.registry.get(req.params.id);
|
|
485
485
|
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
486
486
|
|
|
487
|
+
// Record user feedback so the journalist can include it in future agent context
|
|
488
|
+
if (daemon.journalist) daemon.journalist.recordUserFeedback(agent, message.trim());
|
|
489
|
+
|
|
487
490
|
// Agent loop path — send message directly to the running loop
|
|
488
491
|
if (daemon.processes.hasAgentLoop(req.params.id)) {
|
|
489
492
|
const sent = await daemon.processes.sendMessage(req.params.id, message.trim());
|
|
@@ -85,6 +85,19 @@ export class Introducer {
|
|
|
85
85
|
lines.push(` GROOVE will automatically wake the target agent and deliver your request.`);
|
|
86
86
|
lines.push(`- Check AGENTS_REGISTRY.md for the latest team state.`);
|
|
87
87
|
|
|
88
|
+
// User feedback from previous tasks — critical context about what the user
|
|
89
|
+
// observed and what needs to change. Prevents agents from repeating mistakes.
|
|
90
|
+
const feedback = this.daemon.journalist?.getUserFeedback() || [];
|
|
91
|
+
if (feedback.length > 0) {
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push(`## User Feedback (from previous tasks)`);
|
|
94
|
+
lines.push('');
|
|
95
|
+
lines.push(`The user sent these messages about previous agents' work. Pay close attention — these indicate issues that previous agents missed:`);
|
|
96
|
+
for (const fb of feedback.slice(-10)) {
|
|
97
|
+
lines.push(`- **${fb.agentName}** (${fb.role}): "${fb.message}"`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
// Project files section — tell the new agent what exists and what to read
|
|
89
102
|
if (allTeamFiles.length > 0) {
|
|
90
103
|
lines.push('');
|
|
@@ -57,9 +57,18 @@ export class Journalist {
|
|
|
57
57
|
if (this.synthesizing) return; // Don't overlap
|
|
58
58
|
|
|
59
59
|
const agents = this.daemon.registry.getAll();
|
|
60
|
-
const
|
|
60
|
+
const running = agents.filter((a) => a.status === 'running');
|
|
61
|
+
|
|
62
|
+
// Include recently completed agents (last 30 min) so their work persists
|
|
63
|
+
// in the project map instead of vanishing the moment they finish
|
|
64
|
+
const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
65
|
+
const recentlyCompleted = agents.filter((a) =>
|
|
66
|
+
a.status === 'completed' && a.lastActivity && a.lastActivity > thirtyMinAgo
|
|
67
|
+
&& !running.some((r) => r.id === a.id)
|
|
68
|
+
);
|
|
69
|
+
const activeAgents = [...running, ...recentlyCompleted];
|
|
61
70
|
|
|
62
|
-
// Skip if no
|
|
71
|
+
// Skip if no agents to synthesize
|
|
63
72
|
if (activeAgents.length === 0) return;
|
|
64
73
|
|
|
65
74
|
// Smart scheduling: skip if no new log output since last cycle
|
|
@@ -149,33 +158,75 @@ export class Journalist {
|
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
filterLog(rawLog, agent) {
|
|
152
|
-
// Parse stream-json lines and extract meaningful events
|
|
161
|
+
// Parse stream-json lines and extract meaningful events.
|
|
162
|
+
// Focus on PROGRESS (writes, edits, commands with results) not EXPLORATION (reads, greps).
|
|
153
163
|
const entries = [];
|
|
154
164
|
const lines = rawLog.split('\n');
|
|
165
|
+
const toolResults = new Map(); // tool_use_id -> result text
|
|
155
166
|
|
|
167
|
+
// First pass: collect tool results so we can attach them to tool calls
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
if (!line.trim() || line.startsWith('[')) continue;
|
|
170
|
+
try {
|
|
171
|
+
const data = JSON.parse(line);
|
|
172
|
+
if (data.type === 'user' && data.message?.content) {
|
|
173
|
+
const content = Array.isArray(data.message.content) ? data.message.content : [];
|
|
174
|
+
for (const block of content) {
|
|
175
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
176
|
+
const text = typeof block.content === 'string' ? block.content
|
|
177
|
+
: Array.isArray(block.content) ? block.content.map((c) => c.text || '').join('').slice(0, 300)
|
|
178
|
+
: '';
|
|
179
|
+
if (text) toolResults.set(block.tool_use_id, text);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch { /* skip */ }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Second pass: extract meaningful events
|
|
156
187
|
for (const line of lines) {
|
|
157
|
-
// Skip empty lines and GROOVE spawn headers
|
|
158
188
|
if (!line.trim() || line.startsWith('[')) continue;
|
|
159
189
|
|
|
160
190
|
try {
|
|
161
191
|
const data = JSON.parse(line);
|
|
162
192
|
|
|
163
|
-
// Tool use
|
|
193
|
+
// Tool use — only keep WRITES, EDITS, and COMMANDS (progress, not exploration)
|
|
164
194
|
if (data.type === 'assistant' && data.message?.content) {
|
|
165
195
|
const content = data.message.content;
|
|
166
196
|
if (Array.isArray(content)) {
|
|
167
197
|
for (const block of content) {
|
|
168
198
|
if (block.type === 'tool_use') {
|
|
169
|
-
|
|
199
|
+
const tool = block.name;
|
|
200
|
+
|
|
201
|
+
// Skip exploration tools — reads/searches are noise for synthesis
|
|
202
|
+
if (tool === 'Read' || tool === 'Glob' || tool === 'Grep') continue;
|
|
203
|
+
|
|
204
|
+
const entry = {
|
|
170
205
|
type: 'tool',
|
|
171
|
-
tool
|
|
172
|
-
input: this.summarizeToolInput(
|
|
206
|
+
tool,
|
|
207
|
+
input: this.summarizeToolInput(tool, block.input),
|
|
173
208
|
timestamp: data.timestamp,
|
|
174
|
-
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Capture what was actually changed for Edit operations
|
|
212
|
+
if (tool === 'Edit' && block.input) {
|
|
213
|
+
const old = (block.input.old_string || '').slice(0, 150);
|
|
214
|
+
const nw = (block.input.new_string || '').slice(0, 150);
|
|
215
|
+
if (old && nw) entry.diff = `"${old}" → "${nw}"`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Capture Bash command output (exit code, first line of result)
|
|
219
|
+
if (tool === 'Bash' && block.id) {
|
|
220
|
+
const result = toolResults.get(block.id);
|
|
221
|
+
if (result) entry.output = result.slice(0, 200);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
entries.push(entry);
|
|
175
225
|
} else if (block.type === 'text' && block.text) {
|
|
176
|
-
// Only keep substantial
|
|
226
|
+
// Only keep substantial reasoning (decisions, conclusions)
|
|
227
|
+
// Short fragments like "Let me check..." are noise
|
|
177
228
|
const text = block.text.trim();
|
|
178
|
-
if (text.length >
|
|
229
|
+
if (text.length > 200) {
|
|
179
230
|
entries.push({ type: 'thinking', text: text.slice(0, 2000), timestamp: data.timestamp });
|
|
180
231
|
}
|
|
181
232
|
}
|
|
@@ -194,10 +245,8 @@ export class Journalist {
|
|
|
194
245
|
});
|
|
195
246
|
}
|
|
196
247
|
} catch {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
entries.push({ type: 'error', text: line.trim().slice(0, 200) });
|
|
200
|
-
}
|
|
248
|
+
// Skip non-JSON lines entirely — transient stderr like ENOENT
|
|
249
|
+
// causes degradation when agents try to "fix" phantom errors
|
|
201
250
|
}
|
|
202
251
|
}
|
|
203
252
|
|
|
@@ -208,15 +257,11 @@ export class Journalist {
|
|
|
208
257
|
if (!input) return '';
|
|
209
258
|
switch (toolName) {
|
|
210
259
|
case 'Write':
|
|
211
|
-
case 'Edit':
|
|
212
260
|
return input.file_path || input.path || '';
|
|
213
|
-
case '
|
|
261
|
+
case 'Edit':
|
|
214
262
|
return input.file_path || input.path || '';
|
|
215
263
|
case 'Bash':
|
|
216
|
-
return (input.command || '').slice(0,
|
|
217
|
-
case 'Glob':
|
|
218
|
-
case 'Grep':
|
|
219
|
-
return input.pattern || '';
|
|
264
|
+
return (input.command || '').slice(0, 150);
|
|
220
265
|
default:
|
|
221
266
|
return JSON.stringify(input).slice(0, 100);
|
|
222
267
|
}
|
|
@@ -295,14 +340,16 @@ export class Journalist {
|
|
|
295
340
|
|
|
296
341
|
formatEntry(entry) {
|
|
297
342
|
switch (entry.type) {
|
|
298
|
-
case 'tool':
|
|
299
|
-
|
|
343
|
+
case 'tool': {
|
|
344
|
+
let line = `- [${entry.tool}] ${entry.input}`;
|
|
345
|
+
if (entry.diff) line += ` — changed: ${entry.diff}`;
|
|
346
|
+
if (entry.output) line += ` → ${entry.output.split('\n')[0]}`;
|
|
347
|
+
return line;
|
|
348
|
+
}
|
|
300
349
|
case 'thinking':
|
|
301
|
-
return `- [thought] ${entry.text.slice(0,
|
|
350
|
+
return `- [thought] ${entry.text.slice(0, 300)}`;
|
|
302
351
|
case 'result':
|
|
303
|
-
return `- [result] ${entry.text.slice(0,
|
|
304
|
-
case 'error':
|
|
305
|
-
return `- [error] ${entry.text}`;
|
|
352
|
+
return `- [result] ${entry.text.slice(0, 300)} (${entry.turns} turns, ${entry.duration}ms)`;
|
|
306
353
|
default:
|
|
307
354
|
return `- [${entry.type}] ${entry.text || ''}`;
|
|
308
355
|
}
|
|
@@ -515,8 +562,25 @@ export class Journalist {
|
|
|
515
562
|
existing = readFileSync(path, 'utf8').replace(/^# GROOVE Decisions Log[\s\S]*?\n\n/, '');
|
|
516
563
|
}
|
|
517
564
|
|
|
565
|
+
// Deduplicate — skip if the new decisions are substantially similar to the most recent entry.
|
|
566
|
+
// Extract the last cycle's decisions text for comparison.
|
|
567
|
+
if (existing) {
|
|
568
|
+
const lastEntry = existing.match(/^## Cycle[\s\S]*?\n\n([\s\S]*?)(?=\n---|\n\n\*Auto|\n## Cycle|$)/);
|
|
569
|
+
if (lastEntry) {
|
|
570
|
+
const lastText = lastEntry[1].trim().toLowerCase().replace(/\s+/g, ' ');
|
|
571
|
+
const newText = decisions.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
572
|
+
// If >60% of the new text appears in the last entry, skip (near-duplicate)
|
|
573
|
+
const newWords = newText.split(' ').filter((w) => w.length > 3);
|
|
574
|
+
const overlap = newWords.filter((w) => lastText.includes(w)).length;
|
|
575
|
+
if (newWords.length > 0 && overlap / newWords.length > 0.6) return;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
518
579
|
const entry = `## Cycle ${this.cycleCount} — ${new Date().toISOString()}\n\n${decisions}\n\n`;
|
|
519
|
-
|
|
580
|
+
|
|
581
|
+
// Keep last 20 entries max to prevent unbounded growth
|
|
582
|
+
const entries = (entry + existing).split(/(?=^## Cycle)/m).slice(0, 20).join('');
|
|
583
|
+
writeFileSync(path, header + entries);
|
|
520
584
|
}
|
|
521
585
|
|
|
522
586
|
writeAgentSessionLogs(agents, filteredLogs) {
|
|
@@ -724,6 +788,60 @@ export class Journalist {
|
|
|
724
788
|
}
|
|
725
789
|
}
|
|
726
790
|
|
|
791
|
+
// --- User Feedback Tracking ---
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Record user feedback/messages sent to agents.
|
|
795
|
+
* This gets included in the project map and handoff briefs so future
|
|
796
|
+
* agents know what the user said about previous work.
|
|
797
|
+
*/
|
|
798
|
+
recordUserFeedback(agent, message) {
|
|
799
|
+
if (!this._userFeedback) this._loadFeedback();
|
|
800
|
+
if (!this._userFeedback[agent.id]) this._userFeedback[agent.id] = [];
|
|
801
|
+
this._userFeedback[agent.id].push({
|
|
802
|
+
agentName: agent.name,
|
|
803
|
+
role: agent.role,
|
|
804
|
+
message: message.slice(0, 500),
|
|
805
|
+
timestamp: new Date().toISOString(),
|
|
806
|
+
});
|
|
807
|
+
// Keep last 20 per agent
|
|
808
|
+
if (this._userFeedback[agent.id].length > 20) {
|
|
809
|
+
this._userFeedback[agent.id] = this._userFeedback[agent.id].slice(-20);
|
|
810
|
+
}
|
|
811
|
+
this._saveFeedback();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
_loadFeedback() {
|
|
815
|
+
const p = resolve(this.daemon.grooveDir, 'user-feedback.json');
|
|
816
|
+
try {
|
|
817
|
+
if (existsSync(p)) this._userFeedback = JSON.parse(readFileSync(p, 'utf8'));
|
|
818
|
+
else this._userFeedback = {};
|
|
819
|
+
} catch { this._userFeedback = {}; }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
_saveFeedback() {
|
|
823
|
+
try {
|
|
824
|
+
writeFileSync(resolve(this.daemon.grooveDir, 'user-feedback.json'), JSON.stringify(this._userFeedback, null, 2));
|
|
825
|
+
} catch { /* non-fatal */ }
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Get user feedback for an agent (or all agents in a team).
|
|
830
|
+
* Used by the introducer to give agents context about previous attempts.
|
|
831
|
+
*/
|
|
832
|
+
getUserFeedback(agentOrTeamId) {
|
|
833
|
+
if (!this._userFeedback) this._loadFeedback();
|
|
834
|
+
if (!this._userFeedback) return [];
|
|
835
|
+
// Check if it's an agent ID
|
|
836
|
+
if (this._userFeedback[agentOrTeamId]) return this._userFeedback[agentOrTeamId];
|
|
837
|
+
// Check by team — collect feedback for all agents in the team
|
|
838
|
+
const all = [];
|
|
839
|
+
for (const [, entries] of Object.entries(this._userFeedback)) {
|
|
840
|
+
all.push(...entries);
|
|
841
|
+
}
|
|
842
|
+
return all.sort((a, b) => a.timestamp.localeCompare(b.timestamp)).slice(-30);
|
|
843
|
+
}
|
|
844
|
+
|
|
727
845
|
// --- Accessors ---
|
|
728
846
|
|
|
729
847
|
getLastSynthesis() {
|
|
@@ -75,9 +75,11 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
buildHeadlessCommand(prompt, model) {
|
|
78
|
-
|
|
78
|
+
// Pass prompt via stdin to avoid OS argument length limits.
|
|
79
|
+
// Long prompts (journalist synthesis with agent logs) can exceed ARG_MAX.
|
|
80
|
+
const args = ['-p', '--output-format', 'stream-json', '--verbose'];
|
|
79
81
|
if (model) args.push('--model', model);
|
|
80
|
-
return { command: 'claude', args, env: {} };
|
|
82
|
+
return { command: 'claude', args, env: {}, stdin: prompt };
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
buildFullPrompt(agent) {
|
|
@@ -52,13 +52,13 @@ describe('Journalist', () => {
|
|
|
52
52
|
assert.equal(entries[0].input, 'src/api/auth.js');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('should
|
|
55
|
+
it('should skip non-JSON lines (transient errors are noise)', () => {
|
|
56
56
|
const { daemon } = createMockDaemon();
|
|
57
57
|
const journalist = new Journalist(daemon);
|
|
58
58
|
|
|
59
|
+
// Non-JSON error lines are dropped to prevent context degradation
|
|
59
60
|
const entries = journalist.filterLog('Error: something broke\nTypeError: undefined is not a function', {});
|
|
60
|
-
assert.equal(entries.length,
|
|
61
|
-
assert.equal(entries[0].type, 'error');
|
|
61
|
+
assert.equal(entries.length, 0);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
it('should extract result events', () => {
|