@claudetree/cli 0.5.0 → 0.5.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 (111) hide show
  1. package/dist/commands/log.d.ts +3 -0
  2. package/dist/commands/log.d.ts.map +1 -0
  3. package/dist/commands/log.js +116 -0
  4. package/dist/commands/log.js.map +1 -0
  5. package/dist/index.js +6 -1
  6. package/dist/index.js.map +1 -1
  7. package/package.json +7 -4
  8. package/dist/commands/batch.test.d.ts +0 -2
  9. package/dist/commands/batch.test.d.ts.map +0 -1
  10. package/dist/commands/batch.test.js +0 -230
  11. package/dist/commands/batch.test.js.map +0 -1
  12. package/dist/commands/bustercall/issueFilters.test.d.ts +0 -2
  13. package/dist/commands/bustercall/issueFilters.test.d.ts.map +0 -1
  14. package/dist/commands/bustercall/issueFilters.test.js +0 -151
  15. package/dist/commands/bustercall/issueFilters.test.js.map +0 -1
  16. package/dist/commands/bustercall/sessionManager.test.d.ts +0 -2
  17. package/dist/commands/bustercall/sessionManager.test.d.ts.map +0 -1
  18. package/dist/commands/bustercall/sessionManager.test.js +0 -79
  19. package/dist/commands/bustercall/sessionManager.test.js.map +0 -1
  20. package/dist/commands/bustercall/statusDisplay.test.d.ts +0 -2
  21. package/dist/commands/bustercall/statusDisplay.test.d.ts.map +0 -1
  22. package/dist/commands/bustercall/statusDisplay.test.js +0 -111
  23. package/dist/commands/bustercall/statusDisplay.test.js.map +0 -1
  24. package/dist/commands/bustercall.test.d.ts +0 -2
  25. package/dist/commands/bustercall.test.d.ts.map +0 -1
  26. package/dist/commands/bustercall.test.js +0 -258
  27. package/dist/commands/bustercall.test.js.map +0 -1
  28. package/dist/commands/chain/chainDisplay.test.d.ts +0 -2
  29. package/dist/commands/chain/chainDisplay.test.d.ts.map +0 -1
  30. package/dist/commands/chain/chainDisplay.test.js +0 -128
  31. package/dist/commands/chain/chainDisplay.test.js.map +0 -1
  32. package/dist/commands/chain/chainSession.test.d.ts +0 -2
  33. package/dist/commands/chain/chainSession.test.d.ts.map +0 -1
  34. package/dist/commands/chain/chainSession.test.js +0 -93
  35. package/dist/commands/chain/chainSession.test.js.map +0 -1
  36. package/dist/commands/chain.test.d.ts +0 -2
  37. package/dist/commands/chain.test.d.ts.map +0 -1
  38. package/dist/commands/chain.test.js +0 -216
  39. package/dist/commands/chain.test.js.map +0 -1
  40. package/dist/commands/clean.test.d.ts +0 -2
  41. package/dist/commands/clean.test.d.ts.map +0 -1
  42. package/dist/commands/clean.test.js +0 -193
  43. package/dist/commands/clean.test.js.map +0 -1
  44. package/dist/commands/demo.d.ts +0 -3
  45. package/dist/commands/demo.d.ts.map +0 -1
  46. package/dist/commands/demo.js +0 -175
  47. package/dist/commands/demo.js.map +0 -1
  48. package/dist/commands/demo.test.d.ts +0 -2
  49. package/dist/commands/demo.test.d.ts.map +0 -1
  50. package/dist/commands/demo.test.js +0 -34
  51. package/dist/commands/demo.test.js.map +0 -1
  52. package/dist/commands/doctor.test.d.ts +0 -2
  53. package/dist/commands/doctor.test.d.ts.map +0 -1
  54. package/dist/commands/doctor.test.js +0 -324
  55. package/dist/commands/doctor.test.js.map +0 -1
  56. package/dist/commands/init.test.d.ts +0 -2
  57. package/dist/commands/init.test.d.ts.map +0 -1
  58. package/dist/commands/init.test.js +0 -94
  59. package/dist/commands/init.test.js.map +0 -1
  60. package/dist/commands/list.test.d.ts +0 -2
  61. package/dist/commands/list.test.d.ts.map +0 -1
  62. package/dist/commands/list.test.js +0 -106
  63. package/dist/commands/list.test.js.map +0 -1
  64. package/dist/commands/resume.test.d.ts +0 -2
  65. package/dist/commands/resume.test.d.ts.map +0 -1
  66. package/dist/commands/resume.test.js +0 -362
  67. package/dist/commands/resume.test.js.map +0 -1
  68. package/dist/commands/start/buildPrompt.test.d.ts +0 -2
  69. package/dist/commands/start/buildPrompt.test.d.ts.map +0 -1
  70. package/dist/commands/start/buildPrompt.test.js +0 -182
  71. package/dist/commands/start/buildPrompt.test.js.map +0 -1
  72. package/dist/commands/start/createWorktree.test.d.ts +0 -2
  73. package/dist/commands/start/createWorktree.test.d.ts.map +0 -1
  74. package/dist/commands/start/createWorktree.test.js +0 -81
  75. package/dist/commands/start/createWorktree.test.js.map +0 -1
  76. package/dist/commands/start/parseIssueInput.test.d.ts +0 -2
  77. package/dist/commands/start/parseIssueInput.test.d.ts.map +0 -1
  78. package/dist/commands/start/parseIssueInput.test.js +0 -118
  79. package/dist/commands/start/parseIssueInput.test.js.map +0 -1
  80. package/dist/commands/start/progressTracker.test.d.ts +0 -2
  81. package/dist/commands/start/progressTracker.test.d.ts.map +0 -1
  82. package/dist/commands/start/progressTracker.test.js +0 -100
  83. package/dist/commands/start/progressTracker.test.js.map +0 -1
  84. package/dist/commands/start/validationGates.test.d.ts +0 -2
  85. package/dist/commands/start/validationGates.test.d.ts.map +0 -1
  86. package/dist/commands/start/validationGates.test.js +0 -66
  87. package/dist/commands/start/validationGates.test.js.map +0 -1
  88. package/dist/commands/start.test.d.ts +0 -2
  89. package/dist/commands/start.test.d.ts.map +0 -1
  90. package/dist/commands/start.test.js +0 -260
  91. package/dist/commands/start.test.js.map +0 -1
  92. package/dist/commands/stats.test.d.ts +0 -2
  93. package/dist/commands/stats.test.d.ts.map +0 -1
  94. package/dist/commands/stats.test.js +0 -127
  95. package/dist/commands/stats.test.js.map +0 -1
  96. package/dist/commands/status.test.d.ts +0 -2
  97. package/dist/commands/status.test.d.ts.map +0 -1
  98. package/dist/commands/status.test.js +0 -172
  99. package/dist/commands/status.test.js.map +0 -1
  100. package/dist/commands/stop.test.d.ts +0 -2
  101. package/dist/commands/stop.test.d.ts.map +0 -1
  102. package/dist/commands/stop.test.js +0 -152
  103. package/dist/commands/stop.test.js.map +0 -1
  104. package/dist/commands/tdd.d.ts +0 -3
  105. package/dist/commands/tdd.d.ts.map +0 -1
  106. package/dist/commands/tdd.js +0 -472
  107. package/dist/commands/tdd.js.map +0 -1
  108. package/dist/commands/web.test.d.ts +0 -2
  109. package/dist/commands/web.test.d.ts.map +0 -1
  110. package/dist/commands/web.test.js +0 -133
  111. package/dist/commands/web.test.js.map +0 -1
@@ -1,152 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { mkdtemp, rm, mkdir } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- // Create mock functions
6
- const mockFindAll = vi.fn();
7
- const mockSave = vi.fn();
8
- // Mock FileSessionRepository
9
- vi.mock('@claudetree/core', () => ({
10
- FileSessionRepository: vi.fn().mockImplementation(() => ({
11
- findAll: mockFindAll,
12
- save: mockSave,
13
- })),
14
- }));
15
- // Import after mock
16
- import { stopCommand } from './stop.js';
17
- describe('stopCommand', () => {
18
- let testDir;
19
- let originalCwd;
20
- let originalExit;
21
- let consoleLogSpy;
22
- let consoleErrorSpy;
23
- const createMockSession = (overrides = {}) => ({
24
- id: 'test-session-id-123',
25
- worktreeId: 'worktree-id-456',
26
- claudeSessionId: null,
27
- status: 'running',
28
- issueNumber: 42,
29
- prompt: 'Fix the bug',
30
- createdAt: new Date('2024-01-15'),
31
- updatedAt: new Date('2024-01-15'),
32
- processId: null,
33
- osProcessId: null,
34
- lastHeartbeat: null,
35
- errorCount: 0,
36
- worktreePath: '/path/to/worktree',
37
- usage: null,
38
- progress: null,
39
- ...overrides,
40
- });
41
- beforeEach(async () => {
42
- testDir = await mkdtemp(join(tmpdir(), 'claudetree-stop-test-'));
43
- originalCwd = process.cwd();
44
- process.chdir(testDir);
45
- originalExit = process.exit;
46
- process.exit = vi.fn();
47
- consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
48
- consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
49
- mockFindAll.mockReset();
50
- mockSave.mockReset();
51
- });
52
- afterEach(async () => {
53
- process.chdir(originalCwd);
54
- process.exit = originalExit;
55
- consoleLogSpy.mockRestore();
56
- consoleErrorSpy.mockRestore();
57
- vi.clearAllMocks();
58
- await rm(testDir, { recursive: true, force: true });
59
- });
60
- describe('when not initialized', () => {
61
- it('should display error and exit with code 1', async () => {
62
- const exitError = new Error('process.exit called');
63
- process.exit.mockImplementation(() => {
64
- throw exitError;
65
- });
66
- await expect(stopCommand.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
67
- expect(consoleErrorSpy).toHaveBeenCalledWith('Error: claudetree not initialized. Run "claudetree init" first.');
68
- expect(process.exit).toHaveBeenCalledWith(1);
69
- });
70
- });
71
- describe('when initialized', () => {
72
- beforeEach(async () => {
73
- await mkdir(join(testDir, '.claudetree'), { recursive: true });
74
- });
75
- describe('when no sessions exist', () => {
76
- it('should display "No active sessions" message', async () => {
77
- mockFindAll.mockResolvedValue([]);
78
- await stopCommand.parseAsync(['node', 'test']);
79
- expect(consoleLogSpy).toHaveBeenCalledWith('No active sessions.');
80
- });
81
- });
82
- describe('with session-id argument', () => {
83
- it('should stop matching session by ID prefix', async () => {
84
- const session = createMockSession({ id: 'test-session-abc123' });
85
- mockFindAll.mockResolvedValue([session]);
86
- mockSave.mockResolvedValue(undefined);
87
- await stopCommand.parseAsync(['node', 'test', 'test-ses']);
88
- expect(mockSave).toHaveBeenCalledWith(expect.objectContaining({
89
- id: 'test-session-abc123',
90
- status: 'completed',
91
- }));
92
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Stopping session: test-ses'));
93
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Stopped 1 session(s)'));
94
- });
95
- it('should error when no matching session found', async () => {
96
- const session = createMockSession({ id: 'test-session-abc123' });
97
- mockFindAll.mockResolvedValue([session]);
98
- const exitError = new Error('process.exit called');
99
- process.exit.mockImplementation(() => {
100
- throw exitError;
101
- });
102
- await expect(stopCommand.parseAsync(['node', 'test', 'nonexistent'])).rejects.toThrow('process.exit called');
103
- expect(consoleErrorSpy).toHaveBeenCalledWith('No session found matching: nonexistent');
104
- expect(process.exit).toHaveBeenCalledWith(1);
105
- });
106
- });
107
- describe('with --all option', () => {
108
- it('should stop all sessions', async () => {
109
- const sessions = [
110
- createMockSession({ id: 'session-1' }),
111
- createMockSession({ id: 'session-2' }),
112
- createMockSession({ id: 'session-3' }),
113
- ];
114
- mockFindAll.mockResolvedValue(sessions);
115
- mockSave.mockResolvedValue(undefined);
116
- await stopCommand.parseAsync(['node', 'test', '--all']);
117
- expect(mockSave).toHaveBeenCalledTimes(3);
118
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Stopped 3 session(s)'));
119
- });
120
- });
121
- describe('session status update', () => {
122
- it('should update session status to completed', async () => {
123
- const session = createMockSession({
124
- id: 'test-session',
125
- status: 'running',
126
- });
127
- mockFindAll.mockResolvedValue([session]);
128
- mockSave.mockResolvedValue(undefined);
129
- await stopCommand.parseAsync(['node', 'test', 'test']);
130
- expect(mockSave).toHaveBeenCalledWith(expect.objectContaining({
131
- status: 'completed',
132
- }));
133
- });
134
- it('should update the updatedAt timestamp', async () => {
135
- const originalDate = new Date('2024-01-01');
136
- const session = createMockSession({
137
- id: 'test-session',
138
- updatedAt: originalDate,
139
- });
140
- mockFindAll.mockResolvedValue([session]);
141
- mockSave.mockResolvedValue(undefined);
142
- await stopCommand.parseAsync(['node', 'test', 'test']);
143
- expect(mockSave).toHaveBeenCalledWith(expect.objectContaining({
144
- updatedAt: expect.any(Date),
145
- }));
146
- const savedSession = mockSave.mock.calls[0][0];
147
- expect(savedSession.updatedAt.getTime()).toBeGreaterThan(originalDate.getTime());
148
- });
149
- });
150
- });
151
- });
152
- //# sourceMappingURL=stop.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stop.test.js","sourceRoot":"","sources":["../../src/commands/stop.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare const tddCommand: Command;
3
- //# sourceMappingURL=tdd.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tdd.d.ts","sourceRoot":"","sources":["../../src/commands/tdd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqHpC,eAAO,MAAM,UAAU,SAgcnB,CAAC"}
@@ -1,472 +0,0 @@
1
- import { Command } from 'commander';
2
- import { join } from 'node:path';
3
- import { randomUUID } from 'node:crypto';
4
- import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
5
- import { GitWorktreeAdapter, ClaudeSessionAdapter, FileSessionRepository, FileEventRepository, GitHubAdapter, ValidationGateRunner, SlackNotifier, } from '@claudetree/core';
6
- const CONFIG_DIR = '.claudetree';
7
- async function loadConfig(cwd) {
8
- try {
9
- const configPath = join(cwd, CONFIG_DIR, 'config.json');
10
- await access(configPath);
11
- const content = await readFile(configPath, 'utf-8');
12
- return JSON.parse(content);
13
- }
14
- catch {
15
- return null;
16
- }
17
- }
18
- function parseGates(gatesStr, testCommand) {
19
- const gateNames = gatesStr.split(',').map(g => g.trim().toLowerCase());
20
- const gates = [];
21
- for (const name of gateNames) {
22
- switch (name) {
23
- case 'test':
24
- gates.push({
25
- name: 'test',
26
- command: testCommand ?? 'pnpm test',
27
- required: true,
28
- });
29
- break;
30
- case 'type':
31
- gates.push({
32
- name: 'type',
33
- command: 'pnpm tsc --noEmit',
34
- required: true,
35
- });
36
- break;
37
- case 'lint':
38
- gates.push({
39
- name: 'lint',
40
- command: 'pnpm lint',
41
- required: false,
42
- });
43
- break;
44
- case 'build':
45
- gates.push({
46
- name: 'build',
47
- command: 'pnpm build',
48
- required: false,
49
- });
50
- break;
51
- default:
52
- console.warn(`Unknown gate: ${name}, skipping`);
53
- }
54
- }
55
- return gates;
56
- }
57
- function formatDuration(ms) {
58
- const seconds = Math.floor(ms / 1000);
59
- const minutes = Math.floor(seconds / 60);
60
- const hours = Math.floor(minutes / 60);
61
- if (hours > 0) {
62
- return `${hours}h ${minutes % 60}m`;
63
- }
64
- if (minutes > 0) {
65
- return `${minutes}m ${seconds % 60}s`;
66
- }
67
- return `${seconds}s`;
68
- }
69
- export const tddCommand = new Command('tdd')
70
- .description('Run TDD mode: Write tests first, then implement with validation gates')
71
- .argument('<issue>', 'Issue number, GitHub URL, or task name')
72
- .option('-p, --prompt <prompt>', 'Additional prompt for Claude')
73
- .option('--timeout <minutes>', 'Total session timeout in minutes', '120')
74
- .option('--idle-timeout <minutes>', 'Idle timeout (no output) in minutes', '10')
75
- .option('--max-iterations <n>', 'Maximum TDD iterations', '10')
76
- .option('--max-retries <n>', 'Maximum retries per gate', '3')
77
- .option('--gates <gates>', 'Validation gates (comma-separated: test,type,lint,build)', 'test,type')
78
- .option('--test-command <cmd>', 'Custom test command', 'pnpm test')
79
- .option('-b, --branch <branch>', 'Custom branch name')
80
- .option('-t, --token <token>', 'GitHub token')
81
- .option('--dry-run', 'Show what would be done without starting', false)
82
- .action(async (issue, options) => {
83
- const cwd = process.cwd();
84
- const config = await loadConfig(cwd);
85
- if (!config) {
86
- console.error('Error: claudetree not initialized. Run "claudetree init" first.');
87
- process.exit(1);
88
- }
89
- // Build TDD config
90
- const tddConfig = {
91
- timeout: (parseInt(String(options.timeout ?? '120'), 10)) * 60 * 1000,
92
- idleTimeout: (parseInt(String(options.idleTimeout ?? '10'), 10)) * 60 * 1000,
93
- maxIterations: parseInt(String(options.maxIterations ?? '10'), 10),
94
- maxRetries: parseInt(String(options.maxRetries ?? '3'), 10),
95
- gates: parseGates(options.gates ?? 'test,type', options.testCommand),
96
- };
97
- console.log('\n\x1b[36m╔══════════════════════════════════════════╗\x1b[0m');
98
- console.log('\x1b[36m║ TDD Mode Session ║\x1b[0m');
99
- console.log('\x1b[36m╚══════════════════════════════════════════╝\x1b[0m\n');
100
- console.log('\x1b[33m⏱️ Time Limits:\x1b[0m');
101
- console.log(` Session timeout: ${formatDuration(tddConfig.timeout)}`);
102
- console.log(` Idle timeout: ${formatDuration(tddConfig.idleTimeout)}`);
103
- console.log(` Max iterations: ${tddConfig.maxIterations}`);
104
- console.log(` Max retries: ${tddConfig.maxRetries}`);
105
- console.log('\n\x1b[33m✅ Validation Gates:\x1b[0m');
106
- for (const gate of tddConfig.gates) {
107
- const status = gate.required ? '\x1b[31m(required)\x1b[0m' : '\x1b[90m(optional)\x1b[0m';
108
- console.log(` • ${gate.name}: ${gate.command} ${status}`);
109
- }
110
- if (options.dryRun) {
111
- console.log('\n\x1b[33m[Dry Run]\x1b[0m Would start TDD session with above config.');
112
- return;
113
- }
114
- // Resolve issue
115
- let issueNumber = null;
116
- let issueData = null;
117
- let branchName;
118
- const ghToken = options.token ?? process.env.GITHUB_TOKEN ?? config.github?.token;
119
- if (issue.includes('github.com')) {
120
- if (!ghToken) {
121
- console.error('\nError: GitHub token required for URL.');
122
- process.exit(1);
123
- }
124
- const ghAdapter = new GitHubAdapter(ghToken);
125
- const parsed = ghAdapter.parseIssueUrl(issue);
126
- if (!parsed) {
127
- console.error('Error: Invalid GitHub URL format.');
128
- process.exit(1);
129
- }
130
- console.log(`\nFetching issue #${parsed.number}...`);
131
- try {
132
- issueData = await ghAdapter.getIssue(parsed.owner, parsed.repo, parsed.number);
133
- issueNumber = issueData.number;
134
- branchName = options.branch ?? ghAdapter.generateBranchName(issueNumber, issueData.title);
135
- console.log(` Title: ${issueData.title}`);
136
- }
137
- catch (error) {
138
- console.error(`Error: Failed to fetch issue.`);
139
- process.exit(1);
140
- }
141
- }
142
- else {
143
- const parsed = parseInt(issue, 10);
144
- const isNumber = !isNaN(parsed);
145
- if (isNumber && ghToken && config.github?.owner && config.github?.repo) {
146
- const ghAdapter = new GitHubAdapter(ghToken);
147
- try {
148
- console.log(`\nFetching issue #${parsed}...`);
149
- issueData = await ghAdapter.getIssue(config.github.owner, config.github.repo, parsed);
150
- issueNumber = issueData.number;
151
- branchName = options.branch ?? ghAdapter.generateBranchName(issueNumber, issueData.title);
152
- console.log(` Title: ${issueData.title}`);
153
- }
154
- catch {
155
- issueNumber = parsed;
156
- branchName = options.branch ?? `issue-${issueNumber}`;
157
- }
158
- }
159
- else if (isNumber) {
160
- issueNumber = parsed;
161
- branchName = options.branch ?? `issue-${issueNumber}`;
162
- }
163
- else {
164
- branchName = options.branch ?? `task-${issue}`;
165
- }
166
- }
167
- const worktreePath = join(cwd, config.worktreeDir, branchName);
168
- // Create or use existing worktree
169
- const gitAdapter = new GitWorktreeAdapter(cwd);
170
- const existingWorktrees = await gitAdapter.list();
171
- const existingWorktree = existingWorktrees.find((wt) => wt.branch === branchName || wt.path.endsWith(branchName));
172
- let worktree;
173
- if (existingWorktree) {
174
- console.log(`\nUsing existing worktree: ${branchName}`);
175
- worktree = {
176
- id: randomUUID(),
177
- path: existingWorktree.path,
178
- branch: existingWorktree.branch,
179
- };
180
- }
181
- else {
182
- console.log(`\nCreating worktree: ${branchName}`);
183
- try {
184
- worktree = await gitAdapter.create({
185
- path: worktreePath,
186
- branch: branchName,
187
- issueNumber: issueNumber ?? undefined,
188
- });
189
- }
190
- catch (error) {
191
- if (error instanceof Error) {
192
- console.error(`Error: ${error.message}`);
193
- }
194
- process.exit(1);
195
- }
196
- }
197
- console.log(` Path: ${worktree.path}`);
198
- // Check Claude availability
199
- const claudeAdapter = new ClaudeSessionAdapter();
200
- const available = await claudeAdapter.isClaudeAvailable();
201
- if (!available) {
202
- console.error('\nError: Claude CLI not found.');
203
- process.exit(1);
204
- }
205
- // Create session
206
- const sessionRepo = new FileSessionRepository(join(cwd, CONFIG_DIR));
207
- const eventRepo = new FileEventRepository(join(cwd, CONFIG_DIR));
208
- const session = {
209
- id: randomUUID(),
210
- worktreeId: worktree.id,
211
- claudeSessionId: null,
212
- status: 'pending',
213
- issueNumber,
214
- prompt: options.prompt ?? null,
215
- createdAt: new Date(),
216
- updatedAt: new Date(),
217
- processId: null,
218
- osProcessId: null,
219
- lastHeartbeat: null,
220
- errorCount: 0,
221
- worktreePath: worktree.path,
222
- usage: null,
223
- progress: {
224
- currentStep: 'analyzing',
225
- completedSteps: [],
226
- startedAt: new Date(),
227
- },
228
- };
229
- await sessionRepo.save(session);
230
- // Initialize TDD state
231
- const tddState = {
232
- phase: 'initializing',
233
- currentIteration: 0,
234
- gateResults: [],
235
- failureCount: 0,
236
- lastActivity: new Date(),
237
- config: tddConfig,
238
- };
239
- // Save TDD state
240
- const tddStatePath = join(cwd, CONFIG_DIR, 'tdd-state', `${session.id}.json`);
241
- await mkdir(join(cwd, CONFIG_DIR, 'tdd-state'), { recursive: true });
242
- await writeFile(tddStatePath, JSON.stringify(tddState, null, 2));
243
- // Build TDD prompt
244
- const tddSystemPrompt = `You are in TDD (Test-Driven Development) mode. Follow this STRICT workflow:
245
-
246
- ## TDD Cycle (Repeat until done)
247
-
248
- ### 1. RED Phase - Write Failing Test
249
- - Write ONE failing test that describes the expected behavior
250
- - Run the test to confirm it fails
251
- - Commit: "test: add test for <feature>"
252
-
253
- ### 2. GREEN Phase - Minimal Implementation
254
- - Write the MINIMUM code to make the test pass
255
- - Run tests to confirm they pass
256
- - Commit: "feat: implement <feature>"
257
-
258
- ### 3. REFACTOR Phase (Optional)
259
- - Clean up code while keeping tests green
260
- - Commit: "refactor: improve <description>"
261
-
262
- ## Rules
263
- - NEVER write implementation before tests
264
- - ONE test at a time
265
- - Run tests after EVERY change
266
- - Stop when all requirements are met
267
-
268
- ## Validation Gates (Must Pass)
269
- ${tddConfig.gates.map(g => `- ${g.name}: \`${g.command}\` ${g.required ? '(REQUIRED)' : '(optional)'}`).join('\n')}
270
-
271
- ## Time Limits
272
- - Total: ${formatDuration(tddConfig.timeout)}
273
- - Idle: ${formatDuration(tddConfig.idleTimeout)}
274
- - Max iterations: ${tddConfig.maxIterations}
275
-
276
- When done, create a PR to the develop branch.`;
277
- let issuePrompt;
278
- if (issueData) {
279
- issuePrompt = `Issue #${issueNumber}: "${issueData.title}"
280
-
281
- Description:
282
- ${issueData.body || 'No description provided.'}
283
-
284
- ${options.prompt ? `Additional context: ${options.prompt}` : ''}
285
-
286
- Start with TDD - write a failing test first!`;
287
- }
288
- else if (issueNumber) {
289
- issuePrompt = `Working on issue #${issueNumber}. Start with TDD - write a failing test first!`;
290
- }
291
- else {
292
- issuePrompt = `Working on ${branchName}. Start with TDD - write a failing test first!`;
293
- }
294
- console.log('\n\x1b[36m🧪 Starting TDD Session...\x1b[0m');
295
- console.log(` Session: ${session.id.slice(0, 8)}`);
296
- // Track timeouts
297
- const sessionStartTime = Date.now();
298
- let lastOutputTime = Date.now();
299
- let sessionTimedOut = false;
300
- let idleTimedOut = false;
301
- // Timeout checker
302
- const timeoutChecker = setInterval(() => {
303
- const elapsed = Date.now() - sessionStartTime;
304
- const idleTime = Date.now() - lastOutputTime;
305
- if (elapsed >= tddConfig.timeout) {
306
- sessionTimedOut = true;
307
- console.log(`\n\x1b[31m[Timeout]\x1b[0m Session timeout (${formatDuration(tddConfig.timeout)}) exceeded.`);
308
- clearInterval(timeoutChecker);
309
- }
310
- else if (idleTime >= tddConfig.idleTimeout) {
311
- idleTimedOut = true;
312
- console.log(`\n\x1b[31m[Timeout]\x1b[0m Idle timeout (${formatDuration(tddConfig.idleTimeout)}) exceeded.`);
313
- clearInterval(timeoutChecker);
314
- }
315
- }, 5000);
316
- // Setup event listener
317
- claudeAdapter.on('output', async (event) => {
318
- const { output } = event;
319
- lastOutputTime = Date.now();
320
- let eventType = 'output';
321
- if (output.type === 'tool_use')
322
- eventType = 'tool_call';
323
- else if (output.type === 'error')
324
- eventType = 'error';
325
- try {
326
- await eventRepo.append({
327
- id: randomUUID(),
328
- sessionId: session.id,
329
- type: eventType,
330
- content: output.content,
331
- timestamp: output.timestamp,
332
- });
333
- }
334
- catch {
335
- // Ignore
336
- }
337
- });
338
- // Start Claude session
339
- const result = await claudeAdapter.start({
340
- workingDir: worktree.path,
341
- prompt: issuePrompt,
342
- systemPrompt: tddSystemPrompt,
343
- allowedTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'],
344
- });
345
- session.processId = result.processId;
346
- session.osProcessId = result.osProcessId;
347
- session.status = 'running';
348
- session.lastHeartbeat = new Date();
349
- await sessionRepo.save(session);
350
- // Update TDD state
351
- tddState.phase = 'writing_test';
352
- tddState.currentIteration = 1;
353
- await writeFile(tddStatePath, JSON.stringify(tddState, null, 2));
354
- // Graceful shutdown
355
- const handleShutdown = async () => {
356
- console.log('\n[Info] Pausing TDD session...');
357
- clearInterval(timeoutChecker);
358
- session.status = 'paused';
359
- tddState.phase = 'failed';
360
- await sessionRepo.save(session);
361
- await writeFile(tddStatePath, JSON.stringify(tddState, null, 2));
362
- console.log(`Resume with: claudetree resume ${session.id.slice(0, 8)}`);
363
- process.exit(0);
364
- };
365
- process.on('SIGINT', handleShutdown);
366
- process.on('SIGTERM', handleShutdown);
367
- console.log('\nClaude is working in TDD mode...\n');
368
- // Process output
369
- for await (const output of claudeAdapter.getOutput(result.processId)) {
370
- session.lastHeartbeat = new Date();
371
- lastOutputTime = Date.now();
372
- // Check timeouts
373
- if (sessionTimedOut || idleTimedOut) {
374
- await claudeAdapter.stop(result.processId);
375
- session.status = 'failed';
376
- tddState.phase = 'failed';
377
- break;
378
- }
379
- if (output.type === 'text') {
380
- console.log(output.content);
381
- }
382
- else if (output.type === 'tool_use') {
383
- console.log(`\x1b[36m[Tool]\x1b[0m ${output.content}`);
384
- }
385
- else if (output.type === 'error') {
386
- console.error(`\x1b[31m[Error]\x1b[0m ${output.content}`);
387
- }
388
- else if (output.type === 'done') {
389
- console.log(`\x1b[32m[Done]\x1b[0m Session ID: ${output.content}`);
390
- if (output.content) {
391
- session.claudeSessionId = output.content;
392
- }
393
- if (output.usage) {
394
- session.usage = output.usage;
395
- console.log(`\x1b[32m[Usage]\x1b[0m Tokens: ${output.usage.inputTokens} in / ${output.usage.outputTokens} out | Cost: $${output.usage.totalCostUsd.toFixed(4)}`);
396
- }
397
- }
398
- }
399
- clearInterval(timeoutChecker);
400
- // Run validation gates
401
- if (session.status !== 'failed') {
402
- console.log('\n\x1b[36m╔══════════════════════════════════════════╗\x1b[0m');
403
- console.log('\x1b[36m║ Running Validation Gates ║\x1b[0m');
404
- console.log('\x1b[36m╚══════════════════════════════════════════╝\x1b[0m\n');
405
- tddState.phase = 'validating';
406
- await writeFile(tddStatePath, JSON.stringify(tddState, null, 2));
407
- const gateRunner = new ValidationGateRunner();
408
- const gateResults = await gateRunner.runWithAutoRetry(tddConfig.gates, {
409
- cwd: worktree.path,
410
- maxRetries: tddConfig.maxRetries,
411
- onRetry: (attempt, failedGate) => {
412
- console.log(`\x1b[33m[Retry]\x1b[0m Gate '${failedGate}' failed, attempt ${attempt + 1}/${tddConfig.maxRetries}`);
413
- },
414
- });
415
- // Display results
416
- console.log('\n\x1b[33m📊 Gate Results:\x1b[0m');
417
- for (const result of gateResults.results) {
418
- const icon = result.passed ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
419
- const attempts = result.attempts > 1 ? ` (${result.attempts} attempts)` : '';
420
- console.log(` ${icon} ${result.gateName}${attempts}`);
421
- }
422
- console.log(`\n Total time: ${formatDuration(gateResults.totalTime)}`);
423
- tddState.gateResults = gateResults.results;
424
- if (gateResults.allPassed) {
425
- console.log('\n\x1b[32m✅ All validation gates passed!\x1b[0m');
426
- session.status = 'completed';
427
- tddState.phase = 'completed';
428
- }
429
- else {
430
- console.log('\n\x1b[31m❌ Validation gates failed.\x1b[0m');
431
- session.status = 'failed';
432
- tddState.phase = 'failed';
433
- // Show failed gate output
434
- const failedGate = gateResults.results.find(r => !r.passed);
435
- if (failedGate?.output) {
436
- console.log(`\n\x1b[33mFailed gate output (${failedGate.gateName}):\x1b[0m`);
437
- console.log(failedGate.output);
438
- }
439
- }
440
- }
441
- // Final stats
442
- const totalDuration = Date.now() - sessionStartTime;
443
- console.log('\n\x1b[36m╔══════════════════════════════════════════╗\x1b[0m');
444
- console.log('\x1b[36m║ Session Summary ║\x1b[0m');
445
- console.log('\x1b[36m╚══════════════════════════════════════════╝\x1b[0m\n');
446
- console.log(` Status: ${session.status === 'completed' ? '\x1b[32mcompleted\x1b[0m' : '\x1b[31mfailed\x1b[0m'}`);
447
- console.log(` Duration: ${formatDuration(totalDuration)}`);
448
- console.log(` Iterations: ${tddState.currentIteration}`);
449
- if (session.usage) {
450
- console.log(` Cost: $${session.usage.totalCostUsd.toFixed(4)}`);
451
- }
452
- // Save final state
453
- session.updatedAt = new Date();
454
- await sessionRepo.save(session);
455
- await writeFile(tddStatePath, JSON.stringify(tddState, null, 2));
456
- // Slack notification
457
- if (config.slack?.webhookUrl) {
458
- const slack = new SlackNotifier(config.slack.webhookUrl);
459
- await slack.notifySession({
460
- sessionId: session.id,
461
- status: session.status === 'completed' ? 'completed' : 'failed',
462
- issueNumber,
463
- branch: branchName,
464
- worktreePath: worktree.path,
465
- duration: totalDuration,
466
- });
467
- }
468
- if (session.status === 'failed') {
469
- process.exit(1);
470
- }
471
- });
472
- //# sourceMappingURL=tdd.js.map