claude-mycelium 2.0.0 → 2.2.0
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/.agent-meta/_inhibitors.ndjson +1287 -0
- package/.agent-meta/_quarantine.json +45 -0
- package/.agent-meta/config.json +9 -0
- package/.agent-meta/tasks/_active.json +4 -0
- package/.agent-meta/tasks/task_0657b028-05a0-4b0c-b0b9-a4eae3d66cd9.json +168 -0
- package/.claude/memory.db +0 -0
- package/.claude/settings.local.json +4 -1
- package/README.md +85 -233
- package/SECURITY.md +145 -0
- package/dist/agent/task-worker.d.ts +11 -0
- package/dist/agent/task-worker.d.ts.map +1 -0
- package/dist/agent/task-worker.js +173 -0
- package/dist/agent/task-worker.js.map +1 -0
- package/dist/agent/worker.d.ts +8 -0
- package/dist/agent/worker.d.ts.map +1 -0
- package/dist/agent/worker.js +97 -0
- package/dist/agent/worker.js.map +1 -0
- package/dist/bin.d.ts +7 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/cost.d.ts +10 -0
- package/dist/cli/cost.d.ts.map +1 -0
- package/dist/cli/cost.js +163 -0
- package/dist/cli/cost.js.map +1 -0
- package/dist/cli/gc.d.ts +10 -0
- package/dist/cli/gc.d.ts.map +1 -0
- package/dist/cli/gc.js +108 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/gradients.d.ts +10 -0
- package/dist/cli/gradients.d.ts.map +1 -0
- package/dist/cli/gradients.js +70 -0
- package/dist/cli/gradients.js.map +1 -0
- package/dist/cli/grow.d.ts +17 -0
- package/dist/cli/grow.d.ts.map +1 -0
- package/dist/cli/grow.js +373 -0
- package/dist/cli/grow.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +11 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +97 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/status.d.ts +10 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +191 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/coordination/file-locks.d.ts +42 -0
- package/dist/coordination/file-locks.d.ts.map +1 -0
- package/dist/coordination/file-locks.js +269 -0
- package/dist/coordination/file-locks.js.map +1 -0
- package/dist/coordination/index.d.ts +4 -0
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +4 -0
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/inhibitors.d.ts +84 -0
- package/dist/coordination/inhibitors.d.ts.map +1 -0
- package/dist/coordination/inhibitors.js +290 -0
- package/dist/coordination/inhibitors.js.map +1 -0
- package/dist/coordination/process-manager.d.ts +73 -0
- package/dist/coordination/process-manager.d.ts.map +1 -0
- package/dist/coordination/process-manager.js +144 -0
- package/dist/coordination/process-manager.js.map +1 -0
- package/dist/core/agent-executor.d.ts +4 -1
- package/dist/core/agent-executor.d.ts.map +1 -1
- package/dist/core/agent-executor.js +38 -12
- package/dist/core/agent-executor.js.map +1 -1
- package/dist/core/change-applier.d.ts +29 -5
- package/dist/core/change-applier.d.ts.map +1 -1
- package/dist/core/change-applier.js +254 -24
- package/dist/core/change-applier.js.map +1 -1
- package/dist/core/signals/churn.d.ts.map +1 -1
- package/dist/core/signals/churn.js +6 -4
- package/dist/core/signals/churn.js.map +1 -1
- package/dist/core/signals/debt.d.ts.map +1 -1
- package/dist/core/signals/debt.js +4 -3
- package/dist/core/signals/debt.js.map +1 -1
- package/dist/cost/cost-tracker.d.ts.map +1 -1
- package/dist/cost/cost-tracker.js +2 -0
- package/dist/cost/cost-tracker.js.map +1 -1
- package/dist/gc/index.d.ts +17 -0
- package/dist/gc/index.d.ts.map +1 -0
- package/dist/gc/index.js +17 -0
- package/dist/gc/index.js.map +1 -0
- package/dist/gc/runner.d.ts +39 -0
- package/dist/gc/runner.d.ts.map +1 -0
- package/dist/gc/runner.js +277 -0
- package/dist/gc/runner.js.map +1 -0
- package/dist/gc/trace-compactor.d.ts +31 -0
- package/dist/gc/trace-compactor.d.ts.map +1 -0
- package/dist/gc/trace-compactor.js +162 -0
- package/dist/gc/trace-compactor.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js.map +1 -1
- package/dist/quarantine/explorer.d.ts +65 -0
- package/dist/quarantine/explorer.d.ts.map +1 -0
- package/dist/quarantine/explorer.js +175 -0
- package/dist/quarantine/explorer.js.map +1 -0
- package/dist/quarantine/index.d.ts +7 -0
- package/dist/quarantine/index.d.ts.map +1 -0
- package/dist/quarantine/index.js +7 -0
- package/dist/quarantine/index.js.map +1 -0
- package/dist/quarantine/manager.d.ts +75 -0
- package/dist/quarantine/manager.d.ts.map +1 -0
- package/dist/quarantine/manager.js +275 -0
- package/dist/quarantine/manager.js.map +1 -0
- package/dist/task/acceptance.d.ts +29 -0
- package/dist/task/acceptance.d.ts.map +1 -0
- package/dist/task/acceptance.js +228 -0
- package/dist/task/acceptance.js.map +1 -0
- package/dist/task/agent-coordinator.d.ts +40 -0
- package/dist/task/agent-coordinator.d.ts.map +1 -0
- package/dist/task/agent-coordinator.js +168 -0
- package/dist/task/agent-coordinator.js.map +1 -0
- package/dist/task/executor.d.ts +37 -0
- package/dist/task/executor.d.ts.map +1 -0
- package/dist/task/executor.js +462 -0
- package/dist/task/executor.js.map +1 -0
- package/dist/task/index.d.ts +12 -0
- package/dist/task/index.d.ts.map +1 -0
- package/dist/task/index.js +12 -0
- package/dist/task/index.js.map +1 -0
- package/dist/task/planner.d.ts +21 -0
- package/dist/task/planner.d.ts.map +1 -0
- package/dist/task/planner.js +253 -0
- package/dist/task/planner.js.map +1 -0
- package/dist/task/storage.d.ts +46 -0
- package/dist/task/storage.d.ts.map +1 -0
- package/dist/task/storage.js +266 -0
- package/dist/task/storage.js.map +1 -0
- package/dist/trace/trace-event.d.ts +2 -18
- package/dist/trace/trace-event.d.ts.map +1 -1
- package/dist/trace/trace-event.js +6 -6
- package/dist/trace/trace-event.js.map +1 -1
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +54 -15
- package/dist/utils/file-utils.js.map +1 -1
- package/docs/PHASE5_IMPLEMENTATION.md +237 -0
- package/docs/PHASES-3-7-COMPLETE.md +177 -0
- package/docs/PHASE_4_COMPLETE.md +135 -0
- package/docs/PHASE_7_DELIVERABLES.md +295 -0
- package/docs/PHASE_7_IMPLEMENTATION.md +306 -0
- package/docs/PHASE_7_SUMMARY.txt +195 -0
- package/docs/RELEASE-NOTES-v2.1.md +213 -0
- package/docs/ROADMAP.md +194 -107
- package/docs/SECURITY-AUDIT.md +387 -0
- package/docs/SNAPSHOT.md +59 -32
- package/docs/implementation/phase3-summary.md +220 -0
- package/package.json +27 -11
- package/src/agent/task-worker.ts +196 -0
- package/src/agent/worker.ts +111 -0
- package/src/bin.ts +13 -0
- package/src/cli/cost.ts +210 -0
- package/src/cli/gc.ts +138 -0
- package/src/cli/gradients.ts +97 -0
- package/src/cli/grow.ts +416 -0
- package/src/cli/index.ts +81 -0
- package/src/cli/init.ts +139 -0
- package/src/cli/status.ts +218 -0
- package/src/coordination/file-locks.ts +300 -0
- package/src/coordination/index.ts +4 -0
- package/src/coordination/inhibitors.ts +345 -0
- package/src/coordination/process-manager.ts +199 -0
- package/src/core/agent-executor.ts +37 -8
- package/src/core/signals/churn.ts +8 -5
- package/src/core/signals/debt.ts +4 -3
- package/src/cost/cost-tracker.ts +2 -0
- package/src/gc/index.ts +17 -0
- package/src/gc/runner.ts +314 -0
- package/src/gc/trace-compactor.ts +187 -0
- package/src/index.ts +7 -1
- package/src/prompts/index.ts +2 -1
- package/src/quarantine/explorer.ts +234 -0
- package/src/quarantine/index.ts +7 -0
- package/src/quarantine/manager.ts +336 -0
- package/src/task/acceptance.ts +267 -0
- package/src/task/agent-coordinator.ts +220 -0
- package/src/task/executor.ts +543 -0
- package/src/task/index.ts +38 -0
- package/src/task/planner.ts +294 -0
- package/src/task/storage.ts +332 -0
- package/src/trace/trace-event.ts +7 -26
- package/src/utils/file-utils.ts +61 -15
- package/tests/cli/gc.test.ts +206 -0
- package/tests/cli/init.test.ts +181 -0
- package/tests/cli/status.test.ts +282 -0
- package/tests/coordination/file-locks.test.ts +196 -0
- package/tests/coordination/inhibitors.test.ts +459 -0
- package/tests/coordination/integration.test.ts +195 -0
- package/tests/coordination/process-manager.test.ts +165 -0
- package/tests/gc/trace-compactor.test.ts +245 -0
- package/tests/integration/phase-7.test.ts +145 -0
- package/tests/quarantine/explorer.test.ts +381 -0
- package/tests/quarantine/manager.test.ts +399 -0
- package/tests/security/command-injection.test.ts +88 -0
- package/tests/security/path-traversal.test.ts +103 -0
- package/tests/task/acceptance.test.ts +411 -0
- package/tests/task/executor.test.ts +421 -0
- package/tests/task/planner.test.ts +359 -0
- package/tests/trace/trace-event.test.ts +62 -20
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for process manager
|
|
3
|
+
* Tests agent spawning, IPC communication, and lifecycle management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
spawnAgent,
|
|
9
|
+
killAgent,
|
|
10
|
+
listAgents,
|
|
11
|
+
isAgentRunning,
|
|
12
|
+
getAgentInfo,
|
|
13
|
+
killAllAgents,
|
|
14
|
+
waitForAllAgents,
|
|
15
|
+
} from '../../src/coordination/process-manager.js';
|
|
16
|
+
|
|
17
|
+
describe('Process Manager', () => {
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
// Clean up all agents after each test
|
|
20
|
+
killAllAgents();
|
|
21
|
+
// Give processes time to exit
|
|
22
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should spawn an agent successfully', () => {
|
|
26
|
+
const child = spawnAgent({
|
|
27
|
+
agentId: 'test-agent-1',
|
|
28
|
+
maxIterations: 5,
|
|
29
|
+
timeout: 10000,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(child).toBeDefined();
|
|
33
|
+
expect(child.pid).toBeDefined();
|
|
34
|
+
expect(isAgentRunning('test-agent-1')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should track spawned agents', () => {
|
|
38
|
+
spawnAgent({ agentId: 'agent-1' });
|
|
39
|
+
spawnAgent({ agentId: 'agent-2' });
|
|
40
|
+
spawnAgent({ agentId: 'agent-3' });
|
|
41
|
+
|
|
42
|
+
const agents = listAgents();
|
|
43
|
+
expect(agents).toHaveLength(3);
|
|
44
|
+
|
|
45
|
+
const agentIds = agents.map((a) => a.agentId).sort();
|
|
46
|
+
expect(agentIds).toEqual(['agent-1', 'agent-2', 'agent-3']);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should kill an agent', async () => {
|
|
50
|
+
spawnAgent({ agentId: 'test-agent' });
|
|
51
|
+
expect(isAgentRunning('test-agent')).toBe(true);
|
|
52
|
+
|
|
53
|
+
const killed = killAgent('test-agent');
|
|
54
|
+
expect(killed).toBe(true);
|
|
55
|
+
|
|
56
|
+
// Wait a bit for process to exit
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
58
|
+
expect(isAgentRunning('test-agent')).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return false when killing non-existent agent', () => {
|
|
62
|
+
const killed = killAgent('non-existent-agent');
|
|
63
|
+
expect(killed).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should get agent info', () => {
|
|
67
|
+
spawnAgent({ agentId: 'test-agent' });
|
|
68
|
+
|
|
69
|
+
const info = getAgentInfo('test-agent');
|
|
70
|
+
expect(info).not.toBeNull();
|
|
71
|
+
expect(info?.agentId).toBe('test-agent');
|
|
72
|
+
expect(info?.pid).toBeDefined();
|
|
73
|
+
expect(info?.startedAt).toBeInstanceOf(Date);
|
|
74
|
+
expect(info?.uptime).toBeGreaterThanOrEqual(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return null for non-existent agent info', () => {
|
|
78
|
+
const info = getAgentInfo('non-existent');
|
|
79
|
+
expect(info).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should kill all agents', async () => {
|
|
83
|
+
spawnAgent({ agentId: 'agent-1' });
|
|
84
|
+
spawnAgent({ agentId: 'agent-2' });
|
|
85
|
+
spawnAgent({ agentId: 'agent-3' });
|
|
86
|
+
|
|
87
|
+
expect(listAgents()).toHaveLength(3);
|
|
88
|
+
|
|
89
|
+
killAllAgents();
|
|
90
|
+
|
|
91
|
+
// Wait for processes to exit
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
93
|
+
|
|
94
|
+
expect(listAgents()).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle agent exit event', async () => {
|
|
98
|
+
const child = spawnAgent({
|
|
99
|
+
agentId: 'test-agent',
|
|
100
|
+
maxIterations: 1,
|
|
101
|
+
timeout: 5000,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(isAgentRunning('test-agent')).toBe(true);
|
|
105
|
+
|
|
106
|
+
// Kill the agent and wait for exit event
|
|
107
|
+
await new Promise<void>((resolve) => {
|
|
108
|
+
child.once('exit', () => {
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
child.kill('SIGTERM');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Should be removed from active agents
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
116
|
+
expect(isAgentRunning('test-agent')).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should wait for all agents to complete', async () => {
|
|
120
|
+
spawnAgent({ agentId: 'agent-1', timeout: 1000 });
|
|
121
|
+
spawnAgent({ agentId: 'agent-2', timeout: 1000 });
|
|
122
|
+
|
|
123
|
+
// Kill them all
|
|
124
|
+
killAllAgents();
|
|
125
|
+
|
|
126
|
+
// This should resolve when all agents have exited
|
|
127
|
+
await waitForAllAgents();
|
|
128
|
+
|
|
129
|
+
// All agents should be gone
|
|
130
|
+
expect(listAgents()).toHaveLength(0);
|
|
131
|
+
}, 10000);
|
|
132
|
+
|
|
133
|
+
it('should pass environment variables to agent', () => {
|
|
134
|
+
const child = spawnAgent({
|
|
135
|
+
agentId: 'env-test-agent',
|
|
136
|
+
maxIterations: 42,
|
|
137
|
+
timeout: 5000,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// We can't directly access child env, but we can verify spawn worked
|
|
141
|
+
expect(child.pid).toBeDefined();
|
|
142
|
+
expect(isAgentRunning('env-test-agent')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should track agent uptime', async () => {
|
|
146
|
+
spawnAgent({ agentId: 'uptime-test' });
|
|
147
|
+
|
|
148
|
+
// Wait a bit
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
150
|
+
|
|
151
|
+
const info = getAgentInfo('uptime-test');
|
|
152
|
+
expect(info?.uptime).toBeGreaterThan(50);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should handle IPC communication setup', () => {
|
|
156
|
+
const child = spawnAgent({
|
|
157
|
+
agentId: 'ipc-test',
|
|
158
|
+
maxIterations: 5,
|
|
159
|
+
timeout: 5000,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Verify IPC channel is set up (stdio[3])
|
|
163
|
+
expect(child.channel).toBeDefined();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace Compactor Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for trace compaction functionality
|
|
5
|
+
* - Verify last 10 samples + 7 day retention
|
|
6
|
+
* - Test summarization of old traces
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { compactTraces, keepRecent } from '../../src/gc/trace-compactor.js';
|
|
11
|
+
import type { TraceEvent, Mode } from '../../src/types/index.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a test trace event
|
|
15
|
+
*/
|
|
16
|
+
function createTestEvent(
|
|
17
|
+
filePath: string,
|
|
18
|
+
mode: Mode,
|
|
19
|
+
daysAgo: number,
|
|
20
|
+
efficiency: number = 0.5
|
|
21
|
+
): TraceEvent {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const date = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
id: `event-${Date.now()}-${Math.random()}`,
|
|
27
|
+
timestamp: date.toISOString(),
|
|
28
|
+
file_path: filePath,
|
|
29
|
+
mode,
|
|
30
|
+
gradient_before: 0.5,
|
|
31
|
+
gradient_after: 0.6,
|
|
32
|
+
gradient_delta: 0.1,
|
|
33
|
+
metabolic_cost: 0.01,
|
|
34
|
+
efficiency,
|
|
35
|
+
ci_passed: true,
|
|
36
|
+
changes: {
|
|
37
|
+
additions: 10,
|
|
38
|
+
deletions: 5,
|
|
39
|
+
files_touched: [filePath],
|
|
40
|
+
},
|
|
41
|
+
notes: ['Test event'],
|
|
42
|
+
cost: {
|
|
43
|
+
tokens_in: 100,
|
|
44
|
+
tokens_out: 50,
|
|
45
|
+
model: 'claude-3-sonnet',
|
|
46
|
+
estimated_usd: 0.01,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('Trace Compactor', () => {
|
|
52
|
+
describe('compactTraces', () => {
|
|
53
|
+
it('should keep all events if less than 10', () => {
|
|
54
|
+
const events = [
|
|
55
|
+
createTestEvent('file1.ts', 'error_reducer', 1),
|
|
56
|
+
createTestEvent('file1.ts', 'error_reducer', 2),
|
|
57
|
+
createTestEvent('file1.ts', 'error_reducer', 3),
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const compacted = compactTraces(events);
|
|
61
|
+
|
|
62
|
+
expect(compacted).toHaveLength(3);
|
|
63
|
+
expect(compacted.every(e => !(e as any).__summary__)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should keep last 10 events verbatim', () => {
|
|
67
|
+
const events = [];
|
|
68
|
+
// Create 20 events where 15 are old (>7 days) and 5 are recent
|
|
69
|
+
for (let i = 0; i < 15; i++) {
|
|
70
|
+
events.push(createTestEvent('file1.ts', 'error_reducer', i + 10)); // 10+ days old
|
|
71
|
+
}
|
|
72
|
+
for (let i = 0; i < 5; i++) {
|
|
73
|
+
events.push(createTestEvent('file1.ts', 'error_reducer', i)); // Recent (< 7 days)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const compacted = compactTraces(events);
|
|
77
|
+
|
|
78
|
+
// Last 10 should be verbatim (not summaries) - recent events only
|
|
79
|
+
const verbatim = compacted.filter(e => !(e as any).__summary__);
|
|
80
|
+
expect(verbatim.length).toBeGreaterThanOrEqual(5);
|
|
81
|
+
expect(compacted.some(e => (e as any).__summary__)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should summarize events older than 7 days', () => {
|
|
85
|
+
const events = [];
|
|
86
|
+
|
|
87
|
+
// Add 15 old events (>7 days)
|
|
88
|
+
for (let i = 8; i < 23; i++) {
|
|
89
|
+
events.push(createTestEvent('file1.ts', 'error_reducer', i, 0.4));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add 5 recent events (<7 days)
|
|
93
|
+
for (let i = 1; i < 6; i++) {
|
|
94
|
+
events.push(createTestEvent('file1.ts', 'error_reducer', i, 0.6));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const compacted = compactTraces(events);
|
|
98
|
+
|
|
99
|
+
// Should have summaries for old events
|
|
100
|
+
const summaries = compacted.filter(e => (e as any).__summary__);
|
|
101
|
+
expect(summaries.length).toBeGreaterThan(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should group summaries by mode', () => {
|
|
105
|
+
const events = [];
|
|
106
|
+
|
|
107
|
+
// Add old events with different modes
|
|
108
|
+
for (let i = 8; i < 13; i++) {
|
|
109
|
+
events.push(
|
|
110
|
+
createTestEvent('file1.ts', 'error_reducer', i, 0.4)
|
|
111
|
+
);
|
|
112
|
+
events.push(
|
|
113
|
+
createTestEvent('file2.ts', 'complexity_reducer', i, 0.5)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const compacted = compactTraces(events);
|
|
118
|
+
const summaries = compacted.filter(e => (e as any).__summary__);
|
|
119
|
+
|
|
120
|
+
// Should have at least 2 summaries (one per mode)
|
|
121
|
+
const modes = new Set(summaries.map(s => s.mode));
|
|
122
|
+
expect(modes.size).toBeGreaterThanOrEqual(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should aggregate metrics in summaries', () => {
|
|
126
|
+
const events = [
|
|
127
|
+
createTestEvent('file1.ts', 'error_reducer', 10, 0.4),
|
|
128
|
+
createTestEvent('file1.ts', 'error_reducer', 11, 0.6),
|
|
129
|
+
createTestEvent('file1.ts', 'error_reducer', 12, 0.8),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const compacted = compactTraces(events);
|
|
133
|
+
const summaries = compacted.filter(e => (e as any).__summary__);
|
|
134
|
+
|
|
135
|
+
expect(summaries.length).toBeGreaterThan(0);
|
|
136
|
+
|
|
137
|
+
const summary = summaries[0];
|
|
138
|
+
expect(summary.efficiency).toBeCloseTo(0.6, 1); // Average of 0.4, 0.6, 0.8
|
|
139
|
+
expect(summary.cost.tokens_in).toBe(300); // 3 * 100
|
|
140
|
+
expect(summary.cost.tokens_out).toBe(150); // 3 * 50
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should include notes in summaries', () => {
|
|
144
|
+
const events = [];
|
|
145
|
+
for (let i = 8; i < 13; i++) {
|
|
146
|
+
events.push(createTestEvent('file1.ts', 'error_reducer', i, 0.5));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const compacted = compactTraces(events);
|
|
150
|
+
const summaries = compacted.filter(e => (e as any).__summary__);
|
|
151
|
+
|
|
152
|
+
expect(summaries.length).toBeGreaterThan(0);
|
|
153
|
+
expect(summaries[0].notes.length).toBeGreaterThan(0);
|
|
154
|
+
expect(summaries[0].notes[0]).toContain('Compacted');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should mark summaries with __summary__ flag', () => {
|
|
158
|
+
const events = [];
|
|
159
|
+
for (let i = 8; i < 13; i++) {
|
|
160
|
+
events.push(createTestEvent('file1.ts', 'error_reducer', i, 0.5));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const compacted = compactTraces(events);
|
|
164
|
+
const summaries = compacted.filter(e => (e as any).__summary__);
|
|
165
|
+
|
|
166
|
+
expect(summaries.length).toBeGreaterThan(0);
|
|
167
|
+
summaries.forEach(s => {
|
|
168
|
+
expect((s as any).__summary__).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should handle empty array', () => {
|
|
173
|
+
const compacted = compactTraces([]);
|
|
174
|
+
expect(compacted).toEqual([]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should preserve cost model information', () => {
|
|
178
|
+
const events = [
|
|
179
|
+
createTestEvent('file1.ts', 'error_reducer', 10, 0.5),
|
|
180
|
+
createTestEvent('file1.ts', 'error_reducer', 11, 0.5),
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const compacted = compactTraces(events);
|
|
184
|
+
const summaries = compacted.filter(e => (e as any).__summary__);
|
|
185
|
+
|
|
186
|
+
expect(summaries.length).toBeGreaterThan(0);
|
|
187
|
+
expect(summaries[0].cost.model).toBe('claude-3-sonnet');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('keepRecent', () => {
|
|
192
|
+
it('should keep events from last N days', () => {
|
|
193
|
+
const events = [
|
|
194
|
+
createTestEvent('file1.ts', 'error_reducer', 1),
|
|
195
|
+
createTestEvent('file1.ts', 'error_reducer', 3),
|
|
196
|
+
createTestEvent('file1.ts', 'error_reducer', 5),
|
|
197
|
+
createTestEvent('file1.ts', 'error_reducer', 10),
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const recent = keepRecent(events, 7);
|
|
201
|
+
|
|
202
|
+
expect(recent).toHaveLength(3);
|
|
203
|
+
expect(recent.every(e => {
|
|
204
|
+
const ageMs = Date.now() - new Date(e.timestamp).getTime();
|
|
205
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
206
|
+
return ageDays < 7;
|
|
207
|
+
})).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle custom retention period', () => {
|
|
211
|
+
const events = [
|
|
212
|
+
createTestEvent('file1.ts', 'error_reducer', 1),
|
|
213
|
+
createTestEvent('file1.ts', 'error_reducer', 3),
|
|
214
|
+
createTestEvent('file1.ts', 'error_reducer', 15),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const recent = keepRecent(events, 10);
|
|
218
|
+
|
|
219
|
+
expect(recent).toHaveLength(2);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should return empty array if all events are old', () => {
|
|
223
|
+
const events = [
|
|
224
|
+
createTestEvent('file1.ts', 'error_reducer', 30),
|
|
225
|
+
createTestEvent('file1.ts', 'error_reducer', 40),
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const recent = keepRecent(events, 7);
|
|
229
|
+
|
|
230
|
+
expect(recent).toHaveLength(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return all events if all are recent', () => {
|
|
234
|
+
const events = [
|
|
235
|
+
createTestEvent('file1.ts', 'error_reducer', 0),
|
|
236
|
+
createTestEvent('file1.ts', 'error_reducer', 1),
|
|
237
|
+
createTestEvent('file1.ts', 'error_reducer', 2),
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
const recent = keepRecent(events, 7);
|
|
241
|
+
|
|
242
|
+
expect(recent).toHaveLength(3);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 7 Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Verify that GC + CLI implementation is complete and working
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
describe('Phase 7: GC + CLI', () => {
|
|
10
|
+
describe('GC Module', () => {
|
|
11
|
+
it('should export trace compactor', async () => {
|
|
12
|
+
const { compactTraces, keepRecent, summarizeOld } = await import(
|
|
13
|
+
'../../src/gc/trace-compactor.js'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
expect(typeof compactTraces).toBe('function');
|
|
17
|
+
expect(typeof keepRecent).toBe('function');
|
|
18
|
+
expect(typeof summarizeOld).toBe('function');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should export GC runner', async () => {
|
|
22
|
+
const { runGC, readGCLog } = await import('../../src/gc/runner.js');
|
|
23
|
+
|
|
24
|
+
expect(typeof runGC).toBe('function');
|
|
25
|
+
expect(typeof readGCLog).toBe('function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should have GC config', async () => {
|
|
29
|
+
const { GC_CONFIG } = await import('../../src/gc/runner.js');
|
|
30
|
+
|
|
31
|
+
expect(GC_CONFIG.lowConfidenceRetentionDays).toBe(7);
|
|
32
|
+
expect(GC_CONFIG.traceRetentionDays).toBe(30);
|
|
33
|
+
expect(GC_CONFIG.compactionThreshold).toBe(50);
|
|
34
|
+
expect(GC_CONFIG.inhibitorStrengthThreshold).toBe(0.05);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('CLI Module', () => {
|
|
39
|
+
it('should export CLI program', async () => {
|
|
40
|
+
const { program } = await import('../../src/cli/index.js');
|
|
41
|
+
|
|
42
|
+
expect(program).toBeDefined();
|
|
43
|
+
expect(program.name()).toBe('claude-mycelium');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should have version', async () => {
|
|
47
|
+
const { program } = await import('../../src/cli/index.js');
|
|
48
|
+
|
|
49
|
+
expect(program.version()).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should export init command', async () => {
|
|
53
|
+
const initCommand = await import('../../src/cli/init.js');
|
|
54
|
+
|
|
55
|
+
expect(initCommand.default).toBeDefined();
|
|
56
|
+
expect(initCommand.default.name()).toBe('init');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should export gc command', async () => {
|
|
60
|
+
const gcCommand = await import('../../src/cli/gc.js');
|
|
61
|
+
|
|
62
|
+
expect(gcCommand.default).toBeDefined();
|
|
63
|
+
expect(gcCommand.default.name()).toBe('gc');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should export gradients command', async () => {
|
|
67
|
+
const gradientsCommand = await import('../../src/cli/gradients.js');
|
|
68
|
+
|
|
69
|
+
expect(gradientsCommand.default).toBeDefined();
|
|
70
|
+
expect(gradientsCommand.default.name()).toBe('gradients');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should export cost command', async () => {
|
|
74
|
+
const costCommand = await import('../../src/cli/cost.js');
|
|
75
|
+
|
|
76
|
+
expect(costCommand.default).toBeDefined();
|
|
77
|
+
expect(costCommand.default.name()).toBe('cost');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should export status command', async () => {
|
|
81
|
+
const statusCommand = await import('../../src/cli/status.js');
|
|
82
|
+
|
|
83
|
+
expect(statusCommand.default).toBeDefined();
|
|
84
|
+
expect(statusCommand.default.name()).toBe('status');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Spec Compliance', () => {
|
|
89
|
+
it('should keep last 10 samples per file', async () => {
|
|
90
|
+
const { GC_CONFIG } = await import('../../src/gc/runner.js');
|
|
91
|
+
|
|
92
|
+
// The compaction threshold determines when to compact (>50 events)
|
|
93
|
+
// and we keep the last 10 verbatim
|
|
94
|
+
expect(GC_CONFIG.compactionThreshold).toBeGreaterThan(10);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should keep all samples from last 7 days', async () => {
|
|
98
|
+
const { GC_CONFIG } = await import('../../src/gc/runner.js');
|
|
99
|
+
|
|
100
|
+
expect(GC_CONFIG.lowConfidenceRetentionDays).toBe(7);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should delete weak inhibitors (strength <0.05)', async () => {
|
|
104
|
+
const { GC_CONFIG } = await import('../../src/gc/runner.js');
|
|
105
|
+
|
|
106
|
+
expect(GC_CONFIG.inhibitorStrengthThreshold).toBe(0.05);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should run GC every 100 spawns', async () => {
|
|
110
|
+
const { incrementSpawnCount, loadConfig } = await import(
|
|
111
|
+
'../../src/utils/config.js'
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// The config tracks last_gc_at_spawn
|
|
115
|
+
const config = loadConfig();
|
|
116
|
+
expect(config).toHaveProperty('last_gc_at_spawn');
|
|
117
|
+
expect(config).toHaveProperty('spawn_count');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should initialize .agent-meta/ with required files', async () => {
|
|
121
|
+
// The init command creates these files (not testing file system here)
|
|
122
|
+
const initCommand = await import('../../src/cli/init.js');
|
|
123
|
+
|
|
124
|
+
expect(initCommand.default).toBeDefined();
|
|
125
|
+
expect(initCommand.default.name()).toBe('init');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should use Commander.js for CLI', async () => {
|
|
129
|
+
const { program } = await import('../../src/cli/index.js');
|
|
130
|
+
const { Command } = await import('commander');
|
|
131
|
+
|
|
132
|
+
expect(program instanceof Command).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('Type Exports', () => {
|
|
137
|
+
it('should export GCReport type', async () => {
|
|
138
|
+
const types = await import('../../src/types/index.js');
|
|
139
|
+
|
|
140
|
+
expect(types).toHaveProperty('TraceEvent');
|
|
141
|
+
expect(types).toHaveProperty('GCReport');
|
|
142
|
+
expect(types).toHaveProperty('Inhibitor');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|