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,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CCS Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the full Context Checkpoint System workflow:
|
|
5
|
+
* Context Monitor → Checkpoint → Ralph Stop → Prompt Generator
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import {
|
|
12
|
+
TestStateManager,
|
|
13
|
+
runContextMonitor,
|
|
14
|
+
runRalphStop,
|
|
15
|
+
runPythonHook,
|
|
16
|
+
isJqAvailable,
|
|
17
|
+
isPythonAvailable,
|
|
18
|
+
LIB_DIR,
|
|
19
|
+
} from './test-utils';
|
|
20
|
+
import {
|
|
21
|
+
createMinimalRalphState,
|
|
22
|
+
createFullRalphState,
|
|
23
|
+
CONTEXT_INPUTS,
|
|
24
|
+
} from './fixtures';
|
|
25
|
+
|
|
26
|
+
const PROMPT_GENERATOR_PATH = join(LIB_DIR, 'ralph_prompt_generator.py');
|
|
27
|
+
|
|
28
|
+
describe('CCS Integration', () => {
|
|
29
|
+
let stateManager: TestStateManager;
|
|
30
|
+
let jqAvailable: boolean;
|
|
31
|
+
let pythonAvailable: boolean;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
stateManager = new TestStateManager('ccs-integration');
|
|
35
|
+
stateManager.init();
|
|
36
|
+
[jqAvailable, pythonAvailable] = await Promise.all([
|
|
37
|
+
isJqAvailable(),
|
|
38
|
+
isPythonAvailable(),
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
stateManager.cleanup();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Checkpoint Creation Flow', () => {
|
|
47
|
+
test('L2 context triggers checkpoint creation in state', async () => {
|
|
48
|
+
if (!pythonAvailable) {
|
|
49
|
+
console.log('Skipping: Python not available');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Setup: Ralph state without checkpoint
|
|
54
|
+
const initialState = createFullRalphState({
|
|
55
|
+
context_checkpoint: undefined,
|
|
56
|
+
});
|
|
57
|
+
stateManager.writeState(initialState);
|
|
58
|
+
|
|
59
|
+
// Act: Run context monitor with L2 context
|
|
60
|
+
const result = await runContextMonitor(CONTEXT_INPUTS.L2_MEDIUM, stateManager);
|
|
61
|
+
|
|
62
|
+
// Verify: Monitor detected L2
|
|
63
|
+
expect(result.exitCode).toBe(0);
|
|
64
|
+
expect(result.stdout).toContain('L2');
|
|
65
|
+
|
|
66
|
+
// Verify: State now has checkpoint
|
|
67
|
+
const finalState = stateManager.readState();
|
|
68
|
+
expect(finalState.context_checkpoint).toBeDefined();
|
|
69
|
+
if (finalState.context_checkpoint) {
|
|
70
|
+
const checkpoint = finalState.context_checkpoint as Record<string, unknown>;
|
|
71
|
+
expect(checkpoint.active).toBe(true);
|
|
72
|
+
expect(checkpoint.level).toBe('L2');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('L3 context triggers emergency checkpoint', async () => {
|
|
77
|
+
if (!pythonAvailable) {
|
|
78
|
+
console.log('Skipping: Python not available');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const initialState = createFullRalphState({
|
|
83
|
+
task_name: 'Emergency Test Task',
|
|
84
|
+
todo_items: ['Urgent item'],
|
|
85
|
+
});
|
|
86
|
+
stateManager.writeState(initialState);
|
|
87
|
+
|
|
88
|
+
const result = await runContextMonitor(CONTEXT_INPUTS.L3_MEDIUM, stateManager);
|
|
89
|
+
|
|
90
|
+
// L3 exits with code 1 to signal emergency stop
|
|
91
|
+
expect(result.exitCode).toBe(1);
|
|
92
|
+
expect(result.stdout).toContain('L3');
|
|
93
|
+
|
|
94
|
+
const finalState = stateManager.readState();
|
|
95
|
+
expect(finalState.context_checkpoint).toBeDefined();
|
|
96
|
+
if (finalState.context_checkpoint) {
|
|
97
|
+
const checkpoint = finalState.context_checkpoint as Record<string, unknown>;
|
|
98
|
+
expect(checkpoint.level).toBe('L3');
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('handoff file is referenced in checkpoint', async () => {
|
|
103
|
+
if (!pythonAvailable) {
|
|
104
|
+
console.log('Skipping: Python not available');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const initialState = createFullRalphState();
|
|
109
|
+
stateManager.writeState(initialState);
|
|
110
|
+
|
|
111
|
+
await runContextMonitor(CONTEXT_INPUTS.L2_LOW, stateManager);
|
|
112
|
+
|
|
113
|
+
const finalState = stateManager.readState();
|
|
114
|
+
if (finalState.context_checkpoint) {
|
|
115
|
+
const checkpoint = finalState.context_checkpoint as Record<string, unknown>;
|
|
116
|
+
expect(checkpoint.handoff_file).toBeDefined();
|
|
117
|
+
expect(typeof checkpoint.handoff_file).toBe('string');
|
|
118
|
+
expect((checkpoint.handoff_file as string)).toContain('.claude/handoffs/');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Ralph Stop Checkpoint Detection', () => {
|
|
124
|
+
test('ralph stop detects active checkpoint and restarts', async () => {
|
|
125
|
+
if (!jqAvailable) {
|
|
126
|
+
console.log('Skipping: jq not available');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Setup: Create state with active checkpoint
|
|
131
|
+
const stateWithCheckpoint = createFullRalphState({
|
|
132
|
+
context_checkpoint: {
|
|
133
|
+
active: true,
|
|
134
|
+
level: 'L2',
|
|
135
|
+
percent_at_checkpoint: 87,
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
handoff_file: '.claude/handoffs/test.md',
|
|
138
|
+
resume_summary: 'Test resume',
|
|
139
|
+
files_in_progress: [{ path: 'test.ts', start_line: 1, end_line: 10 }],
|
|
140
|
+
current_todo_item: 'Current task',
|
|
141
|
+
progress_on_item: 'In progress',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
stateManager.writeState(stateWithCheckpoint);
|
|
145
|
+
|
|
146
|
+
// Act: Run ralph stop
|
|
147
|
+
const result = await runRalphStop(stateManager);
|
|
148
|
+
|
|
149
|
+
// Verify: Should restart loop (exit 1) due to checkpoint
|
|
150
|
+
expect(result.exitCode).toBe(1);
|
|
151
|
+
expect(result.stderr).toContain('checkpoint');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('ralph stop continues without checkpoint', async () => {
|
|
155
|
+
if (!jqAvailable) {
|
|
156
|
+
console.log('Skipping: jq not available');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const stateNoCheckpoint = createMinimalRalphState({ iteration: 5 });
|
|
161
|
+
stateManager.writeState(stateNoCheckpoint);
|
|
162
|
+
|
|
163
|
+
const result = await runRalphStop(stateManager);
|
|
164
|
+
|
|
165
|
+
// Should restart loop normally
|
|
166
|
+
expect(result.exitCode).toBe(1);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Prompt Generator Integration', () => {
|
|
171
|
+
test('prompt generator reads checkpoint state correctly', async () => {
|
|
172
|
+
if (!pythonAvailable) {
|
|
173
|
+
console.log('Skipping: Python not available');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Setup: First create checkpoint via context monitor
|
|
178
|
+
const initialState = createFullRalphState({
|
|
179
|
+
task_name: 'Integration Test Task',
|
|
180
|
+
objective: 'Complete the integration',
|
|
181
|
+
todo_items: ['Step 1', 'Step 2', 'Step 3'],
|
|
182
|
+
completed_items: ['Setup'],
|
|
183
|
+
});
|
|
184
|
+
stateManager.writeState(initialState);
|
|
185
|
+
|
|
186
|
+
// Trigger checkpoint
|
|
187
|
+
await runContextMonitor(CONTEXT_INPUTS.L2_HIGH, stateManager);
|
|
188
|
+
|
|
189
|
+
// Now generate prompt from checkpointed state
|
|
190
|
+
const result = await runPythonHook(
|
|
191
|
+
PROMPT_GENERATOR_PATH,
|
|
192
|
+
{},
|
|
193
|
+
{
|
|
194
|
+
cwd: stateManager.baseDir,
|
|
195
|
+
args: ['--state', stateManager.stateFile, '--dry-run'],
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(result.exitCode).toBe(0);
|
|
200
|
+
expect(result.stdout).toContain('Integration Test Task');
|
|
201
|
+
expect(result.stdout).toContain('L2');
|
|
202
|
+
expect(result.stdout).toContain('Step 1');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('prompt generator writes valid PROMPT.md', async () => {
|
|
206
|
+
if (!pythonAvailable) {
|
|
207
|
+
console.log('Skipping: Python not available');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const state = createFullRalphState({
|
|
212
|
+
context_checkpoint: {
|
|
213
|
+
active: true,
|
|
214
|
+
level: 'L2',
|
|
215
|
+
percent_at_checkpoint: 89,
|
|
216
|
+
timestamp: new Date().toISOString(),
|
|
217
|
+
handoff_file: '.claude/handoffs/session.md',
|
|
218
|
+
resume_summary: '',
|
|
219
|
+
files_in_progress: [{ path: 'main.ts', start_line: 100, end_line: 200 }],
|
|
220
|
+
current_todo_item: 'Current work',
|
|
221
|
+
progress_on_item: 'Halfway done',
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
stateManager.writeState(state);
|
|
225
|
+
|
|
226
|
+
const promptPath = join(stateManager.baseDir, 'PROMPT.md');
|
|
227
|
+
const result = await runPythonHook(
|
|
228
|
+
PROMPT_GENERATOR_PATH,
|
|
229
|
+
{},
|
|
230
|
+
{
|
|
231
|
+
cwd: stateManager.baseDir,
|
|
232
|
+
args: ['--state', stateManager.stateFile, '--output', promptPath],
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(result.exitCode).toBe(0);
|
|
237
|
+
expect(existsSync(promptPath)).toBe(true);
|
|
238
|
+
|
|
239
|
+
const promptContent = readFileSync(promptPath, 'utf-8');
|
|
240
|
+
expect(promptContent).toContain('Session Resume');
|
|
241
|
+
expect(promptContent).toContain('Resume Instructions');
|
|
242
|
+
expect(promptContent).toContain('main.ts');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('Full Cycle Test', () => {
|
|
247
|
+
test('complete checkpoint → resume cycle', async () => {
|
|
248
|
+
if (!pythonAvailable || !jqAvailable) {
|
|
249
|
+
console.log('Skipping: Dependencies not available');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Step 1: Initial state - running task
|
|
254
|
+
const initialState = createFullRalphState({
|
|
255
|
+
iteration: 10,
|
|
256
|
+
task_name: 'Full Cycle Test',
|
|
257
|
+
objective: 'Test complete CCS workflow',
|
|
258
|
+
todo_items: ['Remaining item 1', 'Remaining item 2'],
|
|
259
|
+
completed_items: ['Completed item 1'],
|
|
260
|
+
});
|
|
261
|
+
stateManager.writeState(initialState);
|
|
262
|
+
|
|
263
|
+
// Step 2: Context monitor detects high usage and creates checkpoint
|
|
264
|
+
const monitorResult = await runContextMonitor(CONTEXT_INPUTS.L2_MEDIUM, stateManager);
|
|
265
|
+
expect(monitorResult.exitCode).toBe(0);
|
|
266
|
+
|
|
267
|
+
// Verify checkpoint was created
|
|
268
|
+
const stateAfterMonitor = stateManager.readState();
|
|
269
|
+
expect(stateAfterMonitor.context_checkpoint).toBeDefined();
|
|
270
|
+
const checkpoint = stateAfterMonitor.context_checkpoint as Record<string, unknown>;
|
|
271
|
+
expect(checkpoint.active).toBe(true);
|
|
272
|
+
|
|
273
|
+
// Step 3: Ralph stop detects checkpoint and signals restart
|
|
274
|
+
const stopResult = await runRalphStop(stateManager);
|
|
275
|
+
expect(stopResult.exitCode).toBe(1); // Restart loop
|
|
276
|
+
|
|
277
|
+
// Step 4: Generate resume prompt
|
|
278
|
+
const promptPath = join(stateManager.baseDir, 'PROMPT.md');
|
|
279
|
+
const promptResult = await runPythonHook(
|
|
280
|
+
PROMPT_GENERATOR_PATH,
|
|
281
|
+
{},
|
|
282
|
+
{
|
|
283
|
+
cwd: stateManager.baseDir,
|
|
284
|
+
args: ['--state', stateManager.stateFile, '--output', promptPath],
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
expect(promptResult.exitCode).toBe(0);
|
|
288
|
+
|
|
289
|
+
// Step 5: Validate generated prompt
|
|
290
|
+
expect(existsSync(promptPath)).toBe(true);
|
|
291
|
+
const promptContent = readFileSync(promptPath, 'utf-8');
|
|
292
|
+
expect(promptContent).toContain('Full Cycle Test');
|
|
293
|
+
expect(promptContent).toContain('Resume Instructions');
|
|
294
|
+
expect(promptContent).toContain('L2');
|
|
295
|
+
expect(promptContent).toContain('Remaining item 1');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('Context History Tracking', () => {
|
|
300
|
+
test('context history accumulates across iterations', async () => {
|
|
301
|
+
if (!pythonAvailable) {
|
|
302
|
+
console.log('Skipping: Python not available');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const initialState = createMinimalRalphState({
|
|
307
|
+
iteration: 1,
|
|
308
|
+
context_history: [],
|
|
309
|
+
});
|
|
310
|
+
stateManager.writeState(initialState);
|
|
311
|
+
|
|
312
|
+
// Run several context monitor calls at different levels
|
|
313
|
+
await runContextMonitor(CONTEXT_INPUTS.L0_MEDIUM, stateManager);
|
|
314
|
+
|
|
315
|
+
// Update iteration and run again
|
|
316
|
+
const state1 = stateManager.readState();
|
|
317
|
+
stateManager.writeState({ ...state1, iteration: 2 });
|
|
318
|
+
await runContextMonitor(CONTEXT_INPUTS.L1_LOW, stateManager);
|
|
319
|
+
|
|
320
|
+
// Final check
|
|
321
|
+
const finalState = stateManager.readState();
|
|
322
|
+
const history = finalState.context_history as Array<Record<string, unknown>> | undefined;
|
|
323
|
+
|
|
324
|
+
// History should have entries
|
|
325
|
+
expect(history).toBeDefined();
|
|
326
|
+
if (history) {
|
|
327
|
+
expect(history.length).toBeGreaterThan(0);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('checkpoint event is recorded in history', async () => {
|
|
332
|
+
if (!pythonAvailable) {
|
|
333
|
+
console.log('Skipping: Python not available');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const initialState = createFullRalphState({
|
|
338
|
+
context_history: [
|
|
339
|
+
{ iteration: 1, peak_percent: 30, checkpoint: false },
|
|
340
|
+
{ iteration: 2, peak_percent: 50, checkpoint: false },
|
|
341
|
+
],
|
|
342
|
+
});
|
|
343
|
+
stateManager.writeState(initialState);
|
|
344
|
+
|
|
345
|
+
// Trigger checkpoint
|
|
346
|
+
await runContextMonitor(CONTEXT_INPUTS.L2_MEDIUM, stateManager);
|
|
347
|
+
|
|
348
|
+
const finalState = stateManager.readState();
|
|
349
|
+
const history = finalState.context_history as Array<Record<string, unknown>> | undefined;
|
|
350
|
+
|
|
351
|
+
if (history) {
|
|
352
|
+
// Find checkpoint entry
|
|
353
|
+
const checkpointEntry = history.find(h => h.checkpoint === true);
|
|
354
|
+
expect(checkpointEntry).toBeDefined();
|
|
355
|
+
if (checkpointEntry) {
|
|
356
|
+
expect(checkpointEntry.level).toBe('L2');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('Error Recovery', () => {
|
|
363
|
+
test('system handles missing handoff directory gracefully', async () => {
|
|
364
|
+
if (!pythonAvailable) {
|
|
365
|
+
console.log('Skipping: Python not available');
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Create state but remove handoffs directory
|
|
370
|
+
const state = createFullRalphState();
|
|
371
|
+
stateManager.writeState(state);
|
|
372
|
+
|
|
373
|
+
// This should still work - context monitor should create directory if needed
|
|
374
|
+
const result = await runContextMonitor(CONTEXT_INPUTS.L2_LOW, stateManager);
|
|
375
|
+
|
|
376
|
+
// Should not crash
|
|
377
|
+
expect(typeof result.exitCode).toBe('number');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('system handles sequential state modifications', async () => {
|
|
381
|
+
if (!pythonAvailable) {
|
|
382
|
+
console.log('Skipping: Python not available');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const state = createFullRalphState();
|
|
387
|
+
stateManager.writeState(state);
|
|
388
|
+
|
|
389
|
+
// Run monitors sequentially (concurrent writes are a known race condition)
|
|
390
|
+
const result1 = await runContextMonitor(CONTEXT_INPUTS.L1_LOW, stateManager);
|
|
391
|
+
const result2 = await runContextMonitor(CONTEXT_INPUTS.L1_MEDIUM, stateManager);
|
|
392
|
+
|
|
393
|
+
// Both should complete without crashing
|
|
394
|
+
expect(typeof result1.exitCode).toBe('number');
|
|
395
|
+
expect(typeof result2.exitCode).toBe('number');
|
|
396
|
+
|
|
397
|
+
// State should still be valid JSON
|
|
398
|
+
const finalState = stateManager.readState();
|
|
399
|
+
expect(finalState).toBeDefined();
|
|
400
|
+
expect(finalState.context_history).toBeDefined();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('CCS Integration - Dependency Check', () => {
|
|
406
|
+
test('all required hook scripts exist', () => {
|
|
407
|
+
const hookPaths = [
|
|
408
|
+
'/Users/alexandercahiz/Projects/anvil-dev-framework/global/hooks/ralph_context_monitor.py',
|
|
409
|
+
'/Users/alexandercahiz/Projects/anvil-dev-framework/global/hooks/ralph_stop.sh',
|
|
410
|
+
'/Users/alexandercahiz/Projects/anvil-dev-framework/global/lib/ralph_prompt_generator.py',
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
for (const hookPath of hookPaths) {
|
|
414
|
+
expect(existsSync(hookPath)).toBe(true);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
});
|