@agentic15.com/agentic15-claude-zen 1.1.0 ā 2.0.1
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/bin/agentic15.js +50 -0
- package/package.json +5 -3
- package/src/cli/AuthCommand.js +195 -0
- package/src/cli/CommitCommand.js +290 -0
- package/src/cli/PlanCommand.js +123 -0
- package/src/cli/StatusCommand.js +156 -0
- package/src/cli/TaskCommand.js +249 -0
- package/templates/.claude/POST-INSTALL.md +18 -367
- package/templates/.claude/settings.json +7 -0
- package/templates/package.json +6 -15
package/bin/agentic15.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { AuthCommand } from '../src/cli/AuthCommand.js';
|
|
5
|
+
import { TaskCommand } from '../src/cli/TaskCommand.js';
|
|
6
|
+
import { CommitCommand } from '../src/cli/CommitCommand.js';
|
|
7
|
+
import { StatusCommand } from '../src/cli/StatusCommand.js';
|
|
8
|
+
import { PlanCommand } from '../src/cli/PlanCommand.js';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('agentic15')
|
|
14
|
+
.description('Agentic15 Claude Zen - Automated AI development workflow')
|
|
15
|
+
.version('2.0.0');
|
|
16
|
+
|
|
17
|
+
// GitHub authentication setup
|
|
18
|
+
program
|
|
19
|
+
.command('auth')
|
|
20
|
+
.description('GitHub authentication setup')
|
|
21
|
+
.action(() => AuthCommand.setup());
|
|
22
|
+
|
|
23
|
+
// Task management
|
|
24
|
+
program
|
|
25
|
+
.command('task')
|
|
26
|
+
.description('Task management')
|
|
27
|
+
.argument('<action>', 'Action: start, next, status')
|
|
28
|
+
.argument('[taskId]', 'Task ID (e.g., TASK-001) - required for "start"')
|
|
29
|
+
.action((action, taskId) => TaskCommand.handle(action, taskId));
|
|
30
|
+
|
|
31
|
+
// Auto-commit workflow
|
|
32
|
+
program
|
|
33
|
+
.command('commit')
|
|
34
|
+
.description('Run tests, commit, push, create PR')
|
|
35
|
+
.action(() => CommitCommand.execute());
|
|
36
|
+
|
|
37
|
+
// Show status
|
|
38
|
+
program
|
|
39
|
+
.command('status')
|
|
40
|
+
.description('Show current task status and progress')
|
|
41
|
+
.action(() => StatusCommand.show());
|
|
42
|
+
|
|
43
|
+
// Plan management (single command for generate + lock)
|
|
44
|
+
program
|
|
45
|
+
.command('plan')
|
|
46
|
+
.description('Generate and lock plan')
|
|
47
|
+
.argument('[description]', 'Project description (required for first run)')
|
|
48
|
+
.action((description) => PlanCommand.handle(description));
|
|
49
|
+
|
|
50
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic15.com/agentic15-claude-zen",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Structured AI-assisted development framework for Claude Code with enforced quality standards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"agentic15-claude-zen": "./bin/create-agentic15-claude-zen.js"
|
|
7
|
+
"agentic15-claude-zen": "./bin/create-agentic15-claude-zen.js",
|
|
8
|
+
"agentic15": "./bin/agentic15.js"
|
|
8
9
|
},
|
|
9
10
|
"type": "module",
|
|
10
11
|
"scripts": {
|
|
@@ -54,7 +55,8 @@
|
|
|
54
55
|
"node": ">=18.0.0"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
|
-
"@octokit/rest": "^20.0.2"
|
|
58
|
+
"@octokit/rest": "^20.0.2",
|
|
59
|
+
"commander": "^12.1.0"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
62
|
"@playwright/test": "^1.57.0",
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
import { Octokit } from '@octokit/rest';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
export class AuthCommand {
|
|
12
|
+
static async setup() {
|
|
13
|
+
console.log('\nš GitHub Authentication Setup\n');
|
|
14
|
+
|
|
15
|
+
// Step 1: Display current git configuration
|
|
16
|
+
this.displayGitConfig();
|
|
17
|
+
|
|
18
|
+
// Step 2: Confirm configuration
|
|
19
|
+
const configOk = await this.confirmConfig();
|
|
20
|
+
if (!configOk) {
|
|
21
|
+
this.showConfigCommands();
|
|
22
|
+
console.log('\nā Setup cancelled. Please configure git first.\n');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Step 3: Get GitHub token
|
|
27
|
+
const token = await this.promptToken();
|
|
28
|
+
|
|
29
|
+
// Step 4: Validate token
|
|
30
|
+
const isValid = await this.validateToken(token);
|
|
31
|
+
if (!isValid) {
|
|
32
|
+
console.log('\nā Invalid GitHub token. Please try again.\n');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Step 5: Auto-detect owner/repo
|
|
37
|
+
const { owner, repo } = this.detectRepo();
|
|
38
|
+
|
|
39
|
+
// Step 6: Save configuration
|
|
40
|
+
this.saveConfig(token, owner, repo);
|
|
41
|
+
|
|
42
|
+
console.log('\nā
GitHub authentication configured successfully!\n');
|
|
43
|
+
console.log(` Owner: ${owner}`);
|
|
44
|
+
console.log(` Repo: ${repo}`);
|
|
45
|
+
console.log(` Config: .claude/settings.local.json\n`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static displayGitConfig() {
|
|
49
|
+
console.log('š Current Git Configuration:\n');
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Global config
|
|
53
|
+
const globalName = execSync('git config --global user.name', { encoding: 'utf-8' }).trim();
|
|
54
|
+
const globalEmail = execSync('git config --global user.email', { encoding: 'utf-8' }).trim();
|
|
55
|
+
|
|
56
|
+
console.log(' Global:');
|
|
57
|
+
console.log(` - user.name: ${globalName || '(not set)'}`);
|
|
58
|
+
console.log(` - user.email: ${globalEmail || '(not set)'}`);
|
|
59
|
+
|
|
60
|
+
// Local config (project-specific)
|
|
61
|
+
try {
|
|
62
|
+
const localName = execSync('git config --local user.name', { encoding: 'utf-8' }).trim();
|
|
63
|
+
const localEmail = execSync('git config --local user.email', { encoding: 'utf-8' }).trim();
|
|
64
|
+
|
|
65
|
+
if (localName || localEmail) {
|
|
66
|
+
console.log('\n Local (this project):');
|
|
67
|
+
console.log(` - user.name: ${localName || '(not set)'}`);
|
|
68
|
+
console.log(` - user.email: ${localEmail || '(not set)'}`);
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// No local config, that's ok
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log('');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.log(' ā ļø Unable to read git config. Is git installed?\n');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static showConfigCommands() {
|
|
81
|
+
console.log('\nš” To configure git, use these commands:\n');
|
|
82
|
+
console.log(' Global (all projects):');
|
|
83
|
+
console.log(' git config --global user.name "Your Name"');
|
|
84
|
+
console.log(' git config --global user.email "your@email.com"\n');
|
|
85
|
+
console.log(' Local (this project only):');
|
|
86
|
+
console.log(' git config --local user.name "Your Name"');
|
|
87
|
+
console.log(' git config --local user.email "your@email.com"\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static async confirmConfig() {
|
|
91
|
+
const rl = readline.createInterface({
|
|
92
|
+
input: process.stdin,
|
|
93
|
+
output: process.stdout
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
rl.question('Is this configuration correct? (y/n): ', (answer) => {
|
|
98
|
+
rl.close();
|
|
99
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static async promptToken() {
|
|
105
|
+
const rl = readline.createInterface({
|
|
106
|
+
input: process.stdin,
|
|
107
|
+
output: process.stdout
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log('š GitHub Personal Access Token:');
|
|
111
|
+
console.log(' Create one at: https://github.com/settings/tokens');
|
|
112
|
+
console.log(' Required scopes: repo (Full control of private repositories)\n');
|
|
113
|
+
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
rl.question('Enter your GitHub token: ', (token) => {
|
|
116
|
+
rl.close();
|
|
117
|
+
resolve(token.trim());
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static async validateToken(token) {
|
|
123
|
+
try {
|
|
124
|
+
const octokit = new Octokit({ auth: token });
|
|
125
|
+
await octokit.rest.users.getAuthenticated();
|
|
126
|
+
console.log('\nā Token validated successfully');
|
|
127
|
+
return true;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static detectRepo() {
|
|
134
|
+
try {
|
|
135
|
+
// Get remote URL
|
|
136
|
+
const remoteUrl = execSync('git config --get remote.origin.url', { encoding: 'utf-8' }).trim();
|
|
137
|
+
|
|
138
|
+
// Parse owner/repo from URL
|
|
139
|
+
// Handles: git@github.com:owner/repo.git or https://github.com/owner/repo.git
|
|
140
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/(.+?)(\.git)?$/);
|
|
141
|
+
|
|
142
|
+
if (match) {
|
|
143
|
+
return {
|
|
144
|
+
owner: match[1],
|
|
145
|
+
repo: match[2]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// Fallback if no remote
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Fallback: prompt user
|
|
153
|
+
console.log('\nā ļø Could not auto-detect repository from git remote.');
|
|
154
|
+
console.log(' Using placeholder values. Update .claude/settings.local.json manually.\n');
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
owner: 'your-github-username',
|
|
158
|
+
repo: 'your-repo-name'
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static saveConfig(token, owner, repo) {
|
|
163
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.local.json');
|
|
164
|
+
const settingsDir = dirname(settingsPath);
|
|
165
|
+
|
|
166
|
+
// Ensure directory exists
|
|
167
|
+
if (!existsSync(settingsDir)) {
|
|
168
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Read existing settings or create new
|
|
172
|
+
let settings = {};
|
|
173
|
+
if (existsSync(settingsPath)) {
|
|
174
|
+
try {
|
|
175
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
176
|
+
} catch (e) {
|
|
177
|
+
// Invalid JSON, start fresh
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Update GitHub config
|
|
182
|
+
settings.github = {
|
|
183
|
+
enabled: true,
|
|
184
|
+
autoCreate: true,
|
|
185
|
+
autoUpdate: true,
|
|
186
|
+
autoClose: true,
|
|
187
|
+
token,
|
|
188
|
+
owner,
|
|
189
|
+
repo
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Write settings
|
|
193
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { GitHubClient } from '../core/GitHubClient.js';
|
|
5
|
+
import { GitHubConfig } from '../core/GitHubConfig.js';
|
|
6
|
+
|
|
7
|
+
export class CommitCommand {
|
|
8
|
+
static async execute() {
|
|
9
|
+
console.log('\nš Starting commit workflow...\n');
|
|
10
|
+
|
|
11
|
+
// Step 1: Find active task
|
|
12
|
+
const { task, tracker } = this.getActiveTask();
|
|
13
|
+
|
|
14
|
+
if (!task) {
|
|
15
|
+
console.log('ā No task in progress');
|
|
16
|
+
console.log(' Start a task first: agentic15 task start TASK-001\n');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`š Task: ${task.id} - ${task.title}\n`);
|
|
21
|
+
|
|
22
|
+
// Step 2: Run tests
|
|
23
|
+
console.log('š§Ŗ Running tests...\n');
|
|
24
|
+
this.runTests();
|
|
25
|
+
|
|
26
|
+
// Step 3: Stage files in Agent/
|
|
27
|
+
console.log('\nš¦ Staging changes...\n');
|
|
28
|
+
this.stageFiles();
|
|
29
|
+
|
|
30
|
+
// Step 4: Generate commit message
|
|
31
|
+
const commitMessage = this.generateCommitMessage(task);
|
|
32
|
+
|
|
33
|
+
// Step 5: Commit
|
|
34
|
+
console.log('š¾ Creating commit...\n');
|
|
35
|
+
this.createCommit(commitMessage);
|
|
36
|
+
|
|
37
|
+
// Step 6: Push to feature branch
|
|
38
|
+
console.log('ā¬ļø Pushing to remote...\n');
|
|
39
|
+
this.pushBranch(task.id);
|
|
40
|
+
|
|
41
|
+
// Step 7: Create PR
|
|
42
|
+
console.log('š Creating pull request...\n');
|
|
43
|
+
const prUrl = await this.createPullRequest(task, commitMessage);
|
|
44
|
+
|
|
45
|
+
// Step 8: Update GitHub issue status
|
|
46
|
+
await this.updateGitHubIssue(task, prUrl);
|
|
47
|
+
|
|
48
|
+
// Step 9: Display summary
|
|
49
|
+
this.displaySummary(task, prUrl, tracker);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static getActiveTask() {
|
|
53
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
54
|
+
|
|
55
|
+
if (!existsSync(activePlanPath)) {
|
|
56
|
+
console.log('\nā No active plan found\n');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
61
|
+
const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
|
|
62
|
+
|
|
63
|
+
if (!existsSync(trackerPath)) {
|
|
64
|
+
console.log('\nā Task tracker not found\n');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tracker = JSON.parse(readFileSync(trackerPath, 'utf-8'));
|
|
69
|
+
const task = tracker.taskFiles.find(t => t.status === 'in_progress');
|
|
70
|
+
|
|
71
|
+
return { task, tracker };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static runTests() {
|
|
75
|
+
try {
|
|
76
|
+
execSync('npm test', { stdio: 'inherit' });
|
|
77
|
+
console.log('\nā
All tests passed');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.log('\nā Tests failed. Fix errors before committing.\n');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static stageFiles() {
|
|
85
|
+
try {
|
|
86
|
+
// Stage all files in Agent/
|
|
87
|
+
execSync('git add Agent/', { stdio: 'inherit' });
|
|
88
|
+
|
|
89
|
+
// Also stage scripts/ if exists
|
|
90
|
+
try {
|
|
91
|
+
execSync('git add scripts/', { stdio: 'pipe' });
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// scripts/ might not exist
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Show what was staged
|
|
97
|
+
const staged = execSync('git diff --cached --name-only', { encoding: 'utf-8' });
|
|
98
|
+
|
|
99
|
+
if (!staged.trim()) {
|
|
100
|
+
console.log('ā ļø No changes to commit\n');
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log('Staged files:');
|
|
105
|
+
staged.trim().split('\n').forEach(file => {
|
|
106
|
+
console.log(` ā ${file}`);
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.log(`\nā Failed to stage files: ${error.message}\n`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static generateCommitMessage(task) {
|
|
115
|
+
// Load full task details
|
|
116
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
117
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
118
|
+
const taskPath = join(process.cwd(), '.claude', 'plans', planId, 'tasks', `${task.id}.json`);
|
|
119
|
+
|
|
120
|
+
let taskData;
|
|
121
|
+
try {
|
|
122
|
+
taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
|
|
123
|
+
} catch (e) {
|
|
124
|
+
taskData = task;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Format: [TASK-001] Task title
|
|
128
|
+
return `[${task.id}] ${taskData.title || task.title}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static createCommit(message) {
|
|
132
|
+
try {
|
|
133
|
+
// Escape double quotes and backslashes for shell
|
|
134
|
+
const escapedMessage = message.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
135
|
+
execSync(`git commit -m "${escapedMessage}"`, { stdio: 'inherit' });
|
|
136
|
+
console.log('ā
Commit created');
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log(`\nā Failed to create commit: ${error.message}\n`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static pushBranch(taskId) {
|
|
144
|
+
const branchName = `feature/${taskId.toLowerCase()}`;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Check if branch has upstream
|
|
148
|
+
try {
|
|
149
|
+
execSync('git rev-parse --abbrev-ref --symbolic-full-name @{u}', { stdio: 'pipe' });
|
|
150
|
+
// Has upstream, just push
|
|
151
|
+
execSync('git push', { stdio: 'inherit' });
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// No upstream, set it
|
|
154
|
+
execSync(`git push -u origin ${branchName}`, { stdio: 'inherit' });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log('ā
Pushed to remote');
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.log(`\nā Failed to push: ${error.message}\n`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static async createPullRequest(task, commitMessage) {
|
|
165
|
+
try {
|
|
166
|
+
// Get main branch name
|
|
167
|
+
const mainBranch = this.getMainBranch();
|
|
168
|
+
|
|
169
|
+
// Load task for issue number
|
|
170
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
171
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
172
|
+
const taskPath = join(process.cwd(), '.claude', 'plans', planId, 'tasks', `${task.id}.json`);
|
|
173
|
+
|
|
174
|
+
let taskData;
|
|
175
|
+
try {
|
|
176
|
+
taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
|
|
177
|
+
} catch (e) {
|
|
178
|
+
taskData = task;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Build PR body
|
|
182
|
+
let prBody = taskData.description || '';
|
|
183
|
+
|
|
184
|
+
if (taskData.githubIssue) {
|
|
185
|
+
prBody += `\n\nCloses #${taskData.githubIssue}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Create PR using gh CLI
|
|
189
|
+
const prCommand = `gh pr create --title "${commitMessage}" --body "${prBody}" --base ${mainBranch}`;
|
|
190
|
+
const prOutput = execSync(prCommand, { encoding: 'utf-8' });
|
|
191
|
+
|
|
192
|
+
// Extract PR URL from output
|
|
193
|
+
const prUrl = prOutput.match(/https:\/\/github\.com\/[^\s]+/)?.[0];
|
|
194
|
+
|
|
195
|
+
if (prUrl) {
|
|
196
|
+
console.log(`ā
Pull request created: ${prUrl}`);
|
|
197
|
+
return prUrl;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log('ā
Pull request created');
|
|
201
|
+
return null;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.log(`\nā ļø Failed to create PR: ${error.message}`);
|
|
204
|
+
console.log(' You may need to install GitHub CLI: https://cli.github.com/\n');
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static async updateGitHubIssue(task, prUrl) {
|
|
210
|
+
const config = new GitHubConfig(process.cwd());
|
|
211
|
+
|
|
212
|
+
if (!config.isAutoUpdateEnabled()) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// Load task data
|
|
218
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
219
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
220
|
+
const taskPath = join(process.cwd(), '.claude', 'plans', planId, 'tasks', `${task.id}.json`);
|
|
221
|
+
|
|
222
|
+
const taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
|
|
223
|
+
|
|
224
|
+
if (!taskData.githubIssue) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const client = new GitHubClient(
|
|
229
|
+
config.getToken(),
|
|
230
|
+
config.getRepoInfo().owner,
|
|
231
|
+
config.getRepoInfo().repo
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (!client.isConfigured()) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Update labels to "in review"
|
|
239
|
+
await client.updateIssueLabels(taskData.githubIssue, ['status: in-review', `phase: ${taskData.phase || 'implementation'}`]);
|
|
240
|
+
|
|
241
|
+
// Add comment with PR link
|
|
242
|
+
if (prUrl) {
|
|
243
|
+
const comment = `Pull request created: ${prUrl}\n\nTask is now in code review.`;
|
|
244
|
+
await client.addIssueComment(taskData.githubIssue, comment);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log(`ā
Updated GitHub issue #${taskData.githubIssue}`);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.log(`\nā ļø Failed to update GitHub issue: ${error.message}\n`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static displaySummary(task, prUrl, tracker) {
|
|
254
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
255
|
+
console.log('ā ā
Commit Workflow Complete ā');
|
|
256
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
257
|
+
|
|
258
|
+
console.log(`š Task: ${task.id} - ${task.title}`);
|
|
259
|
+
|
|
260
|
+
if (prUrl) {
|
|
261
|
+
console.log(`š PR: ${prUrl}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
265
|
+
const total = tracker.taskFiles.length;
|
|
266
|
+
|
|
267
|
+
console.log(`\nš Progress: ${completed}/${total} completed\n`);
|
|
268
|
+
|
|
269
|
+
console.log('š” Next steps:');
|
|
270
|
+
console.log(' 1. Review and merge PR on GitHub');
|
|
271
|
+
console.log(' 2. After merge, run: agentic15 task next\n');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
static getMainBranch() {
|
|
275
|
+
try {
|
|
276
|
+
// Try to detect main branch
|
|
277
|
+
const branches = execSync('git branch -r', { encoding: 'utf-8' });
|
|
278
|
+
|
|
279
|
+
if (branches.includes('origin/main')) {
|
|
280
|
+
return 'main';
|
|
281
|
+
} else if (branches.includes('origin/master')) {
|
|
282
|
+
return 'master';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return 'main'; // Default
|
|
286
|
+
} catch (e) {
|
|
287
|
+
return 'main';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export class PlanCommand {
|
|
6
|
+
static async handle(description) {
|
|
7
|
+
// Check if plan already exists
|
|
8
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
9
|
+
|
|
10
|
+
if (existsSync(activePlanPath)) {
|
|
11
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
12
|
+
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
13
|
+
const projectPlanPath = join(planPath, 'PROJECT-PLAN.json');
|
|
14
|
+
|
|
15
|
+
// Check if plan file exists
|
|
16
|
+
if (existsSync(projectPlanPath)) {
|
|
17
|
+
// Plan exists, check if it's locked
|
|
18
|
+
const lockedPath = join(planPath, '.plan-locked');
|
|
19
|
+
|
|
20
|
+
if (existsSync(lockedPath)) {
|
|
21
|
+
console.log('\nā ļø Plan already locked');
|
|
22
|
+
console.log(` Plan: ${planId}\n`);
|
|
23
|
+
this.showPlanStatus(planId);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Plan exists but not locked - lock it
|
|
28
|
+
console.log('\nš Found existing plan, locking it...\n');
|
|
29
|
+
return this.lockPlan(planId);
|
|
30
|
+
} else {
|
|
31
|
+
// Requirements exist but plan not created yet
|
|
32
|
+
console.log('\nā ļø Waiting for PROJECT-PLAN.json');
|
|
33
|
+
console.log(` Tell Claude: "Create the project plan"\n`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// No plan exists - create new one
|
|
39
|
+
if (!description) {
|
|
40
|
+
console.log('\nā Project description required');
|
|
41
|
+
console.log(' Usage: agentic15 plan "Build a calculator app"\n');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return this.generatePlan(description);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static generatePlan(description) {
|
|
49
|
+
console.log('\nš Generating new plan...\n');
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Run plan:generate script
|
|
53
|
+
execSync(`npm run plan:generate "${description}"`, { stdio: 'inherit' });
|
|
54
|
+
|
|
55
|
+
// Find the generated plan
|
|
56
|
+
const plansDir = join(process.cwd(), '.claude', 'plans');
|
|
57
|
+
const plans = readdirSync(plansDir)
|
|
58
|
+
.filter(name => name.startsWith('plan-') && name.includes('-generated'));
|
|
59
|
+
|
|
60
|
+
if (plans.length === 0) {
|
|
61
|
+
console.log('\nā Failed to generate plan\n');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Use the most recent generated plan
|
|
66
|
+
const planId = plans[plans.length - 1];
|
|
67
|
+
|
|
68
|
+
// Set as active plan
|
|
69
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
70
|
+
writeFileSync(activePlanPath, planId);
|
|
71
|
+
|
|
72
|
+
console.log(`\nā
Plan requirements created: ${planId}`);
|
|
73
|
+
console.log(` Location: .claude/plans/${planId}/PROJECT-REQUIREMENTS.txt\n`);
|
|
74
|
+
console.log('š” Next steps:');
|
|
75
|
+
console.log(` 1. Tell Claude: "Create the project plan"`);
|
|
76
|
+
console.log(` 2. When Claude is done, run: agentic15 plan\n`);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.log(`\nā Failed to generate plan: ${error.message}\n`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static lockPlan(planId) {
|
|
84
|
+
console.log(`š Locking plan: ${planId}\n`);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Run plan:init script
|
|
88
|
+
execSync('npm run plan:init', { stdio: 'inherit' });
|
|
89
|
+
|
|
90
|
+
console.log('\nā
Plan locked successfully\n');
|
|
91
|
+
|
|
92
|
+
this.showPlanStatus(planId);
|
|
93
|
+
|
|
94
|
+
console.log('š” Next step: agentic15 task next\n');
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.log(`\nā Failed to lock plan: ${error.message}\n`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static showPlanStatus(planId) {
|
|
102
|
+
const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
|
|
103
|
+
|
|
104
|
+
if (!existsSync(trackerPath)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const tracker = JSON.parse(readFileSync(trackerPath, 'utf-8'));
|
|
110
|
+
|
|
111
|
+
const total = tracker.taskFiles.length;
|
|
112
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
113
|
+
const pending = tracker.taskFiles.filter(t => t.status === 'pending').length;
|
|
114
|
+
|
|
115
|
+
console.log('š Plan Status:');
|
|
116
|
+
console.log(` Total Tasks: ${total}`);
|
|
117
|
+
console.log(` Completed: ${completed}`);
|
|
118
|
+
console.log(` Pending: ${pending}\n`);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
// Ignore errors
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|