anvil-dev-framework 0.1.6 → 0.1.7

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.
Files changed (48) hide show
  1. package/README.md +13 -12
  2. package/VERSION +1 -1
  3. package/docs/INSTALLATION.md +18 -0
  4. package/docs/command-reference.md +1 -1
  5. package/global/commands/orient.md +29 -0
  6. package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
  7. package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
  8. package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
  9. package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
  10. package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
  11. package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
  12. package/global/lib/__pycache__/git_utils.cpython-314.pyc +0 -0
  13. package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
  14. package/global/lib/__pycache__/handoff_generator.cpython-314.pyc +0 -0
  15. package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
  16. package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
  17. package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
  18. package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
  19. package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
  20. package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
  21. package/global/lib/__pycache__/orient_fast.cpython-314.pyc +0 -0
  22. package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
  23. package/global/lib/__pycache__/ralph_prompt_generator.cpython-314.pyc +0 -0
  24. package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
  25. package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
  26. package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
  27. package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
  28. package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
  29. package/global/lib/git_utils.py +267 -0
  30. package/global/lib/issue_models.py +28 -0
  31. package/global/lib/linear_provider.py +7 -0
  32. package/global/lib/orient_fast.py +24 -1
  33. package/global/tests/test_git_utils.py +160 -0
  34. package/global/tests/test_issue_models.py +40 -0
  35. package/global/tests/test_linear_provider.py +86 -0
  36. package/global/tools/anvil-memory/src/__tests__/commands.test.ts +238 -1
  37. package/global/tools/anvil-memory/src/commands/ralph-iteration.ts +249 -0
  38. package/global/tools/anvil-memory/src/index.ts +2 -8
  39. package/package.json +1 -1
  40. package/scripts/anvil +7 -2
  41. package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +0 -535
  42. package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +0 -645
  43. package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +0 -363
  44. package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +0 -8
  45. package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +0 -417
  46. package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +0 -571
  47. package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +0 -440
  48. package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +0 -252
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unit tests for git_utils.py
4
+
5
+ Run with: python3 -m pytest global/tests/test_git_utils.py -v
6
+ Or: python3 global/tests/test_git_utils.py
7
+ """
8
+
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add parent directory to path for imports
13
+ sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
14
+
15
+ from git_utils import (
16
+ get_current_branch,
17
+ get_repo_status,
18
+ get_divergence_status,
19
+ list_stashes,
20
+ run_git,
21
+ )
22
+
23
+
24
+ class TestRunGit:
25
+ """Tests for the run_git helper."""
26
+
27
+ def test_run_git_returns_tuple(self):
28
+ code, stdout, stderr = run_git('--version')
29
+ assert isinstance(code, int)
30
+ assert isinstance(stdout, str)
31
+ assert isinstance(stderr, str)
32
+
33
+ def test_run_git_version_succeeds(self):
34
+ code, stdout, _ = run_git('--version')
35
+ assert code == 0
36
+ assert 'git version' in stdout
37
+
38
+
39
+ class TestGetCurrentBranch:
40
+ """Tests for get_current_branch()."""
41
+
42
+ def test_returns_string_in_git_repo(self):
43
+ branch = get_current_branch()
44
+ # Should return a non-empty string when in a git repo
45
+ assert isinstance(branch, str)
46
+ assert len(branch) > 0
47
+
48
+ def test_returns_expected_format(self):
49
+ branch = get_current_branch()
50
+ # Branch names shouldn't have newlines
51
+ assert '\n' not in branch
52
+
53
+
54
+ class TestGetRepoStatus:
55
+ """Tests for get_repo_status()."""
56
+
57
+ def test_returns_dict_with_expected_keys(self):
58
+ status = get_repo_status()
59
+ expected_keys = [
60
+ 'has_changes',
61
+ 'has_staged',
62
+ 'has_unstaged',
63
+ 'has_untracked',
64
+ 'changed_files',
65
+ 'untracked_files',
66
+ 'current_branch',
67
+ ]
68
+ for key in expected_keys:
69
+ assert key in status, f"Missing key: {key}"
70
+
71
+ def test_has_changes_is_boolean(self):
72
+ status = get_repo_status()
73
+ assert isinstance(status['has_changes'], bool)
74
+
75
+ def test_changed_files_is_list(self):
76
+ status = get_repo_status()
77
+ assert isinstance(status['changed_files'], list)
78
+
79
+ def test_current_branch_matches_get_current_branch(self):
80
+ status = get_repo_status()
81
+ branch = get_current_branch()
82
+ assert status['current_branch'] == branch
83
+
84
+
85
+ class TestGetDivergenceStatus:
86
+ """Tests for get_divergence_status()."""
87
+
88
+ def test_returns_dict_with_expected_keys(self):
89
+ div = get_divergence_status()
90
+ expected_keys = ['ahead', 'behind', 'diverged', 'remote_branch']
91
+ for key in expected_keys:
92
+ assert key in div, f"Missing key: {key}"
93
+
94
+ def test_ahead_behind_are_integers(self):
95
+ div = get_divergence_status()
96
+ assert isinstance(div['ahead'], int)
97
+ assert isinstance(div['behind'], int)
98
+
99
+ def test_diverged_is_boolean(self):
100
+ div = get_divergence_status()
101
+ assert isinstance(div['diverged'], bool)
102
+
103
+ def test_diverged_logic_is_correct(self):
104
+ div = get_divergence_status()
105
+ # diverged should be True only if both ahead > 0 and behind > 0
106
+ expected = div['ahead'] > 0 and div['behind'] > 0
107
+ assert div['diverged'] == expected
108
+
109
+
110
+ class TestListStashes:
111
+ """Tests for list_stashes()."""
112
+
113
+ def test_returns_list(self):
114
+ stashes = list_stashes()
115
+ assert isinstance(stashes, list)
116
+
117
+ def test_stash_items_have_expected_keys(self):
118
+ stashes = list_stashes()
119
+ if stashes: # Only test if there are stashes
120
+ stash = stashes[0]
121
+ assert 'index' in stash
122
+ assert 'branch' in stash
123
+ assert 'message' in stash
124
+
125
+
126
+ def run_tests():
127
+ """Run all tests manually if pytest not available."""
128
+ test_classes = [
129
+ TestRunGit,
130
+ TestGetCurrentBranch,
131
+ TestGetRepoStatus,
132
+ TestGetDivergenceStatus,
133
+ TestListStashes,
134
+ ]
135
+ passed = 0
136
+ failed = 0
137
+
138
+ for test_class in test_classes:
139
+ instance = test_class()
140
+ for method_name in dir(instance):
141
+ if method_name.startswith("test_"):
142
+ try:
143
+ getattr(instance, method_name)()
144
+ print(f" ✓ {test_class.__name__}.{method_name}")
145
+ passed += 1
146
+ except AssertionError as e:
147
+ print(f" ✗ {test_class.__name__}.{method_name}: {e}")
148
+ failed += 1
149
+ except Exception as e:
150
+ print(f" ✗ {test_class.__name__}.{method_name}: {type(e).__name__}: {e}")
151
+ failed += 1
152
+
153
+ print(f"\nResults: {passed} passed, {failed} failed")
154
+ return failed == 0
155
+
156
+
157
+ if __name__ == "__main__":
158
+ print("Running git_utils tests...\n")
159
+ success = run_tests()
160
+ sys.exit(0 if success else 1)
@@ -267,6 +267,46 @@ class TestIssue:
267
267
  )
268
268
  assert "[agent-xyz]" in str(issue)
269
269
 
270
+ # Dict-style access tests (ANV-260)
271
+ def test_getitem_returns_field_value(self):
272
+ issue = Issue(id="1", identifier="TEST-001", title="Test")
273
+ assert issue["identifier"] == "TEST-001"
274
+ assert issue["title"] == "Test"
275
+ assert issue["status"] == IssueStatus.TODO
276
+
277
+ def test_getitem_raises_keyerror_for_unknown_field(self):
278
+ issue = Issue(id="1", identifier="TEST-001", title="Test")
279
+ try:
280
+ _ = issue["nonexistent"]
281
+ raise AssertionError("Should have raised KeyError")
282
+ except KeyError as e:
283
+ assert "nonexistent" in str(e)
284
+
285
+ def test_get_returns_field_value(self):
286
+ issue = Issue(id="1", identifier="TEST-001", title="Test")
287
+ assert issue.get("identifier") == "TEST-001"
288
+ assert issue.get("title") == "Test"
289
+
290
+ def test_get_returns_default_for_unknown_field(self):
291
+ issue = Issue(id="1", identifier="TEST-001", title="Test")
292
+ assert issue.get("nonexistent") is None
293
+ assert issue.get("nonexistent", "default") == "default"
294
+
295
+ def test_dict_style_access_matches_attribute_access(self):
296
+ issue = Issue(
297
+ id="1",
298
+ identifier="TEST-001",
299
+ title="Test",
300
+ status=IssueStatus.IN_PROGRESS,
301
+ priority=Priority.HIGH
302
+ )
303
+ # All fields should be accessible both ways
304
+ assert issue["id"] == issue.id
305
+ assert issue["identifier"] == issue.identifier
306
+ assert issue["title"] == issue.title
307
+ assert issue["status"] == issue.status
308
+ assert issue["priority"] == issue.priority
309
+
270
310
 
271
311
  def run_tests():
272
312
  """Run all tests manually if pytest not available."""
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unit tests for linear_provider.py
4
+
5
+ Run with: python3 -m pytest global/tests/test_linear_provider.py -v
6
+ Or: python3 global/tests/test_linear_provider.py
7
+ """
8
+
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add parent directory to path for imports
13
+ sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
14
+
15
+ from linear_provider import LinearProvider
16
+
17
+
18
+ class TestLinearProviderInit:
19
+ """Tests for LinearProvider initialization validation (ANV-260)."""
20
+
21
+ def test_init_without_team_raises_valueerror(self):
22
+ """LinearProvider() without team_key or team_id should fail fast."""
23
+ try:
24
+ LinearProvider()
25
+ raise AssertionError("Should have raised ValueError")
26
+ except ValueError as e:
27
+ assert "team_key or team_id" in str(e)
28
+ assert "LinearProvider(team_key='ANV')" in str(e)
29
+
30
+ def test_init_with_team_key_succeeds(self):
31
+ """LinearProvider(team_key='ANV') should work."""
32
+ lp = LinearProvider(team_key='ANV')
33
+ assert lp.team_key == 'ANV'
34
+
35
+ def test_init_with_team_id_succeeds(self):
36
+ """LinearProvider(team_id='...') should work."""
37
+ lp = LinearProvider(team_id='some-uuid-123')
38
+ assert lp._team_id == 'some-uuid-123'
39
+
40
+ def test_init_with_both_succeeds(self):
41
+ """LinearProvider with both team_key and team_id should work."""
42
+ lp = LinearProvider(team_key='ANV', team_id='uuid-123')
43
+ assert lp.team_key == 'ANV'
44
+ assert lp._team_id == 'uuid-123'
45
+
46
+ def test_error_message_is_helpful(self):
47
+ """Error message should include example usage."""
48
+ try:
49
+ LinearProvider()
50
+ except ValueError as e:
51
+ error_msg = str(e)
52
+ # Should mention what's required
53
+ assert "requires" in error_msg.lower()
54
+ # Should give an example
55
+ assert "team_key='ANV'" in error_msg
56
+
57
+
58
+ def run_tests():
59
+ """Run all tests manually if pytest not available."""
60
+ test_classes = [TestLinearProviderInit]
61
+ passed = 0
62
+ failed = 0
63
+
64
+ for test_class in test_classes:
65
+ instance = test_class()
66
+ for method_name in dir(instance):
67
+ if method_name.startswith("test_"):
68
+ try:
69
+ getattr(instance, method_name)()
70
+ print(f" ✓ {test_class.__name__}.{method_name}")
71
+ passed += 1
72
+ except AssertionError as e:
73
+ print(f" ✗ {test_class.__name__}.{method_name}: {e}")
74
+ failed += 1
75
+ except Exception as e:
76
+ print(f" ✗ {test_class.__name__}.{method_name}: {type(e).__name__}: {e}")
77
+ failed += 1
78
+
79
+ print(f"\nResults: {passed} passed, {failed} failed")
80
+ return failed == 0
81
+
82
+
83
+ if __name__ == "__main__":
84
+ print("Running linear_provider tests...\n")
85
+ success = run_tests()
86
+ sys.exit(0 if success else 1)
@@ -15,8 +15,9 @@ import { handleObserve, parseObserveArgs } from '../commands/observe';
15
15
  import { handleSearch, parseSearchArgs } from '../commands/search';
16
16
  import { handleGet, parseGetArgs } from '../commands/get';
17
17
  import { handleCheckpoint, parseCheckpointArgs } from '../commands/checkpoint';
18
+ import { handleRalphIteration, parseRalphIterationArgs } from '../commands/ralph-iteration';
18
19
  import { AnvilMemoryDb } from '../db';
19
- import type { Observation, Checkpoint } from '../types';
20
+ import type { Observation, Session, Checkpoint, RalphIteration } from '../types';
20
21
 
21
22
  // Type definitions for command result data
22
23
  interface InitResultData {
@@ -51,6 +52,11 @@ interface CheckpointResultData {
51
52
  observation?: Observation;
52
53
  }
53
54
 
55
+ interface RalphIterationResultData {
56
+ ralph_iteration?: RalphIteration;
57
+ observation?: Observation;
58
+ }
59
+
54
60
  // Test database path
55
61
  const TEST_DB_DIR = join(tmpdir(), 'anvil-memory-cmd-tests');
56
62
  const getTestDbPath = () => join(TEST_DB_DIR, `cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
@@ -629,6 +635,237 @@ describe('checkpoint command', () => {
629
635
  });
630
636
  });
631
637
 
638
+ describe('ralph-iteration command', () => {
639
+ let testDbPath: string;
640
+
641
+ beforeAll(async () => {
642
+ if (!existsSync(TEST_DB_DIR)) {
643
+ mkdirSync(TEST_DB_DIR, { recursive: true });
644
+ }
645
+ testDbPath = getTestDbPath();
646
+ await handleInit(['--path', testDbPath]);
647
+ });
648
+
649
+ afterAll(() => {
650
+ cleanupDb(testDbPath);
651
+ });
652
+
653
+ test('parseRalphIterationArgs parses required options', () => {
654
+ const opts = parseRalphIterationArgs(['--session', 'ralph-abc123', '--iteration', '5', '--status', 'running']);
655
+ expect(opts.session).toBe('ralph-abc123');
656
+ expect(opts.iteration).toBe('5');
657
+ expect(opts.status).toBe('running');
658
+ });
659
+
660
+ test('parseRalphIterationArgs parses short options', () => {
661
+ const opts = parseRalphIterationArgs(['-s', 'ralph-xyz', '-i', '10', '-t', 'completed']);
662
+ expect(opts.session).toBe('ralph-xyz');
663
+ expect(opts.iteration).toBe('10');
664
+ expect(opts.status).toBe('completed');
665
+ });
666
+
667
+ test('parseRalphIterationArgs parses optional fields', () => {
668
+ const opts = parseRalphIterationArgs([
669
+ '--session', 'ralph-test',
670
+ '--iteration', '3',
671
+ '--status', 'checkpointed',
672
+ '--items-completed', '8',
673
+ '--items-total', '10',
674
+ '--checkpoint-id', '42',
675
+ '--linear', 'ANV-203',
676
+ ]);
677
+ expect(opts.itemsCompleted).toBe('8');
678
+ expect(opts.itemsTotal).toBe('10');
679
+ expect(opts.checkpointId).toBe('42');
680
+ expect(opts.linear).toBe('ANV-203');
681
+ });
682
+
683
+ test('handleRalphIteration requires --session', async () => {
684
+ const result = await handleRalphIteration(['--iteration', '1', '--status', 'running', '--path', testDbPath]);
685
+
686
+ expect(result.success).toBe(false);
687
+ expect(result.error).toContain('--session');
688
+ });
689
+
690
+ test('handleRalphIteration requires --iteration', async () => {
691
+ const result = await handleRalphIteration(['--session', 'ralph-test', '--status', 'running', '--path', testDbPath]);
692
+
693
+ expect(result.success).toBe(false);
694
+ expect(result.error).toContain('--iteration');
695
+ });
696
+
697
+ test('handleRalphIteration requires --status', async () => {
698
+ const result = await handleRalphIteration(['--session', 'ralph-test', '--iteration', '1', '--path', testDbPath]);
699
+
700
+ expect(result.success).toBe(false);
701
+ expect(result.error).toContain('--status');
702
+ });
703
+
704
+ test('handleRalphIteration validates status', async () => {
705
+ const result = await handleRalphIteration([
706
+ '--session', 'ralph-test',
707
+ '--iteration', '1',
708
+ '--status', 'invalid-status',
709
+ '--path', testDbPath,
710
+ ]);
711
+
712
+ expect(result.success).toBe(false);
713
+ expect(result.error).toContain('Invalid status');
714
+ });
715
+
716
+ test('handleRalphIteration validates iteration is numeric', async () => {
717
+ const result = await handleRalphIteration([
718
+ '--session', 'ralph-test',
719
+ '--iteration', 'not-a-number',
720
+ '--status', 'running',
721
+ '--path', testDbPath,
722
+ ]);
723
+
724
+ expect(result.success).toBe(false);
725
+ expect(result.error).toContain('Invalid iteration');
726
+ });
727
+
728
+ test('handleRalphIteration validates items-completed is numeric', async () => {
729
+ const result = await handleRalphIteration([
730
+ '--session', 'ralph-test',
731
+ '--iteration', '1',
732
+ '--status', 'running',
733
+ '--items-completed', 'abc',
734
+ '--path', testDbPath,
735
+ ]);
736
+
737
+ expect(result.success).toBe(false);
738
+ expect(result.error).toContain('Invalid items-completed');
739
+ });
740
+
741
+ test('handleRalphIteration validates items-total is numeric', async () => {
742
+ const result = await handleRalphIteration([
743
+ '--session', 'ralph-test',
744
+ '--iteration', '1',
745
+ '--status', 'running',
746
+ '--items-total', 'xyz',
747
+ '--path', testDbPath,
748
+ ]);
749
+
750
+ expect(result.success).toBe(false);
751
+ expect(result.error).toContain('Invalid items-total');
752
+ });
753
+
754
+ test('handleRalphIteration rejects negative iteration', async () => {
755
+ const result = await handleRalphIteration([
756
+ '--session', 'ralph-test',
757
+ '--iteration', '-1',
758
+ '--status', 'running',
759
+ '--path', testDbPath,
760
+ ]);
761
+
762
+ expect(result.success).toBe(false);
763
+ expect(result.error).toContain('Invalid iteration');
764
+ });
765
+
766
+ test('handleRalphIteration creates ralph iteration record', async () => {
767
+ const result = await handleRalphIteration([
768
+ '--session', 'ralph-abc123',
769
+ '--iteration', '5',
770
+ '--status', 'running',
771
+ '--path', testDbPath,
772
+ ]);
773
+ const data = result.data as RalphIterationResultData;
774
+
775
+ expect(result.success).toBe(true);
776
+ expect(result.message).toContain('iteration 5');
777
+ expect(result.message).toContain('running');
778
+ expect(data?.ralph_iteration?.session_id).toBe('ralph-abc123');
779
+ expect(data?.ralph_iteration?.iteration).toBe(5);
780
+ expect(data?.ralph_iteration?.status).toBe('running');
781
+ });
782
+
783
+ test('handleRalphIteration handles all valid statuses', async () => {
784
+ const statuses = ['running', 'completed', 'failed', 'checkpointed'] as const;
785
+
786
+ for (const status of statuses) {
787
+ const result = await handleRalphIteration([
788
+ '--session', `ralph-${status}`,
789
+ '--iteration', '1',
790
+ '--status', status,
791
+ '--path', testDbPath,
792
+ ]);
793
+ const data = result.data as RalphIterationResultData;
794
+ expect(result.success).toBe(true);
795
+ expect(data?.ralph_iteration?.status).toBe(status);
796
+ }
797
+ });
798
+
799
+ test('handleRalphIteration stores items-completed and items-total', async () => {
800
+ const result = await handleRalphIteration([
801
+ '--session', 'ralph-items-test',
802
+ '--iteration', '3',
803
+ '--status', 'completed',
804
+ '--items-completed', '8',
805
+ '--items-total', '10',
806
+ '--path', testDbPath,
807
+ ]);
808
+ const data = result.data as RalphIterationResultData;
809
+
810
+ expect(result.success).toBe(true);
811
+ expect(data?.ralph_iteration?.items_completed).toBe(8);
812
+ expect(data?.ralph_iteration?.items_total).toBe(10);
813
+ });
814
+
815
+ test('handleRalphIteration stores checkpoint-id', async () => {
816
+ // First create a checkpoint to link to
817
+ const checkpointResult = await handleCheckpoint([
818
+ '--level', 'L2',
819
+ '--percent', '90',
820
+ '--path', testDbPath,
821
+ ]);
822
+ const checkpointData = checkpointResult.data as CheckpointResultData;
823
+ const checkpointId = checkpointData?.checkpoint?.id;
824
+
825
+ const result = await handleRalphIteration([
826
+ '--session', 'ralph-checkpoint-test',
827
+ '--iteration', '7',
828
+ '--status', 'checkpointed',
829
+ '--checkpoint-id', String(checkpointId),
830
+ '--path', testDbPath,
831
+ ]);
832
+ const data = result.data as RalphIterationResultData;
833
+
834
+ expect(result.success).toBe(true);
835
+ expect(data?.ralph_iteration?.checkpoint_id).toBe(checkpointId);
836
+ });
837
+
838
+ test('handleRalphIteration creates linked observation with --linear', async () => {
839
+ const result = await handleRalphIteration([
840
+ '--session', 'ralph-observation-test',
841
+ '--iteration', '2',
842
+ '--status', 'completed',
843
+ '--items-completed', '5',
844
+ '--items-total', '5',
845
+ '--linear', 'ANV-203',
846
+ '--path', testDbPath,
847
+ ]);
848
+ const data = result.data as RalphIterationResultData;
849
+
850
+ expect(result.success).toBe(true);
851
+ expect(data?.observation).toBeDefined();
852
+ expect(data?.observation?.type).toBe('ralph_iteration');
853
+ expect(data?.observation?.linear_issue).toBe('ANV-203');
854
+ });
855
+
856
+ test('handleRalphIteration fails when database does not exist', async () => {
857
+ const result = await handleRalphIteration([
858
+ '--session', 'ralph-test',
859
+ '--iteration', '1',
860
+ '--status', 'running',
861
+ '--path', '/tmp/nonexistent-db-12345.db',
862
+ ]);
863
+
864
+ expect(result.success).toBe(false);
865
+ expect(result.error).toContain('Database not found');
866
+ });
867
+ });
868
+
632
869
  describe('Command error handling', () => {
633
870
  test('observe fails when database does not exist', async () => {
634
871
  const result = await handleObserve([