anvil-dev-framework 0.1.6
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/README.md +719 -0
- package/VERSION +1 -0
- package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
- package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
- package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
- package/docs/INSTALLATION.md +984 -0
- package/docs/anvil-hud.md +469 -0
- package/docs/anvil-init.md +255 -0
- package/docs/anvil-state.md +210 -0
- package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
- package/docs/command-reference.md +2022 -0
- package/docs/hooks-tts.md +368 -0
- package/docs/implementation-guide.md +810 -0
- package/docs/linear-github-integration.md +247 -0
- package/docs/local-issues.md +677 -0
- package/docs/patterns/README.md +419 -0
- package/docs/planning-responsibilities.md +139 -0
- package/docs/session-workflow.md +573 -0
- package/docs/simplification-plan-template.md +297 -0
- package/docs/simplification-principles.md +129 -0
- package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
- package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
- package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
- package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
- package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
- package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
- package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
- package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
- package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
- package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
- package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
- package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
- package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
- package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
- package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
- package/docs/sync.md +122 -0
- package/global/CLAUDE.md +140 -0
- package/global/agents/verify-app.md +164 -0
- package/global/commands/anvil-settings.md +527 -0
- package/global/commands/anvil-sync.md +121 -0
- package/global/commands/change.md +197 -0
- package/global/commands/clarify.md +252 -0
- package/global/commands/cleanup.md +292 -0
- package/global/commands/commit-push-pr.md +207 -0
- package/global/commands/decay-review.md +127 -0
- package/global/commands/discover.md +158 -0
- package/global/commands/doc-coverage.md +122 -0
- package/global/commands/evidence.md +307 -0
- package/global/commands/explore.md +121 -0
- package/global/commands/force-exit.md +135 -0
- package/global/commands/handoff.md +191 -0
- package/global/commands/healthcheck.md +302 -0
- package/global/commands/hud.md +84 -0
- package/global/commands/insights.md +319 -0
- package/global/commands/linear-setup.md +184 -0
- package/global/commands/lint-fix.md +198 -0
- package/global/commands/orient.md +510 -0
- package/global/commands/plan.md +228 -0
- package/global/commands/ralph.md +346 -0
- package/global/commands/ready.md +182 -0
- package/global/commands/release.md +305 -0
- package/global/commands/retro.md +96 -0
- package/global/commands/shard.md +166 -0
- package/global/commands/spec.md +227 -0
- package/global/commands/sprint.md +184 -0
- package/global/commands/tasks.md +228 -0
- package/global/commands/test-and-commit.md +151 -0
- package/global/commands/validate.md +132 -0
- package/global/commands/verify.md +251 -0
- package/global/commands/weekly-review.md +156 -0
- package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
- package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
- package/global/hooks/anvil_memory_observe.ts +322 -0
- package/global/hooks/anvil_memory_session.ts +166 -0
- package/global/hooks/anvil_memory_stop.ts +187 -0
- package/global/hooks/parse_transcript.py +116 -0
- package/global/hooks/post_merge_cleanup.sh +132 -0
- package/global/hooks/post_tool_format.sh +215 -0
- package/global/hooks/ralph_context_monitor.py +240 -0
- package/global/hooks/ralph_stop.sh +502 -0
- package/global/hooks/statusline.sh +1110 -0
- package/global/hooks/statusline_agent_sync.py +224 -0
- package/global/hooks/stop_gate.sh +250 -0
- package/global/lib/.claude/anvil-state.json +21 -0
- package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
- package/global/lib/agent_registry.py +995 -0
- package/global/lib/anvil-state.sh +435 -0
- package/global/lib/claim_service.py +515 -0
- package/global/lib/coderabbit_service.py +314 -0
- package/global/lib/config_service.py +423 -0
- package/global/lib/coordination_service.py +331 -0
- package/global/lib/doc_coverage_service.py +1305 -0
- package/global/lib/gate_logger.py +316 -0
- package/global/lib/github_service.py +310 -0
- package/global/lib/handoff_generator.py +775 -0
- package/global/lib/hygiene_service.py +712 -0
- package/global/lib/issue_models.py +257 -0
- package/global/lib/issue_provider.py +339 -0
- package/global/lib/linear_data_service.py +210 -0
- package/global/lib/linear_provider.py +987 -0
- package/global/lib/linear_provider.py.backup +671 -0
- package/global/lib/local_provider.py +486 -0
- package/global/lib/orient_fast.py +457 -0
- package/global/lib/quality_service.py +470 -0
- package/global/lib/ralph_prompt_generator.py +563 -0
- package/global/lib/ralph_state.py +1202 -0
- package/global/lib/state_manager.py +417 -0
- package/global/lib/transcript_parser.py +597 -0
- package/global/lib/verification_runner.py +557 -0
- package/global/lib/verify_iteration.py +490 -0
- package/global/lib/verify_subagent.py +250 -0
- package/global/skills/README.md +155 -0
- package/global/skills/quality-gates/SKILL.md +252 -0
- package/global/skills/skill-template/SKILL.md +109 -0
- package/global/skills/testing-strategies/SKILL.md +337 -0
- package/global/templates/CHANGE-template.md +105 -0
- package/global/templates/HANDOFF-template.md +63 -0
- package/global/templates/PLAN-template.md +111 -0
- package/global/templates/SPEC-template.md +93 -0
- package/global/templates/ralph/PROMPT.md.template +89 -0
- package/global/templates/ralph/fix_plan.md.template +31 -0
- package/global/templates/ralph/progress.txt.template +23 -0
- package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
- package/global/tests/test_doc_coverage.py +520 -0
- package/global/tests/test_issue_models.py +299 -0
- package/global/tests/test_local_provider.py +323 -0
- package/global/tools/README.md +178 -0
- package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
- package/global/tools/anvil-hud.py +3622 -0
- package/global/tools/anvil-hud.py.bak +3318 -0
- package/global/tools/anvil-issue.py +432 -0
- package/global/tools/anvil-memory/CLAUDE.md +49 -0
- package/global/tools/anvil-memory/README.md +42 -0
- package/global/tools/anvil-memory/bun.lock +25 -0
- package/global/tools/anvil-memory/bunfig.toml +9 -0
- package/global/tools/anvil-memory/package.json +23 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
- package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
- package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
- package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
- package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
- package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
- package/global/tools/anvil-memory/src/commands/get.ts +115 -0
- package/global/tools/anvil-memory/src/commands/init.ts +94 -0
- package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
- package/global/tools/anvil-memory/src/commands/search.ts +112 -0
- package/global/tools/anvil-memory/src/db.ts +638 -0
- package/global/tools/anvil-memory/src/index.ts +205 -0
- package/global/tools/anvil-memory/src/types.ts +122 -0
- package/global/tools/anvil-memory/tsconfig.json +29 -0
- package/global/tools/ralph-loop.sh +359 -0
- package/package.json +45 -0
- package/scripts/anvil +822 -0
- package/scripts/extract_patterns.py +222 -0
- package/scripts/init-project.sh +541 -0
- package/scripts/install.sh +229 -0
- package/scripts/postinstall.js +41 -0
- package/scripts/rollback.sh +188 -0
- package/scripts/sync.sh +623 -0
- package/scripts/test-statusline.sh +248 -0
- package/scripts/update_claude_md.py +224 -0
- package/scripts/verify.sh +255 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CCS Edge Cases and Performance Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for boundary conditions, edge cases, and performance benchmarks
|
|
5
|
+
* for the Context Checkpoint System.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
9
|
+
import {
|
|
10
|
+
TestStateManager,
|
|
11
|
+
runContextMonitor,
|
|
12
|
+
runRalphStop,
|
|
13
|
+
runPythonHook,
|
|
14
|
+
isJqAvailable,
|
|
15
|
+
isPythonAvailable,
|
|
16
|
+
LIB_DIR,
|
|
17
|
+
} from './test-utils';
|
|
18
|
+
import {
|
|
19
|
+
createMinimalRalphState,
|
|
20
|
+
createFullRalphState,
|
|
21
|
+
createContextInput,
|
|
22
|
+
} from './fixtures';
|
|
23
|
+
import type { RalphState } from './fixtures';
|
|
24
|
+
import { join } from 'path';
|
|
25
|
+
|
|
26
|
+
const PROMPT_GENERATOR_PATH = join(LIB_DIR, 'ralph_prompt_generator.py');
|
|
27
|
+
|
|
28
|
+
describe('CCS Edge Cases', () => {
|
|
29
|
+
let stateManager: TestStateManager;
|
|
30
|
+
let pythonAvailable: boolean;
|
|
31
|
+
let jqAvailable: boolean;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
stateManager = new TestStateManager('ccs-edge');
|
|
35
|
+
stateManager.init();
|
|
36
|
+
[pythonAvailable, jqAvailable] = await Promise.all([
|
|
37
|
+
isPythonAvailable(),
|
|
38
|
+
isJqAvailable(),
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
stateManager.cleanup();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Boundary Percentages', () => {
|
|
47
|
+
test('exactly 70% triggers L1 (boundary)', async () => {
|
|
48
|
+
if (!pythonAvailable) {
|
|
49
|
+
console.log('Skipping: Python not available');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
stateManager.writeState(createMinimalRalphState());
|
|
54
|
+
const input = createContextInput(70);
|
|
55
|
+
|
|
56
|
+
const result = await runContextMonitor(input, stateManager);
|
|
57
|
+
|
|
58
|
+
expect(result.exitCode).toBe(0);
|
|
59
|
+
expect(result.stdout).toContain('L1');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('exactly 85% triggers L2 (boundary)', async () => {
|
|
63
|
+
if (!pythonAvailable) {
|
|
64
|
+
console.log('Skipping: Python not available');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
stateManager.writeState(createMinimalRalphState());
|
|
69
|
+
const input = createContextInput(85);
|
|
70
|
+
|
|
71
|
+
const result = await runContextMonitor(input, stateManager);
|
|
72
|
+
|
|
73
|
+
expect(result.exitCode).toBe(0);
|
|
74
|
+
expect(result.stdout).toContain('L2');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('exactly 95% triggers L3 (boundary)', async () => {
|
|
78
|
+
if (!pythonAvailable) {
|
|
79
|
+
console.log('Skipping: Python not available');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
stateManager.writeState(createMinimalRalphState());
|
|
84
|
+
const input = createContextInput(95);
|
|
85
|
+
|
|
86
|
+
const result = await runContextMonitor(input, stateManager);
|
|
87
|
+
|
|
88
|
+
// L3 exits with code 1 for emergency
|
|
89
|
+
expect(result.exitCode).toBe(1);
|
|
90
|
+
expect(result.stdout).toContain('L3');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('69.9% stays in L0', async () => {
|
|
94
|
+
if (!pythonAvailable) {
|
|
95
|
+
console.log('Skipping: Python not available');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
stateManager.writeState(createMinimalRalphState());
|
|
100
|
+
const input = createContextInput(69.9);
|
|
101
|
+
|
|
102
|
+
const result = await runContextMonitor(input, stateManager);
|
|
103
|
+
|
|
104
|
+
expect(result.exitCode).toBe(0);
|
|
105
|
+
// L0 may be silent (no output needed) - just verify no L1/L2/L3
|
|
106
|
+
expect(result.stdout).not.toContain('L1');
|
|
107
|
+
expect(result.stdout).not.toContain('L2');
|
|
108
|
+
expect(result.stdout).not.toContain('L3');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('84.9% stays in L1', async () => {
|
|
112
|
+
if (!pythonAvailable) {
|
|
113
|
+
console.log('Skipping: Python not available');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
stateManager.writeState(createMinimalRalphState());
|
|
118
|
+
const input = createContextInput(84.9);
|
|
119
|
+
|
|
120
|
+
const result = await runContextMonitor(input, stateManager);
|
|
121
|
+
|
|
122
|
+
expect(result.exitCode).toBe(0);
|
|
123
|
+
expect(result.stdout).toContain('L1');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('94.9% stays in L2', async () => {
|
|
127
|
+
if (!pythonAvailable) {
|
|
128
|
+
console.log('Skipping: Python not available');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
stateManager.writeState(createMinimalRalphState());
|
|
133
|
+
const input = createContextInput(94.9);
|
|
134
|
+
|
|
135
|
+
const result = await runContextMonitor(input, stateManager);
|
|
136
|
+
|
|
137
|
+
expect(result.exitCode).toBe(0);
|
|
138
|
+
expect(result.stdout).toContain('L2');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('100% context is handled', async () => {
|
|
142
|
+
if (!pythonAvailable) {
|
|
143
|
+
console.log('Skipping: Python not available');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
stateManager.writeState(createMinimalRalphState());
|
|
148
|
+
const input = createContextInput(100);
|
|
149
|
+
|
|
150
|
+
const result = await runContextMonitor(input, stateManager);
|
|
151
|
+
|
|
152
|
+
// Should still process (L3)
|
|
153
|
+
expect(typeof result.exitCode).toBe('number');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('0% context is handled', async () => {
|
|
157
|
+
if (!pythonAvailable) {
|
|
158
|
+
console.log('Skipping: Python not available');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
stateManager.writeState(createMinimalRalphState());
|
|
163
|
+
const input = createContextInput(0);
|
|
164
|
+
|
|
165
|
+
const result = await runContextMonitor(input, stateManager);
|
|
166
|
+
|
|
167
|
+
expect(result.exitCode).toBe(0);
|
|
168
|
+
// L0 may be silent - just verify no warnings triggered
|
|
169
|
+
expect(result.stdout).not.toContain('L1');
|
|
170
|
+
expect(result.stdout).not.toContain('L2');
|
|
171
|
+
expect(result.stdout).not.toContain('L3');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('Empty and Minimal States', () => {
|
|
176
|
+
test('handles completely empty state file', async () => {
|
|
177
|
+
if (!pythonAvailable) {
|
|
178
|
+
console.log('Skipping: Python not available');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
stateManager.writeState({});
|
|
183
|
+
const input = createContextInput(50);
|
|
184
|
+
|
|
185
|
+
const result = await runContextMonitor(input, stateManager);
|
|
186
|
+
|
|
187
|
+
// Should not crash
|
|
188
|
+
expect(typeof result.exitCode).toBe('number');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('handles state with only iteration', async () => {
|
|
192
|
+
if (!pythonAvailable) {
|
|
193
|
+
console.log('Skipping: Python not available');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
stateManager.writeState({ iteration: 1 });
|
|
198
|
+
const input = createContextInput(50);
|
|
199
|
+
|
|
200
|
+
const result = await runContextMonitor(input, stateManager);
|
|
201
|
+
|
|
202
|
+
expect(result.exitCode).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('prompt generator handles empty checkpoint', async () => {
|
|
206
|
+
if (!pythonAvailable) {
|
|
207
|
+
console.log('Skipping: Python not available');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
stateManager.writeState({
|
|
212
|
+
context_checkpoint: {},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const result = await runPythonHook(
|
|
216
|
+
PROMPT_GENERATOR_PATH,
|
|
217
|
+
{},
|
|
218
|
+
{
|
|
219
|
+
cwd: stateManager.baseDir,
|
|
220
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Should not crash, generates with defaults
|
|
225
|
+
expect(result.exitCode).toBe(0);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('Large Data Sets', () => {
|
|
230
|
+
test('handles very long todo list (100 items)', async () => {
|
|
231
|
+
if (!pythonAvailable) {
|
|
232
|
+
console.log('Skipping: Python not available');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const manyTodos = Array.from({ length: 100 }, (_, i) => `Todo item ${i + 1}`);
|
|
237
|
+
const state = createFullRalphState({
|
|
238
|
+
todo_items: manyTodos,
|
|
239
|
+
});
|
|
240
|
+
stateManager.writeState(state);
|
|
241
|
+
|
|
242
|
+
const input = createContextInput(50);
|
|
243
|
+
const result = await runContextMonitor(input, stateManager);
|
|
244
|
+
|
|
245
|
+
expect(result.exitCode).toBe(0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('handles large context history (1000 entries)', async () => {
|
|
249
|
+
if (!pythonAvailable) {
|
|
250
|
+
console.log('Skipping: Python not available');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const largeHistory = Array.from({ length: 1000 }, (_, i) => ({
|
|
255
|
+
iteration: i + 1,
|
|
256
|
+
peak_percent: Math.random() * 100,
|
|
257
|
+
checkpoint: i % 50 === 0,
|
|
258
|
+
}));
|
|
259
|
+
|
|
260
|
+
const state = createFullRalphState({
|
|
261
|
+
context_history: largeHistory,
|
|
262
|
+
});
|
|
263
|
+
stateManager.writeState(state);
|
|
264
|
+
|
|
265
|
+
const input = createContextInput(50);
|
|
266
|
+
const result = await runContextMonitor(input, stateManager);
|
|
267
|
+
|
|
268
|
+
expect(result.exitCode).toBe(0);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('prompt generator handles many completed items', async () => {
|
|
272
|
+
if (!pythonAvailable) {
|
|
273
|
+
console.log('Skipping: Python not available');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const manyCompleted = Array.from({ length: 50 }, (_, i) => `Completed item ${i + 1}`);
|
|
278
|
+
const state = createFullRalphState({
|
|
279
|
+
completed_items: manyCompleted,
|
|
280
|
+
context_checkpoint: {
|
|
281
|
+
active: true,
|
|
282
|
+
level: 'L2',
|
|
283
|
+
percent_at_checkpoint: 87,
|
|
284
|
+
timestamp: new Date().toISOString(),
|
|
285
|
+
handoff_file: '.claude/handoffs/test.md',
|
|
286
|
+
resume_summary: '',
|
|
287
|
+
files_in_progress: [],
|
|
288
|
+
current_todo_item: 'Current',
|
|
289
|
+
progress_on_item: '',
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
stateManager.writeState(state);
|
|
293
|
+
|
|
294
|
+
const result = await runPythonHook(
|
|
295
|
+
PROMPT_GENERATOR_PATH,
|
|
296
|
+
{},
|
|
297
|
+
{
|
|
298
|
+
cwd: stateManager.baseDir,
|
|
299
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(result.exitCode).toBe(0);
|
|
304
|
+
// Should truncate and show "earlier items"
|
|
305
|
+
expect(result.stdout).toContain('earlier items');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('Special Characters', () => {
|
|
310
|
+
test('handles unicode in task name', async () => {
|
|
311
|
+
if (!pythonAvailable) {
|
|
312
|
+
console.log('Skipping: Python not available');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const state = createFullRalphState({
|
|
317
|
+
task_name: 'Implement 日本語 feature with émojis 🎉',
|
|
318
|
+
context_checkpoint: {
|
|
319
|
+
active: true,
|
|
320
|
+
level: 'L2',
|
|
321
|
+
percent_at_checkpoint: 87,
|
|
322
|
+
timestamp: new Date().toISOString(),
|
|
323
|
+
handoff_file: '.claude/handoffs/test.md',
|
|
324
|
+
resume_summary: '',
|
|
325
|
+
files_in_progress: [],
|
|
326
|
+
current_todo_item: 'Unicode task 中文',
|
|
327
|
+
progress_on_item: '',
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
stateManager.writeState(state);
|
|
331
|
+
|
|
332
|
+
const result = await runPythonHook(
|
|
333
|
+
PROMPT_GENERATOR_PATH,
|
|
334
|
+
{},
|
|
335
|
+
{
|
|
336
|
+
cwd: stateManager.baseDir,
|
|
337
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(result.exitCode).toBe(0);
|
|
342
|
+
expect(result.stdout).toContain('日本語');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('handles special chars in file paths', async () => {
|
|
346
|
+
if (!pythonAvailable) {
|
|
347
|
+
console.log('Skipping: Python not available');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const state = createFullRalphState({
|
|
352
|
+
context_checkpoint: {
|
|
353
|
+
active: true,
|
|
354
|
+
level: 'L2',
|
|
355
|
+
percent_at_checkpoint: 87,
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
handoff_file: '.claude/handoffs/test.md',
|
|
358
|
+
resume_summary: '',
|
|
359
|
+
files_in_progress: [
|
|
360
|
+
{ path: 'src/components/My Component.tsx', start_line: 1, end_line: 10 },
|
|
361
|
+
{ path: 'src/utils/helpers-v2.0.ts', start_line: 1, end_line: 20 },
|
|
362
|
+
],
|
|
363
|
+
current_todo_item: '',
|
|
364
|
+
progress_on_item: '',
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
stateManager.writeState(state);
|
|
368
|
+
|
|
369
|
+
const result = await runPythonHook(
|
|
370
|
+
PROMPT_GENERATOR_PATH,
|
|
371
|
+
{},
|
|
372
|
+
{
|
|
373
|
+
cwd: stateManager.baseDir,
|
|
374
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
expect(result.exitCode).toBe(0);
|
|
379
|
+
expect(result.stdout).toContain('My Component.tsx');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('handles quotes in todo items', async () => {
|
|
383
|
+
if (!pythonAvailable) {
|
|
384
|
+
console.log('Skipping: Python not available');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const state = createFullRalphState({
|
|
389
|
+
todo_items: [
|
|
390
|
+
'Fix "double quotes" issue',
|
|
391
|
+
"Handle 'single quotes' correctly",
|
|
392
|
+
'Mixed "quotes" and \'apostrophes\'',
|
|
393
|
+
],
|
|
394
|
+
context_checkpoint: {
|
|
395
|
+
active: true,
|
|
396
|
+
level: 'L2',
|
|
397
|
+
percent_at_checkpoint: 87,
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
399
|
+
handoff_file: '.claude/handoffs/test.md',
|
|
400
|
+
resume_summary: '',
|
|
401
|
+
files_in_progress: [],
|
|
402
|
+
current_todo_item: 'Fix "double quotes" issue',
|
|
403
|
+
progress_on_item: '',
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
stateManager.writeState(state);
|
|
407
|
+
|
|
408
|
+
const result = await runPythonHook(
|
|
409
|
+
PROMPT_GENERATOR_PATH,
|
|
410
|
+
{},
|
|
411
|
+
{
|
|
412
|
+
cwd: stateManager.baseDir,
|
|
413
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
expect(result.exitCode).toBe(0);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe('Max Iterations Edge Cases', () => {
|
|
422
|
+
test('handles iteration at exact max', async () => {
|
|
423
|
+
if (!jqAvailable) {
|
|
424
|
+
console.log('Skipping: jq not available');
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Iteration 49, max 50 - should exit at 50
|
|
429
|
+
stateManager.writeState(createMinimalRalphState({ iteration: 49 }));
|
|
430
|
+
|
|
431
|
+
const result = await runRalphStop(stateManager, { maxIterations: 50 });
|
|
432
|
+
|
|
433
|
+
expect(result.exitCode).toBe(0);
|
|
434
|
+
expect(result.stderr).toContain('Maximum');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('handles iteration one below max', async () => {
|
|
438
|
+
if (!jqAvailable) {
|
|
439
|
+
console.log('Skipping: jq not available');
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Iteration 48, max 50 - should continue
|
|
444
|
+
stateManager.writeState(createMinimalRalphState({ iteration: 48 }));
|
|
445
|
+
|
|
446
|
+
const result = await runRalphStop(stateManager, { maxIterations: 50 });
|
|
447
|
+
|
|
448
|
+
expect(result.exitCode).toBe(1); // Continue
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test('handles very high max iterations', async () => {
|
|
452
|
+
if (!jqAvailable) {
|
|
453
|
+
console.log('Skipping: jq not available');
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
stateManager.writeState(createMinimalRalphState({ iteration: 100 }));
|
|
458
|
+
|
|
459
|
+
const result = await runRalphStop(stateManager, { maxIterations: 1000 });
|
|
460
|
+
|
|
461
|
+
expect(result.exitCode).toBe(1); // Continue
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('handles max iterations = 1', async () => {
|
|
465
|
+
if (!jqAvailable) {
|
|
466
|
+
console.log('Skipping: jq not available');
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
stateManager.writeState(createMinimalRalphState({ iteration: 0 }));
|
|
471
|
+
|
|
472
|
+
const result = await runRalphStop(stateManager, { maxIterations: 1 });
|
|
473
|
+
|
|
474
|
+
expect(result.exitCode).toBe(0); // Max reached
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
describe('Invalid Input Handling', () => {
|
|
479
|
+
test('context monitor handles malformed JSON input', async () => {
|
|
480
|
+
if (!pythonAvailable) {
|
|
481
|
+
console.log('Skipping: Python not available');
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
stateManager.writeState(createMinimalRalphState());
|
|
486
|
+
|
|
487
|
+
// Send malformed input directly
|
|
488
|
+
const proc = Bun.spawn(['python3', join(stateManager.baseDir, '../../..', 'global/hooks/ralph_context_monitor.py')], {
|
|
489
|
+
stdin: new Blob(['not valid json']),
|
|
490
|
+
stdout: 'pipe',
|
|
491
|
+
stderr: 'pipe',
|
|
492
|
+
cwd: stateManager.baseDir,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await proc.exited;
|
|
496
|
+
|
|
497
|
+
// Should handle gracefully (might exit 0 or 1, but shouldn't crash)
|
|
498
|
+
expect(typeof proc.exitCode).toBe('number');
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('context monitor handles missing context_window field', async () => {
|
|
502
|
+
if (!pythonAvailable) {
|
|
503
|
+
console.log('Skipping: Python not available');
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
stateManager.writeState(createMinimalRalphState());
|
|
508
|
+
|
|
509
|
+
const proc = Bun.spawn(['python3', join(stateManager.baseDir, '../../..', 'global/hooks/ralph_context_monitor.py')], {
|
|
510
|
+
stdin: new Blob([JSON.stringify({ some_other_field: 123 })]),
|
|
511
|
+
stdout: 'pipe',
|
|
512
|
+
stderr: 'pipe',
|
|
513
|
+
cwd: stateManager.baseDir,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
await proc.exited;
|
|
517
|
+
|
|
518
|
+
// Should handle gracefully
|
|
519
|
+
expect(typeof proc.exitCode).toBe('number');
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
describe('CCS Performance', () => {
|
|
525
|
+
let stateManager: TestStateManager;
|
|
526
|
+
let pythonAvailable: boolean;
|
|
527
|
+
|
|
528
|
+
beforeEach(async () => {
|
|
529
|
+
stateManager = new TestStateManager('ccs-perf');
|
|
530
|
+
stateManager.init();
|
|
531
|
+
pythonAvailable = await isPythonAvailable();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
afterEach(() => {
|
|
535
|
+
stateManager.cleanup();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe('Response Time', () => {
|
|
539
|
+
test('context monitor responds within 500ms', async () => {
|
|
540
|
+
if (!pythonAvailable) {
|
|
541
|
+
console.log('Skipping: Python not available');
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
stateManager.writeState(createFullRalphState());
|
|
546
|
+
const input = createContextInput(50);
|
|
547
|
+
|
|
548
|
+
const start = performance.now();
|
|
549
|
+
await runContextMonitor(input, stateManager);
|
|
550
|
+
const elapsed = performance.now() - start;
|
|
551
|
+
|
|
552
|
+
expect(elapsed).toBeLessThan(500);
|
|
553
|
+
console.log(` Context monitor response: ${elapsed.toFixed(2)}ms`);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('prompt generator responds within 500ms', async () => {
|
|
557
|
+
if (!pythonAvailable) {
|
|
558
|
+
console.log('Skipping: Python not available');
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const state = createFullRalphState({
|
|
563
|
+
context_checkpoint: {
|
|
564
|
+
active: true,
|
|
565
|
+
level: 'L2',
|
|
566
|
+
percent_at_checkpoint: 87,
|
|
567
|
+
timestamp: new Date().toISOString(),
|
|
568
|
+
handoff_file: '.claude/handoffs/test.md',
|
|
569
|
+
resume_summary: '',
|
|
570
|
+
files_in_progress: [{ path: 'test.ts', start_line: 1, end_line: 100 }],
|
|
571
|
+
current_todo_item: 'Test',
|
|
572
|
+
progress_on_item: '',
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
stateManager.writeState(state);
|
|
576
|
+
|
|
577
|
+
const start = performance.now();
|
|
578
|
+
await runPythonHook(
|
|
579
|
+
PROMPT_GENERATOR_PATH,
|
|
580
|
+
{},
|
|
581
|
+
{
|
|
582
|
+
cwd: stateManager.baseDir,
|
|
583
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
const elapsed = performance.now() - start;
|
|
587
|
+
|
|
588
|
+
expect(elapsed).toBeLessThan(500);
|
|
589
|
+
console.log(` Prompt generator response: ${elapsed.toFixed(2)}ms`);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test('large history does not significantly impact performance', async () => {
|
|
593
|
+
if (!pythonAvailable) {
|
|
594
|
+
console.log('Skipping: Python not available');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Create state with large history
|
|
599
|
+
const largeHistory = Array.from({ length: 500 }, (_, i) => ({
|
|
600
|
+
iteration: i + 1,
|
|
601
|
+
peak_percent: Math.random() * 100,
|
|
602
|
+
checkpoint: i % 50 === 0,
|
|
603
|
+
}));
|
|
604
|
+
|
|
605
|
+
const state = createFullRalphState({
|
|
606
|
+
context_history: largeHistory,
|
|
607
|
+
});
|
|
608
|
+
stateManager.writeState(state);
|
|
609
|
+
|
|
610
|
+
const input = createContextInput(50);
|
|
611
|
+
|
|
612
|
+
const start = performance.now();
|
|
613
|
+
await runContextMonitor(input, stateManager);
|
|
614
|
+
const elapsed = performance.now() - start;
|
|
615
|
+
|
|
616
|
+
expect(elapsed).toBeLessThan(1000);
|
|
617
|
+
console.log(` Large history (500 entries) response: ${elapsed.toFixed(2)}ms`);
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
describe('Throughput', () => {
|
|
622
|
+
test('can process multiple L0 checks quickly', async () => {
|
|
623
|
+
if (!pythonAvailable) {
|
|
624
|
+
console.log('Skipping: Python not available');
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
stateManager.writeState(createMinimalRalphState());
|
|
629
|
+
const input = createContextInput(30);
|
|
630
|
+
|
|
631
|
+
const iterations = 5;
|
|
632
|
+
const start = performance.now();
|
|
633
|
+
|
|
634
|
+
for (let i = 0; i < iterations; i++) {
|
|
635
|
+
await runContextMonitor(input, stateManager);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const elapsed = performance.now() - start;
|
|
639
|
+
const avgTime = elapsed / iterations;
|
|
640
|
+
|
|
641
|
+
expect(avgTime).toBeLessThan(200);
|
|
642
|
+
console.log(` Average L0 check time: ${avgTime.toFixed(2)}ms (${iterations} iterations)`);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
});
|