rafcode 1.2.0 → 1.3.2

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 (43) hide show
  1. package/RAF/019-verbose-chronicle/decisions.md +25 -0
  2. package/RAF/019-verbose-chronicle/input.md +3 -0
  3. package/RAF/019-verbose-chronicle/outcomes/001-amend-iteration-references.md +25 -0
  4. package/RAF/019-verbose-chronicle/outcomes/002-verbose-task-name-display.md +31 -0
  5. package/RAF/019-verbose-chronicle/outcomes/003-verbose-streaming-fix.md +48 -0
  6. package/RAF/019-verbose-chronicle/outcomes/004-commit-verification-before-halt.md +56 -0
  7. package/RAF/019-verbose-chronicle/plans/001-amend-iteration-references.md +35 -0
  8. package/RAF/019-verbose-chronicle/plans/002-verbose-task-name-display.md +38 -0
  9. package/RAF/019-verbose-chronicle/plans/003-verbose-streaming-fix.md +45 -0
  10. package/RAF/019-verbose-chronicle/plans/004-commit-verification-before-halt.md +62 -0
  11. package/dist/commands/do.js +19 -11
  12. package/dist/commands/do.js.map +1 -1
  13. package/dist/core/claude-runner.d.ts +52 -1
  14. package/dist/core/claude-runner.d.ts.map +1 -1
  15. package/dist/core/claude-runner.js +195 -17
  16. package/dist/core/claude-runner.js.map +1 -1
  17. package/dist/core/git.d.ts +15 -0
  18. package/dist/core/git.d.ts.map +1 -1
  19. package/dist/core/git.js +44 -0
  20. package/dist/core/git.js.map +1 -1
  21. package/dist/parsers/stream-renderer.d.ts +42 -0
  22. package/dist/parsers/stream-renderer.d.ts.map +1 -0
  23. package/dist/parsers/stream-renderer.js +100 -0
  24. package/dist/parsers/stream-renderer.js.map +1 -0
  25. package/dist/prompts/amend.d.ts.map +1 -1
  26. package/dist/prompts/amend.js +13 -2
  27. package/dist/prompts/amend.js.map +1 -1
  28. package/dist/prompts/execution.d.ts.map +1 -1
  29. package/dist/prompts/execution.js +1 -3
  30. package/dist/prompts/execution.js.map +1 -1
  31. package/dist/prompts/planning.js +2 -2
  32. package/package.json +1 -1
  33. package/src/commands/do.ts +20 -11
  34. package/src/core/claude-runner.ts +270 -17
  35. package/src/core/git.ts +44 -0
  36. package/src/parsers/stream-renderer.ts +139 -0
  37. package/src/prompts/amend.ts +14 -2
  38. package/src/prompts/execution.ts +1 -3
  39. package/src/prompts/planning.ts +2 -2
  40. package/tests/unit/claude-runner.test.ts +567 -1
  41. package/tests/unit/git-commit-helpers.test.ts +103 -0
  42. package/tests/unit/plan-command.test.ts +51 -0
  43. package/tests/unit/stream-renderer.test.ts +286 -0
@@ -550,6 +550,57 @@ describe('Plan Command - Amend Functionality', () => {
550
550
  expect(userMessage).toContain('Add a caching layer for API responses');
551
551
  expect(userMessage).toContain('planning interview');
552
552
  });
553
+
554
+ it('should include outcome file paths for completed tasks in task summary', () => {
555
+ const params: AmendPromptParams = {
556
+ projectPath: '/test/project',
557
+ existingTasks: [
558
+ { id: '001', planFile: 'plans/001-setup.md', status: 'completed', taskName: 'setup' },
559
+ { id: '002', planFile: 'plans/002-database.md', status: 'completed', taskName: 'database' },
560
+ ],
561
+ nextTaskNumber: 3,
562
+ newTaskDescription: 'Fix setup issues',
563
+ };
564
+
565
+ const { systemPrompt } = getAmendPrompt(params);
566
+
567
+ expect(systemPrompt).toContain('Outcome: /test/project/outcomes/001-setup.md');
568
+ expect(systemPrompt).toContain('Outcome: /test/project/outcomes/002-database.md');
569
+ });
570
+
571
+ it('should not include outcome file paths for non-completed tasks', () => {
572
+ const params: AmendPromptParams = {
573
+ projectPath: '/test/project',
574
+ existingTasks: [
575
+ { id: '001', planFile: 'plans/001-pending-task.md', status: 'pending', taskName: 'pending-task' },
576
+ { id: '002', planFile: 'plans/002-failed-task.md', status: 'failed', taskName: 'failed-task' },
577
+ ],
578
+ nextTaskNumber: 3,
579
+ newTaskDescription: 'New tasks',
580
+ };
581
+
582
+ const { systemPrompt } = getAmendPrompt(params);
583
+
584
+ expect(systemPrompt).not.toContain('Outcome: /test/project/outcomes/001-pending-task.md');
585
+ expect(systemPrompt).not.toContain('Outcome: /test/project/outcomes/002-failed-task.md');
586
+ });
587
+
588
+ it('should include follow-up task instructions in system prompt', () => {
589
+ const params: AmendPromptParams = {
590
+ projectPath: '/test/project',
591
+ existingTasks: [
592
+ { id: '001', planFile: 'plans/001-first.md', status: 'completed', taskName: 'first' },
593
+ ],
594
+ nextTaskNumber: 2,
595
+ newTaskDescription: 'Fix issues from first task',
596
+ };
597
+
598
+ const { systemPrompt } = getAmendPrompt(params);
599
+
600
+ expect(systemPrompt).toContain('Identifying Follow-up Tasks');
601
+ expect(systemPrompt).toContain('follow-up to task NNN');
602
+ expect(systemPrompt).toContain('outcome file paths for completed tasks are listed above');
603
+ });
553
604
  });
554
605
 
555
606
  describe('Existing Project Detection Without Amend Flag', () => {
@@ -0,0 +1,286 @@
1
+ import { renderStreamEvent } from '../../src/parsers/stream-renderer.js';
2
+
3
+ describe('renderStreamEvent', () => {
4
+ describe('system events', () => {
5
+ it('should not display system init events', () => {
6
+ const line = JSON.stringify({
7
+ type: 'system',
8
+ subtype: 'init',
9
+ session_id: 'test-session',
10
+ tools: ['Read', 'Write', 'Bash'],
11
+ model: 'claude-opus-4-6',
12
+ });
13
+ const result = renderStreamEvent(line);
14
+ expect(result.display).toBe('');
15
+ expect(result.textContent).toBe('');
16
+ });
17
+ });
18
+
19
+ describe('assistant events with text', () => {
20
+ it('should display and capture text content', () => {
21
+ const line = JSON.stringify({
22
+ type: 'assistant',
23
+ message: {
24
+ content: [{ type: 'text', text: 'The version is 1.3.1' }],
25
+ },
26
+ });
27
+ const result = renderStreamEvent(line);
28
+ expect(result.display).toBe('The version is 1.3.1\n');
29
+ expect(result.textContent).toBe('The version is 1.3.1');
30
+ });
31
+
32
+ it('should handle multiple text blocks in one message', () => {
33
+ const line = JSON.stringify({
34
+ type: 'assistant',
35
+ message: {
36
+ content: [
37
+ { type: 'text', text: 'First part' },
38
+ { type: 'text', text: 'Second part' },
39
+ ],
40
+ },
41
+ });
42
+ const result = renderStreamEvent(line);
43
+ expect(result.textContent).toBe('First partSecond part');
44
+ });
45
+
46
+ it('should capture text containing completion markers', () => {
47
+ const line = JSON.stringify({
48
+ type: 'assistant',
49
+ message: {
50
+ content: [{ type: 'text', text: 'Task done.\n<promise>COMPLETE</promise>' }],
51
+ },
52
+ });
53
+ const result = renderStreamEvent(line);
54
+ expect(result.textContent).toContain('<promise>COMPLETE</promise>');
55
+ });
56
+ });
57
+
58
+ describe('assistant events with tool_use', () => {
59
+ it('should display Read tool usage', () => {
60
+ const line = JSON.stringify({
61
+ type: 'assistant',
62
+ message: {
63
+ content: [{ type: 'tool_use', name: 'Read', input: { file_path: '/src/main.ts' } }],
64
+ },
65
+ });
66
+ const result = renderStreamEvent(line);
67
+ expect(result.display).toContain('Reading /src/main.ts');
68
+ expect(result.textContent).toBe('');
69
+ });
70
+
71
+ it('should display Write tool usage', () => {
72
+ const line = JSON.stringify({
73
+ type: 'assistant',
74
+ message: {
75
+ content: [{ type: 'tool_use', name: 'Write', input: { file_path: '/src/new.ts' } }],
76
+ },
77
+ });
78
+ const result = renderStreamEvent(line);
79
+ expect(result.display).toContain('Writing /src/new.ts');
80
+ });
81
+
82
+ it('should display Edit tool usage', () => {
83
+ const line = JSON.stringify({
84
+ type: 'assistant',
85
+ message: {
86
+ content: [{ type: 'tool_use', name: 'Edit', input: { file_path: '/src/fix.ts' } }],
87
+ },
88
+ });
89
+ const result = renderStreamEvent(line);
90
+ expect(result.display).toContain('Editing /src/fix.ts');
91
+ });
92
+
93
+ it('should display Bash tool with command', () => {
94
+ const line = JSON.stringify({
95
+ type: 'assistant',
96
+ message: {
97
+ content: [{ type: 'tool_use', name: 'Bash', input: { command: 'npm test' } }],
98
+ },
99
+ });
100
+ const result = renderStreamEvent(line);
101
+ expect(result.display).toContain('Running: npm test');
102
+ });
103
+
104
+ it('should truncate long Bash commands', () => {
105
+ const longCommand = 'a'.repeat(200);
106
+ const line = JSON.stringify({
107
+ type: 'assistant',
108
+ message: {
109
+ content: [{ type: 'tool_use', name: 'Bash', input: { command: longCommand } }],
110
+ },
111
+ });
112
+ const result = renderStreamEvent(line);
113
+ expect(result.display.length).toBeLessThan(200);
114
+ expect(result.display).toContain('...');
115
+ });
116
+
117
+ it('should display Glob tool usage', () => {
118
+ const line = JSON.stringify({
119
+ type: 'assistant',
120
+ message: {
121
+ content: [{ type: 'tool_use', name: 'Glob', input: { pattern: '**/*.ts' } }],
122
+ },
123
+ });
124
+ const result = renderStreamEvent(line);
125
+ expect(result.display).toContain('Searching files: **/*.ts');
126
+ });
127
+
128
+ it('should display Grep tool usage', () => {
129
+ const line = JSON.stringify({
130
+ type: 'assistant',
131
+ message: {
132
+ content: [{ type: 'tool_use', name: 'Grep', input: { pattern: 'TODO' } }],
133
+ },
134
+ });
135
+ const result = renderStreamEvent(line);
136
+ expect(result.display).toContain('Searching for: TODO');
137
+ });
138
+
139
+ it('should display WebFetch tool usage', () => {
140
+ const line = JSON.stringify({
141
+ type: 'assistant',
142
+ message: {
143
+ content: [{ type: 'tool_use', name: 'WebFetch', input: { url: 'https://example.com' } }],
144
+ },
145
+ });
146
+ const result = renderStreamEvent(line);
147
+ expect(result.display).toContain('Fetching: https://example.com');
148
+ });
149
+
150
+ it('should display WebSearch tool usage', () => {
151
+ const line = JSON.stringify({
152
+ type: 'assistant',
153
+ message: {
154
+ content: [{ type: 'tool_use', name: 'WebSearch', input: { query: 'node.js streams' } }],
155
+ },
156
+ });
157
+ const result = renderStreamEvent(line);
158
+ expect(result.display).toContain('Searching web: node.js streams');
159
+ });
160
+
161
+ it('should display TodoWrite tool usage', () => {
162
+ const line = JSON.stringify({
163
+ type: 'assistant',
164
+ message: {
165
+ content: [{ type: 'tool_use', name: 'TodoWrite', input: { todos: [] } }],
166
+ },
167
+ });
168
+ const result = renderStreamEvent(line);
169
+ expect(result.display).toContain('Updating task list');
170
+ });
171
+
172
+ it('should display Task (agent) tool usage', () => {
173
+ const line = JSON.stringify({
174
+ type: 'assistant',
175
+ message: {
176
+ content: [{ type: 'tool_use', name: 'Task', input: { description: 'Search codebase' } }],
177
+ },
178
+ });
179
+ const result = renderStreamEvent(line);
180
+ expect(result.display).toContain('Launching agent: Search codebase');
181
+ });
182
+
183
+ it('should display unknown tools generically', () => {
184
+ const line = JSON.stringify({
185
+ type: 'assistant',
186
+ message: {
187
+ content: [{ type: 'tool_use', name: 'CustomTool', input: {} }],
188
+ },
189
+ });
190
+ const result = renderStreamEvent(line);
191
+ expect(result.display).toContain('Using tool: CustomTool');
192
+ });
193
+ });
194
+
195
+ describe('mixed content blocks', () => {
196
+ it('should handle text and tool_use in same message', () => {
197
+ const line = JSON.stringify({
198
+ type: 'assistant',
199
+ message: {
200
+ content: [
201
+ { type: 'text', text: 'Let me read the file.' },
202
+ { type: 'tool_use', name: 'Read', input: { file_path: '/src/main.ts' } },
203
+ ],
204
+ },
205
+ });
206
+ const result = renderStreamEvent(line);
207
+ expect(result.display).toContain('Let me read the file.');
208
+ expect(result.display).toContain('Reading /src/main.ts');
209
+ expect(result.textContent).toBe('Let me read the file.');
210
+ });
211
+ });
212
+
213
+ describe('user events (tool results)', () => {
214
+ it('should not display tool result events', () => {
215
+ const line = JSON.stringify({
216
+ type: 'user',
217
+ message: {
218
+ content: [{ type: 'tool_result', tool_use_id: 'test-id', content: 'file contents here' }],
219
+ },
220
+ });
221
+ const result = renderStreamEvent(line);
222
+ expect(result.display).toBe('');
223
+ expect(result.textContent).toBe('');
224
+ });
225
+ });
226
+
227
+ describe('result events', () => {
228
+ it('should not duplicate result text (already captured from assistant events)', () => {
229
+ const line = JSON.stringify({
230
+ type: 'result',
231
+ subtype: 'success',
232
+ result: 'The version is 1.3.1',
233
+ });
234
+ const result = renderStreamEvent(line);
235
+ expect(result.display).toBe('');
236
+ expect(result.textContent).toBe('');
237
+ });
238
+ });
239
+
240
+ describe('edge cases', () => {
241
+ it('should handle empty lines', () => {
242
+ const result = renderStreamEvent('');
243
+ expect(result.display).toBe('');
244
+ expect(result.textContent).toBe('');
245
+ });
246
+
247
+ it('should handle whitespace-only lines', () => {
248
+ const result = renderStreamEvent(' ');
249
+ expect(result.display).toBe('');
250
+ expect(result.textContent).toBe('');
251
+ });
252
+
253
+ it('should handle invalid JSON gracefully', () => {
254
+ const result = renderStreamEvent('not json at all');
255
+ expect(result.display).toBe('not json at all\n');
256
+ expect(result.textContent).toBe('not json at all');
257
+ });
258
+
259
+ it('should handle assistant event with no content', () => {
260
+ const line = JSON.stringify({
261
+ type: 'assistant',
262
+ message: {},
263
+ });
264
+ const result = renderStreamEvent(line);
265
+ expect(result.display).toBe('');
266
+ expect(result.textContent).toBe('');
267
+ });
268
+
269
+ it('should handle assistant event with empty content array', () => {
270
+ const line = JSON.stringify({
271
+ type: 'assistant',
272
+ message: { content: [] },
273
+ });
274
+ const result = renderStreamEvent(line);
275
+ expect(result.display).toBe('');
276
+ expect(result.textContent).toBe('');
277
+ });
278
+
279
+ it('should handle unknown event types', () => {
280
+ const line = JSON.stringify({ type: 'unknown_event' });
281
+ const result = renderStreamEvent(line);
282
+ expect(result.display).toBe('');
283
+ expect(result.textContent).toBe('');
284
+ });
285
+ });
286
+ });