@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.
Files changed (65) hide show
  1. package/dist/commands/chain.d.ts +3 -0
  2. package/dist/commands/chain.d.ts.map +1 -0
  3. package/dist/commands/chain.js +284 -0
  4. package/dist/commands/chain.js.map +1 -0
  5. package/dist/commands/init.test.d.ts +2 -0
  6. package/dist/commands/init.test.d.ts.map +1 -0
  7. package/dist/commands/init.test.js +94 -0
  8. package/dist/commands/init.test.js.map +1 -0
  9. package/dist/commands/list.test.d.ts +2 -0
  10. package/dist/commands/list.test.d.ts.map +1 -0
  11. package/dist/commands/list.test.js +106 -0
  12. package/dist/commands/list.test.js.map +1 -0
  13. package/dist/commands/start/buildPrompt.d.ts +29 -0
  14. package/dist/commands/start/buildPrompt.d.ts.map +1 -0
  15. package/dist/commands/start/buildPrompt.js +142 -0
  16. package/dist/commands/start/buildPrompt.js.map +1 -0
  17. package/dist/commands/start/buildPrompt.test.d.ts +2 -0
  18. package/dist/commands/start/buildPrompt.test.d.ts.map +1 -0
  19. package/dist/commands/start/buildPrompt.test.js +182 -0
  20. package/dist/commands/start/buildPrompt.test.js.map +1 -0
  21. package/dist/commands/start/createWorktree.d.ts +21 -0
  22. package/dist/commands/start/createWorktree.d.ts.map +1 -0
  23. package/dist/commands/start/createWorktree.js +36 -0
  24. package/dist/commands/start/createWorktree.js.map +1 -0
  25. package/dist/commands/start/createWorktree.test.d.ts +2 -0
  26. package/dist/commands/start/createWorktree.test.d.ts.map +1 -0
  27. package/dist/commands/start/createWorktree.test.js +81 -0
  28. package/dist/commands/start/createWorktree.test.js.map +1 -0
  29. package/dist/commands/start/index.d.ts +4 -0
  30. package/dist/commands/start/index.d.ts.map +1 -0
  31. package/dist/commands/start/index.js +4 -0
  32. package/dist/commands/start/index.js.map +1 -0
  33. package/dist/commands/start/parseIssueInput.d.ts +21 -0
  34. package/dist/commands/start/parseIssueInput.d.ts.map +1 -0
  35. package/dist/commands/start/parseIssueInput.js +78 -0
  36. package/dist/commands/start/parseIssueInput.js.map +1 -0
  37. package/dist/commands/start/parseIssueInput.test.d.ts +2 -0
  38. package/dist/commands/start/parseIssueInput.test.d.ts.map +1 -0
  39. package/dist/commands/start/parseIssueInput.test.js +118 -0
  40. package/dist/commands/start/parseIssueInput.test.js.map +1 -0
  41. package/dist/commands/start.d.ts.map +1 -1
  42. package/dist/commands/start.js +144 -280
  43. package/dist/commands/start.js.map +1 -1
  44. package/dist/commands/start.test.d.ts +2 -0
  45. package/dist/commands/start.test.d.ts.map +1 -0
  46. package/dist/commands/start.test.js +260 -0
  47. package/dist/commands/start.test.js.map +1 -0
  48. package/dist/commands/status.test.d.ts +2 -0
  49. package/dist/commands/status.test.d.ts.map +1 -0
  50. package/dist/commands/status.test.js +172 -0
  51. package/dist/commands/status.test.js.map +1 -0
  52. package/dist/commands/stop.test.d.ts +2 -0
  53. package/dist/commands/stop.test.d.ts.map +1 -0
  54. package/dist/commands/stop.test.js +152 -0
  55. package/dist/commands/stop.test.js.map +1 -0
  56. package/dist/commands/web.d.ts.map +1 -1
  57. package/dist/commands/web.js +95 -1
  58. package/dist/commands/web.js.map +1 -1
  59. package/dist/commands/web.test.d.ts +2 -0
  60. package/dist/commands/web.test.d.ts.map +1 -0
  61. package/dist/commands/web.test.js +133 -0
  62. package/dist/commands/web.test.js.map +1 -0
  63. package/dist/index.js +2 -0
  64. package/dist/index.js.map +1 -1
  65. 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;AAyHpC,eAAO,MAAM,YAAY,SA4nBrB,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"}
@@ -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 { GitWorktreeAdapter, ClaudeSessionAdapter, FileSessionRepository, FileEventRepository, FileToolApprovalRepository, GitHubAdapter, TemplateLoader, DEFAULT_TEMPLATES, SlackNotifier, ValidationGateRunner, } from '@claudetree/core';
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 formatDuration(ms) {
55
- const seconds = Math.floor(ms / 1000);
56
- const minutes = Math.floor(seconds / 60);
57
- const hours = Math.floor(minutes / 60);
58
- if (hours > 0)
59
- return `${hours}h ${minutes % 60}m`;
60
- if (minutes > 0)
61
- return `${minutes}m ${seconds % 60}s`;
62
- return `${seconds}s`;
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
- let issueNumber = null;
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
- if (issue.includes('github.com')) {
121
- if (!ghToken) {
122
- console.error('\nError: GitHub token required for URL. Set GITHUB_TOKEN or use --token.');
123
- process.exit(1);
124
- }
125
- const ghAdapter = new GitHubAdapter(ghToken);
126
- const parsed = ghAdapter.parseIssueUrl(issue);
127
- if (!parsed) {
128
- console.error('Error: Invalid GitHub URL format.');
129
- process.exit(1);
130
- }
131
- console.log(`\nFetching issue #${parsed.number} from ${parsed.owner}/${parsed.repo}...`);
132
- try {
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
- catch (error) {
140
- console.error(`Error: Failed to fetch issue. ${error instanceof Error ? error.message : ''}`);
141
- process.exit(1);
160
+ else if (parsedInput.taskDescription) {
161
+ console.log(`\n📝 Task: "${parsedInput.taskDescription}"`);
142
162
  }
143
163
  }
144
- else {
145
- const parsed = parseInt(issue, 10);
146
- const isNumber = !isNaN(parsed);
147
- if (isNumber && ghToken && config.github?.owner && config.github?.repo) {
148
- const ghAdapter = new GitHubAdapter(ghToken);
149
- try {
150
- console.log(`\nFetching issue #${parsed}...`);
151
- issueData = await ghAdapter.getIssue(config.github.owner, config.github.repo, parsed);
152
- issueNumber = issueData.number;
153
- branchName = options.branch ?? ghAdapter.generateBranchName(issueNumber, issueData.title);
154
- console.log(` Title: ${issueData.title}`);
155
- }
156
- catch {
157
- issueNumber = parsed;
158
- branchName = options.branch ?? `issue-${issueNumber}`;
159
- }
160
- }
161
- else if (isNumber) {
162
- issueNumber = parsed;
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
- // Natural language input - use as task description
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
- const worktreePath = join(cwd, config.worktreeDir, branchName);
173
- const gitAdapter = new GitWorktreeAdapter(cwd);
174
- const existingWorktrees = await gitAdapter.list();
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
- if (tddEnabled) {
291
- // TDD system prompt (default)
292
- systemPrompt = `You are in TDD (Test-Driven Development) mode. Follow this STRICT workflow:
293
-
294
- ## TDD Cycle (Repeat until done)
295
-
296
- ### 1. RED Phase - Write Failing Test
297
- - Write ONE failing test that describes the expected behavior
298
- - Run the test to confirm it fails
299
- - Commit: "test: add test for <feature>"
300
-
301
- ### 2. GREEN Phase - Minimal Implementation
302
- - Write the MINIMUM code to make the test pass
303
- - Run tests to confirm they pass
304
- - Commit: "feat: implement <feature>"
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
- // Final summary
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