@claudetree/cli 0.4.2 → 0.4.4
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/dist/commands/chain.d.ts +3 -0
- package/dist/commands/chain.d.ts.map +1 -0
- package/dist/commands/chain.js +284 -0
- package/dist/commands/chain.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +94 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/list.test.d.ts +2 -0
- package/dist/commands/list.test.d.ts.map +1 -0
- package/dist/commands/list.test.js +106 -0
- package/dist/commands/list.test.js.map +1 -0
- package/dist/commands/start/buildPrompt.d.ts +29 -0
- package/dist/commands/start/buildPrompt.d.ts.map +1 -0
- package/dist/commands/start/buildPrompt.js +142 -0
- package/dist/commands/start/buildPrompt.js.map +1 -0
- package/dist/commands/start/buildPrompt.test.d.ts +2 -0
- package/dist/commands/start/buildPrompt.test.d.ts.map +1 -0
- package/dist/commands/start/buildPrompt.test.js +182 -0
- package/dist/commands/start/buildPrompt.test.js.map +1 -0
- package/dist/commands/start/createWorktree.d.ts +21 -0
- package/dist/commands/start/createWorktree.d.ts.map +1 -0
- package/dist/commands/start/createWorktree.js +36 -0
- package/dist/commands/start/createWorktree.js.map +1 -0
- package/dist/commands/start/createWorktree.test.d.ts +2 -0
- package/dist/commands/start/createWorktree.test.d.ts.map +1 -0
- package/dist/commands/start/createWorktree.test.js +81 -0
- package/dist/commands/start/createWorktree.test.js.map +1 -0
- package/dist/commands/start/index.d.ts +4 -0
- package/dist/commands/start/index.d.ts.map +1 -0
- package/dist/commands/start/index.js +4 -0
- package/dist/commands/start/index.js.map +1 -0
- package/dist/commands/start/parseIssueInput.d.ts +21 -0
- package/dist/commands/start/parseIssueInput.d.ts.map +1 -0
- package/dist/commands/start/parseIssueInput.js +78 -0
- package/dist/commands/start/parseIssueInput.js.map +1 -0
- package/dist/commands/start/parseIssueInput.test.d.ts +2 -0
- package/dist/commands/start/parseIssueInput.test.d.ts.map +1 -0
- package/dist/commands/start/parseIssueInput.test.js +118 -0
- package/dist/commands/start/parseIssueInput.test.js.map +1 -0
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +144 -280
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/start.test.d.ts +2 -0
- package/dist/commands/start.test.d.ts.map +1 -0
- package/dist/commands/start.test.js +260 -0
- package/dist/commands/start.test.js.map +1 -0
- package/dist/commands/status.test.d.ts +2 -0
- package/dist/commands/status.test.d.ts.map +1 -0
- package/dist/commands/status.test.js +172 -0
- package/dist/commands/status.test.js.map +1 -0
- package/dist/commands/stop.test.d.ts +2 -0
- package/dist/commands/stop.test.d.ts.map +1 -0
- package/dist/commands/stop.test.js +152 -0
- package/dist/commands/stop.test.js.map +1 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +95 -1
- package/dist/commands/web.js.map +1 -1
- package/dist/commands/web.test.d.ts +2 -0
- package/dist/commands/web.test.d.ts.map +1 -0
- package/dist/commands/web.test.js +133 -0
- package/dist/commands/web.test.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { parseIssueInput } from './parseIssueInput.js';
|
|
3
|
+
import { GitHubAdapter } from '@claudetree/core';
|
|
4
|
+
// Mock GitHubAdapter
|
|
5
|
+
vi.mock('@claudetree/core', async () => {
|
|
6
|
+
const actual = await vi.importActual('@claudetree/core');
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
GitHubAdapter: vi.fn().mockImplementation(() => ({
|
|
10
|
+
parseIssueUrl: vi.fn().mockImplementation((url) => {
|
|
11
|
+
const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)/);
|
|
12
|
+
if (!match)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
owner: match[1],
|
|
16
|
+
repo: match[2],
|
|
17
|
+
number: parseInt(match[4], 10),
|
|
18
|
+
};
|
|
19
|
+
}),
|
|
20
|
+
getIssue: vi.fn().mockResolvedValue({
|
|
21
|
+
number: 42,
|
|
22
|
+
title: 'Add feature X',
|
|
23
|
+
body: 'We need to implement feature X',
|
|
24
|
+
labels: ['enhancement'],
|
|
25
|
+
state: 'open',
|
|
26
|
+
url: 'https://github.com/owner/repo/issues/42',
|
|
27
|
+
}),
|
|
28
|
+
generateBranchName: vi.fn().mockImplementation((num, title) => {
|
|
29
|
+
return `issue-${num}-${title.toLowerCase().replace(/\s+/g, '-').slice(0, 30)}`;
|
|
30
|
+
}),
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
describe('parseIssueInput', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
describe('GitHub URL input', () => {
|
|
39
|
+
it('should parse GitHub issue URL and fetch issue data', async () => {
|
|
40
|
+
const result = await parseIssueInput('https://github.com/owner/repo/issues/42', { token: 'fake-token' });
|
|
41
|
+
expect(result.issueNumber).toBe(42);
|
|
42
|
+
expect(result.issueData).toBeDefined();
|
|
43
|
+
expect(result.issueData?.title).toBe('Add feature X');
|
|
44
|
+
expect(result.branchName).toMatch(/^issue-42/);
|
|
45
|
+
});
|
|
46
|
+
it('should throw error when token not provided for URL', async () => {
|
|
47
|
+
await expect(parseIssueInput('https://github.com/owner/repo/issues/42', {})).rejects.toThrow('GitHub token required');
|
|
48
|
+
});
|
|
49
|
+
it('should throw error for invalid GitHub URL', async () => {
|
|
50
|
+
await expect(parseIssueInput('https://github.com/invalid-url', { token: 'fake-token' })).rejects.toThrow('Invalid GitHub URL');
|
|
51
|
+
});
|
|
52
|
+
it('should use custom branch name when provided', async () => {
|
|
53
|
+
const result = await parseIssueInput('https://github.com/owner/repo/issues/42', { token: 'fake-token', branch: 'custom-branch' });
|
|
54
|
+
expect(result.branchName).toBe('custom-branch');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('Issue number input', () => {
|
|
58
|
+
it('should parse issue number with GitHub config', async () => {
|
|
59
|
+
const result = await parseIssueInput('42', {
|
|
60
|
+
token: 'fake-token',
|
|
61
|
+
githubConfig: { owner: 'owner', repo: 'repo' },
|
|
62
|
+
});
|
|
63
|
+
expect(result.issueNumber).toBe(42);
|
|
64
|
+
expect(result.issueData).toBeDefined();
|
|
65
|
+
expect(result.branchName).toMatch(/^issue-42/);
|
|
66
|
+
});
|
|
67
|
+
it('should parse issue number without GitHub config (fallback branch)', async () => {
|
|
68
|
+
const result = await parseIssueInput('42', {});
|
|
69
|
+
expect(result.issueNumber).toBe(42);
|
|
70
|
+
expect(result.issueData).toBeNull();
|
|
71
|
+
expect(result.branchName).toBe('issue-42');
|
|
72
|
+
});
|
|
73
|
+
it('should gracefully handle API failure', async () => {
|
|
74
|
+
// Mock getIssue to throw
|
|
75
|
+
const mockGetIssue = vi.fn().mockRejectedValue(new Error('API error'));
|
|
76
|
+
vi.mocked(GitHubAdapter).mockImplementationOnce(() => ({
|
|
77
|
+
parseIssueUrl: vi.fn(),
|
|
78
|
+
getIssue: mockGetIssue,
|
|
79
|
+
generateBranchName: vi.fn(),
|
|
80
|
+
createPR: vi.fn(),
|
|
81
|
+
getDefaultBranch: vi.fn(),
|
|
82
|
+
listIssues: vi.fn(),
|
|
83
|
+
octokit: {},
|
|
84
|
+
}));
|
|
85
|
+
const result = await parseIssueInput('42', {
|
|
86
|
+
token: 'fake-token',
|
|
87
|
+
githubConfig: { owner: 'owner', repo: 'repo' },
|
|
88
|
+
});
|
|
89
|
+
expect(result.issueNumber).toBe(42);
|
|
90
|
+
expect(result.issueData).toBeNull();
|
|
91
|
+
expect(result.branchName).toBe('issue-42');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('Natural language task input', () => {
|
|
95
|
+
it('should parse natural language task as branch name', async () => {
|
|
96
|
+
const result = await parseIssueInput('add user authentication', {});
|
|
97
|
+
expect(result.issueNumber).toBeNull();
|
|
98
|
+
expect(result.issueData).toBeNull();
|
|
99
|
+
expect(result.taskDescription).toBe('add user authentication');
|
|
100
|
+
expect(result.branchName).toBe('task-add-user-authentication');
|
|
101
|
+
});
|
|
102
|
+
it('should sanitize special characters in task name', async () => {
|
|
103
|
+
const result = await parseIssueInput('Fix: bug [critical] #123!', {});
|
|
104
|
+
expect(result.branchName).toBe('task-fix-bug-critical-123');
|
|
105
|
+
});
|
|
106
|
+
it('should handle Korean characters', async () => {
|
|
107
|
+
const result = await parseIssueInput('로그인 기능 추가', {});
|
|
108
|
+
expect(result.taskDescription).toBe('로그인 기능 추가');
|
|
109
|
+
expect(result.branchName).toBe('task-로그인-기능-추가');
|
|
110
|
+
});
|
|
111
|
+
it('should truncate long task names', async () => {
|
|
112
|
+
const longTask = 'this is a very long task description that should be truncated for branch name';
|
|
113
|
+
const result = await parseIssueInput(longTask, {});
|
|
114
|
+
expect(result.branchName.length).toBeLessThanOrEqual(55); // 'task-' + 50 chars
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=parseIssueInput.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseIssueInput.test.js","sourceRoot":"","sources":["../../../src/commands/start/parseIssueInput.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmKpC,eAAO,MAAM,YAAY,SAygBrB,CAAC"}
|
package/dist/commands/start.js
CHANGED
|
@@ -2,20 +2,11 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
5
|
-
import {
|
|
5
|
+
import { ClaudeSessionAdapter, FileSessionRepository, FileEventRepository, FileToolApprovalRepository, TemplateLoader, DEFAULT_TEMPLATES, SlackNotifier, ValidationGateRunner, generateAIReviewSummary, } from '@claudetree/core';
|
|
6
|
+
import { parseIssueInput } from './start/parseIssueInput.js';
|
|
7
|
+
import { createOrFindWorktree } from './start/createWorktree.js';
|
|
8
|
+
import { buildPrompt, buildSystemPrompt, formatDuration } from './start/buildPrompt.js';
|
|
6
9
|
const CONFIG_DIR = '.claudetree';
|
|
7
|
-
/**
|
|
8
|
-
* Sanitize natural language input to a valid branch name
|
|
9
|
-
*/
|
|
10
|
-
function sanitizeBranchName(input) {
|
|
11
|
-
return input
|
|
12
|
-
.toLowerCase()
|
|
13
|
-
.replace(/[^a-z0-9가-힣\s-]/g, '') // Allow Korean characters
|
|
14
|
-
.replace(/\s+/g, '-')
|
|
15
|
-
.replace(/-+/g, '-')
|
|
16
|
-
.replace(/^-|-$/g, '')
|
|
17
|
-
.slice(0, 50); // Max length
|
|
18
|
-
}
|
|
19
10
|
async function loadConfig(cwd) {
|
|
20
11
|
try {
|
|
21
12
|
const configPath = join(cwd, CONFIG_DIR, 'config.json');
|
|
@@ -30,7 +21,6 @@ async function loadConfig(cwd) {
|
|
|
30
21
|
function parseGates(gatesStr, testCommand) {
|
|
31
22
|
const gateNames = gatesStr.split(',').map(g => g.trim().toLowerCase());
|
|
32
23
|
const gates = [];
|
|
33
|
-
// Always run pnpm install first to ensure dependencies are available
|
|
34
24
|
gates.push({ name: 'install', command: 'pnpm install --frozen-lockfile', required: true });
|
|
35
25
|
for (const name of gateNames) {
|
|
36
26
|
switch (name) {
|
|
@@ -38,7 +28,6 @@ function parseGates(gatesStr, testCommand) {
|
|
|
38
28
|
gates.push({ name: 'test', command: testCommand ?? 'pnpm test:run', required: true });
|
|
39
29
|
break;
|
|
40
30
|
case 'type':
|
|
41
|
-
// Use pnpm -r to run in all workspace packages
|
|
42
31
|
gates.push({ name: 'type', command: 'pnpm -r exec tsc --noEmit', required: true });
|
|
43
32
|
break;
|
|
44
33
|
case 'lint':
|
|
@@ -51,15 +40,57 @@ function parseGates(gatesStr, testCommand) {
|
|
|
51
40
|
}
|
|
52
41
|
return gates;
|
|
53
42
|
}
|
|
54
|
-
function
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
function parseToolCall(content) {
|
|
44
|
+
const match = content.match(/^(\w+):\s*(.+)$/);
|
|
45
|
+
if (!match)
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
return {
|
|
49
|
+
toolName: match[1] ?? '',
|
|
50
|
+
parameters: JSON.parse(match[2] ?? '{}'),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function detectProgressStep(toolName, params) {
|
|
58
|
+
const command = String(params.command ?? '');
|
|
59
|
+
if (toolName === 'Bash') {
|
|
60
|
+
if (command.includes('test') || command.includes('jest') || command.includes('vitest') || command.includes('pytest')) {
|
|
61
|
+
return 'testing';
|
|
62
|
+
}
|
|
63
|
+
if (command.includes('git commit')) {
|
|
64
|
+
return 'committing';
|
|
65
|
+
}
|
|
66
|
+
if (command.includes('gh pr create') || command.includes('git push')) {
|
|
67
|
+
return 'creating_pr';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (toolName === 'Edit' || toolName === 'Write') {
|
|
71
|
+
return 'implementing';
|
|
72
|
+
}
|
|
73
|
+
if (toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep') {
|
|
74
|
+
return 'analyzing';
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function updateProgress(progress, newStep) {
|
|
79
|
+
const stepOrder = ['analyzing', 'implementing', 'testing', 'committing', 'creating_pr'];
|
|
80
|
+
const currentIdx = stepOrder.indexOf(progress.currentStep);
|
|
81
|
+
const newIdx = stepOrder.indexOf(newStep);
|
|
82
|
+
if (newIdx > currentIdx) {
|
|
83
|
+
const completed = new Set(progress.completedSteps);
|
|
84
|
+
for (let i = 0; i <= currentIdx; i++) {
|
|
85
|
+
completed.add(stepOrder[i]);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...progress,
|
|
89
|
+
currentStep: newStep,
|
|
90
|
+
completedSteps: Array.from(completed),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return progress;
|
|
63
94
|
}
|
|
64
95
|
export const startCommand = new Command('start')
|
|
65
96
|
.description('Create worktree from issue and start Claude session (TDD mode by default)')
|
|
@@ -84,7 +115,6 @@ export const startCommand = new Command('start')
|
|
|
84
115
|
console.error('Error: claudetree not initialized. Run "claudetree init" first.');
|
|
85
116
|
process.exit(1);
|
|
86
117
|
}
|
|
87
|
-
// Build TDD config if TDD mode enabled
|
|
88
118
|
const tddEnabled = options.tdd !== false;
|
|
89
119
|
let tddConfig = null;
|
|
90
120
|
if (tddEnabled) {
|
|
@@ -96,7 +126,6 @@ export const startCommand = new Command('start')
|
|
|
96
126
|
gates: parseGates(options.gates ?? 'test,type', options.testCommand),
|
|
97
127
|
};
|
|
98
128
|
}
|
|
99
|
-
// Header
|
|
100
129
|
if (tddEnabled) {
|
|
101
130
|
console.log('\n\x1b[36m╔══════════════════════════════════════════╗\x1b[0m');
|
|
102
131
|
console.log('\x1b[36m║ TDD Mode Session (Default) ║\x1b[0m');
|
|
@@ -112,96 +141,57 @@ export const startCommand = new Command('start')
|
|
|
112
141
|
console.log(` • ${gate.name}: ${gate.command} ${status}`);
|
|
113
142
|
}
|
|
114
143
|
}
|
|
115
|
-
|
|
116
|
-
let issueData = null;
|
|
117
|
-
let branchName;
|
|
118
|
-
let taskDescription = null; // For natural language input
|
|
144
|
+
// Parse issue input using extracted module
|
|
119
145
|
const ghToken = options.token ?? process.env.GITHUB_TOKEN ?? config.github?.token;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
issueData = await ghAdapter.getIssue(parsed.owner, parsed.repo, parsed.number);
|
|
134
|
-
issueNumber = issueData.number;
|
|
135
|
-
branchName = options.branch ?? ghAdapter.generateBranchName(issueNumber, issueData.title);
|
|
136
|
-
console.log(` Title: ${issueData.title}`);
|
|
137
|
-
console.log(` Labels: ${issueData.labels.join(', ') || 'none'}`);
|
|
146
|
+
let parsedInput;
|
|
147
|
+
try {
|
|
148
|
+
parsedInput = await parseIssueInput(issue, {
|
|
149
|
+
token: ghToken,
|
|
150
|
+
branch: options.branch,
|
|
151
|
+
githubConfig: config.github?.owner && config.github?.repo
|
|
152
|
+
? { owner: config.github.owner, repo: config.github.repo }
|
|
153
|
+
: undefined,
|
|
154
|
+
});
|
|
155
|
+
if (parsedInput.issueData) {
|
|
156
|
+
console.log(`\nFetched issue #${parsedInput.issueNumber}`);
|
|
157
|
+
console.log(` Title: ${parsedInput.issueData.title}`);
|
|
158
|
+
console.log(` Labels: ${parsedInput.issueData.labels.join(', ') || 'none'}`);
|
|
138
159
|
}
|
|
139
|
-
|
|
140
|
-
console.
|
|
141
|
-
process.exit(1);
|
|
160
|
+
else if (parsedInput.taskDescription) {
|
|
161
|
+
console.log(`\n📝 Task: "${parsedInput.taskDescription}"`);
|
|
142
162
|
}
|
|
143
163
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
branchName = options.branch ?? `issue-${issueNumber}`;
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Failed to parse issue input'}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
const { issueNumber, issueData, branchName, taskDescription } = parsedInput;
|
|
169
|
+
// Create or find worktree using extracted module
|
|
170
|
+
// Support base branch from environment variable (used by chain command)
|
|
171
|
+
const baseBranch = process.env.CLAUDETREE_BASE_BRANCH;
|
|
172
|
+
let worktreeResult;
|
|
173
|
+
try {
|
|
174
|
+
worktreeResult = await createOrFindWorktree({
|
|
175
|
+
cwd,
|
|
176
|
+
worktreeDir: config.worktreeDir,
|
|
177
|
+
branchName,
|
|
178
|
+
issueNumber: issueNumber ?? undefined,
|
|
179
|
+
baseBranch,
|
|
180
|
+
});
|
|
181
|
+
if (worktreeResult.isExisting) {
|
|
182
|
+
console.log(`\nUsing existing worktree: ${branchName}`);
|
|
164
183
|
}
|
|
165
184
|
else {
|
|
166
|
-
|
|
167
|
-
taskDescription = issue;
|
|
168
|
-
branchName = options.branch ?? `task-${sanitizeBranchName(issue)}`;
|
|
169
|
-
console.log(`\n📝 Task: "${taskDescription}"`);
|
|
185
|
+
console.log(`\nCreating worktree: ${branchName}`);
|
|
170
186
|
}
|
|
187
|
+
console.log(` Branch: ${worktreeResult.worktree.branch}`);
|
|
188
|
+
console.log(` Path: ${worktreeResult.worktree.path}`);
|
|
171
189
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const existingWorktree = existingWorktrees.find((wt) => wt.branch === branchName || wt.path.endsWith(branchName));
|
|
176
|
-
let worktree;
|
|
177
|
-
if (existingWorktree) {
|
|
178
|
-
console.log(`\nUsing existing worktree: ${branchName}`);
|
|
179
|
-
worktree = {
|
|
180
|
-
id: randomUUID(),
|
|
181
|
-
path: existingWorktree.path,
|
|
182
|
-
branch: existingWorktree.branch,
|
|
183
|
-
};
|
|
184
|
-
console.log(` Branch: ${worktree.branch}`);
|
|
185
|
-
console.log(` Path: ${worktree.path}`);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
console.log(`\nCreating worktree: ${branchName}`);
|
|
189
|
-
try {
|
|
190
|
-
worktree = await gitAdapter.create({
|
|
191
|
-
path: worktreePath,
|
|
192
|
-
branch: branchName,
|
|
193
|
-
issueNumber: issueNumber ?? undefined,
|
|
194
|
-
});
|
|
195
|
-
console.log(` Branch: ${worktree.branch}`);
|
|
196
|
-
console.log(` Path: ${worktree.path}`);
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
if (error instanceof Error) {
|
|
200
|
-
console.error(`Error: ${error.message}`);
|
|
201
|
-
}
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Failed to create worktree'}`);
|
|
192
|
+
process.exit(1);
|
|
204
193
|
}
|
|
194
|
+
const { worktree } = worktreeResult;
|
|
205
195
|
try {
|
|
206
196
|
if (options.noSession) {
|
|
207
197
|
console.log('\nWorktree created. Use "cd" to navigate and start working.');
|
|
@@ -252,123 +242,24 @@ export const startCommand = new Command('start')
|
|
|
252
242
|
}
|
|
253
243
|
console.log(` Template: ${template.name}`);
|
|
254
244
|
}
|
|
255
|
-
// Build prompt
|
|
256
|
-
let prompt;
|
|
257
|
-
if (options.prompt) {
|
|
258
|
-
prompt = options.prompt;
|
|
259
|
-
}
|
|
260
|
-
else if (issueData) {
|
|
261
|
-
prompt = `You are working on Issue #${issueNumber}: "${issueData.title}"
|
|
262
|
-
|
|
263
|
-
Issue Description:
|
|
264
|
-
${issueData.body || 'No description provided.'}
|
|
265
|
-
|
|
266
|
-
IMPORTANT: Do NOT just analyze or suggest. Actually IMPLEMENT the solution.
|
|
267
|
-
${tddEnabled ? '\nStart with TDD - write a failing test first!' : ''}`;
|
|
268
|
-
}
|
|
269
|
-
else if (issueNumber) {
|
|
270
|
-
prompt = `Working on issue #${issueNumber}. ${tddEnabled ? 'Start with TDD - write a failing test first!' : 'Implement the solution.'}`;
|
|
271
|
-
}
|
|
272
|
-
else if (taskDescription) {
|
|
273
|
-
// Natural language task
|
|
274
|
-
prompt = `Your task: ${taskDescription}
|
|
275
|
-
|
|
276
|
-
IMPORTANT: Do NOT just analyze or suggest. Actually IMPLEMENT the solution.
|
|
277
|
-
${tddEnabled ? '\nStart with TDD - write a failing test first!' : ''}`;
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
prompt = `Working on ${branchName}. ${tddEnabled ? 'Start with TDD - write a failing test first!' : 'Implement any required changes.'}`;
|
|
281
|
-
}
|
|
282
|
-
if (template) {
|
|
283
|
-
const prefix = template.promptPrefix ? `${template.promptPrefix}\n\n` : '';
|
|
284
|
-
const suffix = template.promptSuffix ? `\n\n${template.promptSuffix}` : '';
|
|
285
|
-
prompt = `${prefix}${prompt}${suffix}`;
|
|
286
|
-
}
|
|
287
|
-
// Build system prompt
|
|
288
|
-
let systemPrompt;
|
|
245
|
+
// Build prompt using extracted module
|
|
289
246
|
const effectiveSkill = template?.skill || options.skill;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
### 3. REFACTOR Phase (Optional)
|
|
307
|
-
- Clean up code while keeping tests green
|
|
308
|
-
- Commit: "refactor: improve <description>"
|
|
309
|
-
|
|
310
|
-
## Rules
|
|
311
|
-
- NEVER write implementation before tests
|
|
312
|
-
- ONE test at a time
|
|
313
|
-
- Run tests after EVERY change
|
|
314
|
-
- Stop when all requirements are met
|
|
315
|
-
|
|
316
|
-
## Validation Gates (Must Pass Before PR)
|
|
317
|
-
${tddConfig.gates.map(g => `- ${g.name}: \`${g.command}\` ${g.required ? '(REQUIRED)' : '(optional)'}`).join('\n')}
|
|
318
|
-
|
|
319
|
-
## Time Limits
|
|
320
|
-
- Total: ${formatDuration(tddConfig.timeout)}
|
|
321
|
-
- Idle: ${formatDuration(tddConfig.idleTimeout)}
|
|
322
|
-
|
|
323
|
-
When done, create a PR to the develop branch.`;
|
|
324
|
-
}
|
|
325
|
-
else if (effectiveSkill === 'review') {
|
|
326
|
-
systemPrompt = 'Review code thoroughly for security, quality, and best practices.';
|
|
327
|
-
}
|
|
328
|
-
else if (effectiveSkill === 'docs') {
|
|
329
|
-
systemPrompt = `You are a documentation specialist. Generate comprehensive documentation.
|
|
330
|
-
|
|
331
|
-
## Documentation Workflow
|
|
332
|
-
|
|
333
|
-
### 1. Analysis Phase
|
|
334
|
-
- Read package.json for project metadata
|
|
335
|
-
- Scan src/ directory structure
|
|
336
|
-
- Identify exported APIs and types
|
|
337
|
-
- Note configuration files
|
|
338
|
-
|
|
339
|
-
### 2. README Generation
|
|
340
|
-
Structure your README with:
|
|
341
|
-
- Project title and badges
|
|
342
|
-
- Description and features
|
|
343
|
-
- Installation instructions
|
|
344
|
-
- Quick start example
|
|
345
|
-
- API reference (if applicable)
|
|
346
|
-
- Configuration options
|
|
347
|
-
- Contributing guidelines
|
|
348
|
-
|
|
349
|
-
### 3. API Documentation
|
|
350
|
-
For each public module:
|
|
351
|
-
- Purpose and usage
|
|
352
|
-
- Function signatures with types
|
|
353
|
-
- Parameter descriptions
|
|
354
|
-
- Return value descriptions
|
|
355
|
-
- Code examples
|
|
356
|
-
|
|
357
|
-
### 4. Output
|
|
358
|
-
- Create/update README.md
|
|
359
|
-
- Create docs/ folder for detailed docs if needed
|
|
360
|
-
- Use Markdown formatting
|
|
361
|
-
- Include table of contents for long docs
|
|
362
|
-
|
|
363
|
-
## Rules
|
|
364
|
-
- Be concise but complete
|
|
365
|
-
- Use code blocks with proper language tags
|
|
366
|
-
- Include real, working examples
|
|
367
|
-
- Document edge cases and error handling`;
|
|
368
|
-
}
|
|
369
|
-
if (template?.systemPrompt) {
|
|
370
|
-
systemPrompt = template.systemPrompt;
|
|
371
|
-
}
|
|
247
|
+
const prompt = buildPrompt({
|
|
248
|
+
issueNumber,
|
|
249
|
+
issueData,
|
|
250
|
+
branchName,
|
|
251
|
+
taskDescription,
|
|
252
|
+
tddEnabled,
|
|
253
|
+
template,
|
|
254
|
+
customPrompt: options.prompt,
|
|
255
|
+
});
|
|
256
|
+
// Build system prompt using extracted module
|
|
257
|
+
const systemPrompt = buildSystemPrompt({
|
|
258
|
+
tddEnabled,
|
|
259
|
+
tddConfig: tddConfig ?? undefined,
|
|
260
|
+
skill: effectiveSkill,
|
|
261
|
+
template,
|
|
262
|
+
});
|
|
372
263
|
console.log('\n\x1b[36m🚀 Starting Claude session...\x1b[0m');
|
|
373
264
|
if (tddEnabled) {
|
|
374
265
|
console.log(' Mode: \x1b[32mTDD\x1b[0m (Test-Driven Development)');
|
|
@@ -378,7 +269,6 @@ For each public module:
|
|
|
378
269
|
}
|
|
379
270
|
const eventRepo = new FileEventRepository(join(cwd, CONFIG_DIR));
|
|
380
271
|
const approvalRepo = new FileToolApprovalRepository(join(cwd, CONFIG_DIR));
|
|
381
|
-
// Save TDD state if enabled
|
|
382
272
|
let tddState = null;
|
|
383
273
|
let tddStatePath = null;
|
|
384
274
|
if (tddEnabled) {
|
|
@@ -394,7 +284,6 @@ For each public module:
|
|
|
394
284
|
await mkdir(join(cwd, CONFIG_DIR, 'tdd-state'), { recursive: true });
|
|
395
285
|
await writeFile(tddStatePath, JSON.stringify(tddState, null, 2));
|
|
396
286
|
}
|
|
397
|
-
// Track timeouts
|
|
398
287
|
const sessionStartTime = Date.now();
|
|
399
288
|
let lastOutputTime = Date.now();
|
|
400
289
|
let sessionTimedOut = false;
|
|
@@ -505,7 +394,6 @@ For each public module:
|
|
|
505
394
|
outputCount++;
|
|
506
395
|
session.lastHeartbeat = new Date();
|
|
507
396
|
lastOutputTime = Date.now();
|
|
508
|
-
// Check timeouts
|
|
509
397
|
if (sessionTimedOut || idleTimedOut) {
|
|
510
398
|
await claudeAdapter.stop(result.processId);
|
|
511
399
|
session.status = 'failed';
|
|
@@ -551,7 +439,6 @@ For each public module:
|
|
|
551
439
|
}
|
|
552
440
|
if (timeoutChecker)
|
|
553
441
|
clearInterval(timeoutChecker);
|
|
554
|
-
// Run validation gates if TDD mode and session didn't fail
|
|
555
442
|
if (tddEnabled && tddConfig && session.status !== 'failed' && !budgetExceeded) {
|
|
556
443
|
console.log('\n\x1b[36m╔══════════════════════════════════════════╗\x1b[0m');
|
|
557
444
|
console.log('\x1b[36m║ Running Validation Gates ║\x1b[0m');
|
|
@@ -600,7 +487,36 @@ For each public module:
|
|
|
600
487
|
else if (!tddEnabled && session.status !== 'failed' && !budgetExceeded) {
|
|
601
488
|
session.status = 'completed';
|
|
602
489
|
}
|
|
603
|
-
//
|
|
490
|
+
// Generate AI Review Summary for completed sessions
|
|
491
|
+
if (session.status === 'completed') {
|
|
492
|
+
console.log('\n\x1b[36m🤖 Generating AI Code Review Summary...\x1b[0m');
|
|
493
|
+
try {
|
|
494
|
+
const aiReview = await generateAIReviewSummary({
|
|
495
|
+
sessionId: session.id,
|
|
496
|
+
workingDir: worktree.path,
|
|
497
|
+
baseBranch: 'develop',
|
|
498
|
+
});
|
|
499
|
+
if (aiReview) {
|
|
500
|
+
// Save AI review to file
|
|
501
|
+
const reviewsDir = join(cwd, CONFIG_DIR, 'ai-reviews');
|
|
502
|
+
await mkdir(reviewsDir, { recursive: true });
|
|
503
|
+
await writeFile(join(reviewsDir, `${session.id}.json`), JSON.stringify({ ...aiReview, generatedAt: aiReview.generatedAt.toISOString() }, null, 2));
|
|
504
|
+
// Print summary
|
|
505
|
+
console.log(`\n \x1b[32m✓ Summary:\x1b[0m ${aiReview.summary}`);
|
|
506
|
+
console.log(` \x1b[33m⚠ Risk Level:\x1b[0m ${aiReview.riskLevel.toUpperCase()}`);
|
|
507
|
+
if (aiReview.potentialIssues.length > 0) {
|
|
508
|
+
console.log(` \x1b[31m! Issues:\x1b[0m ${aiReview.potentialIssues.length} potential issue(s) found`);
|
|
509
|
+
for (const issue of aiReview.potentialIssues.slice(0, 3)) {
|
|
510
|
+
const icon = issue.severity === 'critical' ? '🔴' : issue.severity === 'warning' ? '🟡' : '🔵';
|
|
511
|
+
console.log(` ${icon} ${issue.title}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
console.log(' \x1b[33m⚠ Could not generate AI review summary\x1b[0m');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
604
520
|
const totalDuration = Date.now() - sessionStartTime;
|
|
605
521
|
console.log('\n\x1b[36m╔══════════════════════════════════════════╗\x1b[0m');
|
|
606
522
|
console.log('\x1b[36m║ Session Summary ║\x1b[0m');
|
|
@@ -648,56 +564,4 @@ For each public module:
|
|
|
648
564
|
process.exit(1);
|
|
649
565
|
}
|
|
650
566
|
});
|
|
651
|
-
function parseToolCall(content) {
|
|
652
|
-
const match = content.match(/^(\w+):\s*(.+)$/);
|
|
653
|
-
if (!match)
|
|
654
|
-
return null;
|
|
655
|
-
try {
|
|
656
|
-
return {
|
|
657
|
-
toolName: match[1] ?? '',
|
|
658
|
-
parameters: JSON.parse(match[2] ?? '{}'),
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
catch {
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
function detectProgressStep(toolName, params) {
|
|
666
|
-
const command = String(params.command ?? '');
|
|
667
|
-
if (toolName === 'Bash') {
|
|
668
|
-
if (command.includes('test') || command.includes('jest') || command.includes('vitest') || command.includes('pytest')) {
|
|
669
|
-
return 'testing';
|
|
670
|
-
}
|
|
671
|
-
if (command.includes('git commit')) {
|
|
672
|
-
return 'committing';
|
|
673
|
-
}
|
|
674
|
-
if (command.includes('gh pr create') || command.includes('git push')) {
|
|
675
|
-
return 'creating_pr';
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
if (toolName === 'Edit' || toolName === 'Write') {
|
|
679
|
-
return 'implementing';
|
|
680
|
-
}
|
|
681
|
-
if (toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep') {
|
|
682
|
-
return 'analyzing';
|
|
683
|
-
}
|
|
684
|
-
return null;
|
|
685
|
-
}
|
|
686
|
-
function updateProgress(progress, newStep) {
|
|
687
|
-
const stepOrder = ['analyzing', 'implementing', 'testing', 'committing', 'creating_pr'];
|
|
688
|
-
const currentIdx = stepOrder.indexOf(progress.currentStep);
|
|
689
|
-
const newIdx = stepOrder.indexOf(newStep);
|
|
690
|
-
if (newIdx > currentIdx) {
|
|
691
|
-
const completed = new Set(progress.completedSteps);
|
|
692
|
-
for (let i = 0; i <= currentIdx; i++) {
|
|
693
|
-
completed.add(stepOrder[i]);
|
|
694
|
-
}
|
|
695
|
-
return {
|
|
696
|
-
...progress,
|
|
697
|
-
currentStep: newStep,
|
|
698
|
-
completedSteps: Array.from(completed),
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
return progress;
|
|
702
|
-
}
|
|
703
567
|
//# sourceMappingURL=start.js.map
|