rafcode 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +4 -1
- package/CLAUDE.md +59 -11
- package/RAF/ahslfe-config-wizard/decisions.md +34 -0
- package/RAF/ahslfe-config-wizard/input.md +1 -0
- package/RAF/ahslfe-config-wizard/outcomes/01-define-config-schema.md +38 -0
- package/RAF/ahslfe-config-wizard/outcomes/02-refactor-codebase-to-use-config.md +67 -0
- package/RAF/ahslfe-config-wizard/outcomes/03-create-config-documentation.md +37 -0
- package/RAF/ahslfe-config-wizard/outcomes/04-implement-raf-config-command.md +47 -0
- package/RAF/ahslfe-config-wizard/outcomes/05-update-claude-md.md +26 -0
- package/RAF/ahslfe-config-wizard/plans/01-define-config-schema.md +73 -0
- package/RAF/ahslfe-config-wizard/plans/02-refactor-codebase-to-use-config.md +74 -0
- package/RAF/ahslfe-config-wizard/plans/03-create-config-documentation.md +57 -0
- package/RAF/ahslfe-config-wizard/plans/04-implement-raf-config-command.md +66 -0
- package/RAF/ahslfe-config-wizard/plans/05-update-claude-md.md +60 -0
- package/RAF/ahstvo-token-tracker/decisions.md +44 -0
- package/RAF/ahstvo-token-tracker/input.md +3 -0
- package/RAF/ahstvo-token-tracker/outcomes/01-full-model-id-support.md +43 -0
- package/RAF/ahstvo-token-tracker/outcomes/02-name-generation-no-session.md +33 -0
- package/RAF/ahstvo-token-tracker/outcomes/03-unify-stream-json-execution.md +48 -0
- package/RAF/ahstvo-token-tracker/outcomes/04-token-tracking-cost-calculation.md +53 -0
- package/RAF/ahstvo-token-tracker/outcomes/05-token-cost-console-reporting.md +57 -0
- package/RAF/ahstvo-token-tracker/outcomes/06-runtime-verbose-toggle.md +53 -0
- package/RAF/ahstvo-token-tracker/outcomes/07-readme-config-docs.md +36 -0
- package/RAF/ahstvo-token-tracker/plans/01-full-model-id-support.md +35 -0
- package/RAF/ahstvo-token-tracker/plans/02-name-generation-no-session.md +36 -0
- package/RAF/ahstvo-token-tracker/plans/03-unify-stream-json-execution.md +44 -0
- package/RAF/ahstvo-token-tracker/plans/04-token-tracking-cost-calculation.md +56 -0
- package/RAF/ahstvo-token-tracker/plans/05-token-cost-console-reporting.md +55 -0
- package/RAF/ahstvo-token-tracker/plans/06-runtime-verbose-toggle.md +48 -0
- package/RAF/ahstvo-token-tracker/plans/07-readme-config-docs.md +44 -0
- package/RAF/ahtahs-token-reaper/decisions.md +37 -0
- package/RAF/ahtahs-token-reaper/input.md +20 -0
- package/RAF/ahtahs-token-reaper/outcomes/01-extend-token-tracker-data-model.md +42 -0
- package/RAF/ahtahs-token-reaper/outcomes/02-accumulate-usage-in-retry-loop.md +31 -0
- package/RAF/ahtahs-token-reaper/outcomes/03-per-attempt-display-formatting.md +60 -0
- package/RAF/ahtahs-token-reaper/outcomes/04-add-model-name-to-claude-call-logs.md +57 -0
- package/RAF/ahtahs-token-reaper/outcomes/05-handle-invalid-config-in-raf-config.md +46 -0
- package/RAF/ahtahs-token-reaper/outcomes/06-fix-verbose-toggle-timer-display.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/01-extend-token-tracker-data-model.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/02-accumulate-usage-in-retry-loop.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/03-per-attempt-display-formatting.md +43 -0
- package/RAF/ahtahs-token-reaper/plans/04-add-model-name-to-claude-call-logs.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/05-handle-invalid-config-in-raf-config.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/06-fix-verbose-toggle-timer-display.md +40 -0
- package/README.md +34 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +195 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +55 -7
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +5 -3
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +19 -2
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +43 -96
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +6 -3
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +10 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/pull-request.d.ts +1 -1
- package/dist/core/pull-request.d.ts.map +1 -1
- package/dist/core/pull-request.js +9 -4
- package/dist/core/pull-request.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +16 -1
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +34 -4
- package/dist/parsers/stream-renderer.js.map +1 -1
- package/dist/prompts/execution.d.ts.map +1 -1
- package/dist/prompts/execution.js +11 -1
- package/dist/prompts/execution.js.map +1 -1
- package/dist/types/config.d.ts +95 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +63 -3
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +65 -7
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +297 -21
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/name-generator.d.ts +3 -7
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +75 -61
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +25 -0
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +87 -0
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +55 -0
- package/dist/utils/token-tracker.d.ts.map +1 -0
- package/dist/utils/token-tracker.js +142 -0
- package/dist/utils/token-tracker.js.map +1 -0
- package/dist/utils/validation.d.ts +5 -5
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +10 -6
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/verbose-toggle.d.ts +33 -0
- package/dist/utils/verbose-toggle.d.ts.map +1 -0
- package/dist/utils/verbose-toggle.js +94 -0
- package/dist/utils/verbose-toggle.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/config.ts +230 -0
- package/src/commands/do.ts +64 -6
- package/src/commands/plan.ts +5 -3
- package/src/core/claude-runner.ts +59 -115
- package/src/core/failure-analyzer.ts +6 -3
- package/src/core/git.ts +10 -3
- package/src/core/pull-request.ts +9 -4
- package/src/index.ts +2 -0
- package/src/parsers/stream-renderer.ts +54 -4
- package/src/prompts/config-docs.md +331 -0
- package/src/prompts/execution.ts +13 -1
- package/src/types/config.ts +156 -7
- package/src/utils/config.ts +357 -21
- package/src/utils/name-generator.ts +84 -71
- package/src/utils/terminal-symbols.ts +103 -0
- package/src/utils/token-tracker.ts +177 -0
- package/src/utils/validation.ts +15 -10
- package/src/utils/verbose-toggle.ts +103 -0
- package/tests/unit/claude-runner.test.ts +171 -7
- package/tests/unit/config-command.test.ts +242 -0
- package/tests/unit/config.test.ts +632 -30
- package/tests/unit/name-generator.test.ts +99 -75
- package/tests/unit/pull-request.test.ts +2 -0
- package/tests/unit/stream-renderer.test.ts +83 -0
- package/tests/unit/terminal-symbols.test.ts +245 -0
- package/tests/unit/timer-verbose-integration.test.ts +170 -0
- package/tests/unit/token-tracker.test.ts +685 -0
- package/tests/unit/verbose-toggle.test.ts +204 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { createConfigCommand } from '../../src/commands/config.js';
|
|
6
|
+
import {
|
|
7
|
+
validateConfig,
|
|
8
|
+
ConfigValidationError,
|
|
9
|
+
resolveConfig,
|
|
10
|
+
getModel,
|
|
11
|
+
getEffort,
|
|
12
|
+
resetConfigCache,
|
|
13
|
+
} from '../../src/utils/config.js';
|
|
14
|
+
import { DEFAULT_CONFIG } from '../../src/types/config.js';
|
|
15
|
+
|
|
16
|
+
describe('Config Command', () => {
|
|
17
|
+
let tempDir: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-config-cmd-test-'));
|
|
21
|
+
resetConfigCache();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
26
|
+
resetConfigCache();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Command setup', () => {
|
|
30
|
+
it('should create a command named "config"', () => {
|
|
31
|
+
const cmd = createConfigCommand();
|
|
32
|
+
expect(cmd.name()).toBe('config');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should have a description', () => {
|
|
36
|
+
const cmd = createConfigCommand();
|
|
37
|
+
expect(cmd.description()).toBeTruthy();
|
|
38
|
+
expect(cmd.description()).toContain('config');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should accept a variadic prompt argument', () => {
|
|
42
|
+
const cmd = createConfigCommand();
|
|
43
|
+
const args = cmd.registeredArguments;
|
|
44
|
+
expect(args.length).toBe(1);
|
|
45
|
+
expect(args[0]!.variadic).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should have a --reset option', () => {
|
|
49
|
+
const cmd = createConfigCommand();
|
|
50
|
+
const resetOption = cmd.options.find((o) => o.long === '--reset');
|
|
51
|
+
expect(resetOption).toBeDefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should register in a parent program', () => {
|
|
55
|
+
const program = new Command();
|
|
56
|
+
program.addCommand(createConfigCommand());
|
|
57
|
+
const configCmd = program.commands.find((c) => c.name() === 'config');
|
|
58
|
+
expect(configCmd).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Post-session validation logic', () => {
|
|
63
|
+
it('should accept valid config with model override', () => {
|
|
64
|
+
const config = { models: { execute: 'sonnet' } };
|
|
65
|
+
expect(() => validateConfig(config)).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should accept valid config with effort override', () => {
|
|
69
|
+
const config = { effort: { plan: 'low' } };
|
|
70
|
+
expect(() => validateConfig(config)).not.toThrow();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should accept valid config with timeout', () => {
|
|
74
|
+
const config = { timeout: 120 };
|
|
75
|
+
expect(() => validateConfig(config)).not.toThrow();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should reject config with unknown keys', () => {
|
|
79
|
+
const config = { unknownKey: true };
|
|
80
|
+
expect(() => validateConfig(config)).toThrow(ConfigValidationError);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should reject config with invalid model name', () => {
|
|
84
|
+
const config = { models: { execute: 'gpt-4' } };
|
|
85
|
+
expect(() => validateConfig(config)).toThrow(ConfigValidationError);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should reject config with invalid effort level', () => {
|
|
89
|
+
const config = { effort: { plan: 'max' } };
|
|
90
|
+
expect(() => validateConfig(config)).toThrow(ConfigValidationError);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should reject non-object config', () => {
|
|
94
|
+
expect(() => validateConfig('string')).toThrow(ConfigValidationError);
|
|
95
|
+
expect(() => validateConfig(null)).toThrow(ConfigValidationError);
|
|
96
|
+
expect(() => validateConfig([])).toThrow(ConfigValidationError);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should accept an empty config (all defaults)', () => {
|
|
100
|
+
expect(() => validateConfig({})).not.toThrow();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Reset flow - file operations', () => {
|
|
105
|
+
it('should be able to delete config file', () => {
|
|
106
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
107
|
+
fs.writeFileSync(configPath, JSON.stringify({ timeout: 90 }, null, 2));
|
|
108
|
+
expect(fs.existsSync(configPath)).toBe(true);
|
|
109
|
+
|
|
110
|
+
fs.unlinkSync(configPath);
|
|
111
|
+
expect(fs.existsSync(configPath)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle non-existent config file gracefully', () => {
|
|
115
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
116
|
+
expect(fs.existsSync(configPath)).toBe(false);
|
|
117
|
+
// Reset when no file exists should not throw
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Config file round-trip', () => {
|
|
122
|
+
it('should write and read valid config', () => {
|
|
123
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
124
|
+
const config = { models: { execute: 'sonnet' as const }, timeout: 90 };
|
|
125
|
+
|
|
126
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
127
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
128
|
+
const parsed = JSON.parse(content);
|
|
129
|
+
|
|
130
|
+
expect(parsed.models.execute).toBe('sonnet');
|
|
131
|
+
expect(parsed.timeout).toBe(90);
|
|
132
|
+
expect(() => validateConfig(parsed)).not.toThrow();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should detect invalid JSON after write', () => {
|
|
136
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
137
|
+
fs.writeFileSync(configPath, '{ invalid json }}}');
|
|
138
|
+
|
|
139
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
140
|
+
expect(() => JSON.parse(content)).toThrow(SyntaxError);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should detect validation errors after write', () => {
|
|
144
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
145
|
+
fs.writeFileSync(configPath, JSON.stringify({ badKey: true }, null, 2));
|
|
146
|
+
|
|
147
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
148
|
+
const parsed = JSON.parse(content);
|
|
149
|
+
expect(() => validateConfig(parsed)).toThrow(ConfigValidationError);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('System prompt construction', () => {
|
|
154
|
+
it('should indicate no config when file does not exist', () => {
|
|
155
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
156
|
+
const exists = fs.existsSync(configPath);
|
|
157
|
+
const state = exists
|
|
158
|
+
? fs.readFileSync(configPath, 'utf-8')
|
|
159
|
+
: 'No config file exists yet.';
|
|
160
|
+
expect(state).toContain('No config file');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should include config contents when file exists', () => {
|
|
164
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
165
|
+
const config = { timeout: 120, worktree: true };
|
|
166
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
167
|
+
|
|
168
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
169
|
+
expect(content).toContain('"timeout": 120');
|
|
170
|
+
expect(content).toContain('"worktree": true');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Error recovery - invalid config fallback', () => {
|
|
175
|
+
// These tests verify the behaviors that runConfigSession relies on for error recovery
|
|
176
|
+
// The config command catches errors from getModel/getEffort and falls back to defaults
|
|
177
|
+
|
|
178
|
+
it('should throw on invalid JSON when resolving config', () => {
|
|
179
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
180
|
+
fs.writeFileSync(configPath, '{ invalid json }}}');
|
|
181
|
+
|
|
182
|
+
expect(() => resolveConfig(configPath)).toThrow(SyntaxError);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should throw on schema validation failure when resolving config', () => {
|
|
186
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
187
|
+
fs.writeFileSync(configPath, JSON.stringify({ unknownKey: true }));
|
|
188
|
+
|
|
189
|
+
expect(() => resolveConfig(configPath)).toThrow(ConfigValidationError);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should have valid default fallback values for config scenario', () => {
|
|
193
|
+
// These are the values that runConfigSession uses when config loading fails
|
|
194
|
+
expect(DEFAULT_CONFIG.models.config).toBe('sonnet');
|
|
195
|
+
expect(DEFAULT_CONFIG.effort.config).toBe('medium');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should be able to read raw file contents even when config is invalid JSON', () => {
|
|
199
|
+
// This verifies that getCurrentConfigState can still read the broken file
|
|
200
|
+
// so Claude can see and help fix it
|
|
201
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
202
|
+
const invalidContent = '{ "broken": true, }'; // trailing comma = invalid
|
|
203
|
+
fs.writeFileSync(configPath, invalidContent);
|
|
204
|
+
|
|
205
|
+
// File is readable even though it's invalid JSON
|
|
206
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
207
|
+
expect(content).toBe(invalidContent);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should be able to read raw file contents even when config fails schema validation', () => {
|
|
211
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
212
|
+
const invalidContent = JSON.stringify({ badKey: 'value' }, null, 2);
|
|
213
|
+
fs.writeFileSync(configPath, invalidContent);
|
|
214
|
+
|
|
215
|
+
// File is readable even though it fails validation
|
|
216
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
217
|
+
expect(JSON.parse(content)).toEqual({ badKey: 'value' });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('resetConfigCache should clear the cached config', () => {
|
|
221
|
+
// This is used by runConfigSession to clear a broken cached config
|
|
222
|
+
// so subsequent operations don't fail
|
|
223
|
+
const configPath = path.join(tempDir, 'valid.json');
|
|
224
|
+
fs.writeFileSync(configPath, JSON.stringify({ timeout: 99 }));
|
|
225
|
+
|
|
226
|
+
// Load the config
|
|
227
|
+
const config1 = resolveConfig(configPath);
|
|
228
|
+
expect(config1.timeout).toBe(99);
|
|
229
|
+
|
|
230
|
+
// Write different content
|
|
231
|
+
fs.writeFileSync(configPath, JSON.stringify({ timeout: 120 }));
|
|
232
|
+
|
|
233
|
+
// Without reset, we'd still get cached value (but resolveConfig doesn't use cache)
|
|
234
|
+
// This test verifies resetConfigCache exists and can be called
|
|
235
|
+
resetConfigCache();
|
|
236
|
+
|
|
237
|
+
// After reset, we should get new value
|
|
238
|
+
const config2 = resolveConfig(configPath);
|
|
239
|
+
expect(config2.timeout).toBe(120);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|