@agentic15.com/agentic15-claude-zen 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+
5
+ export class StatusCommand {
6
+ static show() {
7
+ console.log('\nšŸ“Š Project Status\n');
8
+
9
+ // Load tracker
10
+ const tracker = this.loadTracker();
11
+
12
+ if (!tracker) {
13
+ console.log('āŒ No active plan found\n');
14
+ process.exit(1);
15
+ }
16
+
17
+ // Calculate statistics
18
+ const completed = tracker.taskFiles.filter(t => t.status === 'completed');
19
+ const inProgress = tracker.taskFiles.filter(t => t.status === 'in_progress');
20
+ const pending = tracker.taskFiles.filter(t => t.status === 'pending');
21
+ const blocked = tracker.taskFiles.filter(t => t.status === 'blocked');
22
+ const total = tracker.taskFiles.length;
23
+
24
+ // Display plan info
25
+ console.log(` Plan: ${tracker.planId}`);
26
+ console.log(` Total Tasks: ${total}\n`);
27
+
28
+ // Progress bar
29
+ const completedPercent = Math.round((completed.length / total) * 100);
30
+ const barLength = 30;
31
+ const filledLength = Math.round((completedPercent / 100) * barLength);
32
+ const bar = 'ā–ˆ'.repeat(filledLength) + 'ā–‘'.repeat(barLength - filledLength);
33
+
34
+ console.log(` Progress: [${bar}] ${completedPercent}%\n`);
35
+
36
+ // Status breakdown
37
+ console.log(' Status Breakdown:');
38
+ console.log(` āœ… Completed: ${completed.length}`);
39
+ console.log(` šŸ”„ In Progress: ${inProgress.length}`);
40
+ console.log(` ā³ Pending: ${pending.length}`);
41
+
42
+ if (blocked.length > 0) {
43
+ console.log(` 🚫 Blocked: ${blocked.length}`);
44
+ }
45
+
46
+ console.log('');
47
+
48
+ // Current task
49
+ if (inProgress.length > 0) {
50
+ const current = inProgress[0];
51
+ console.log(' šŸ”„ Current Task:');
52
+ console.log(` ${current.id}: ${current.title}`);
53
+
54
+ // Show changed files
55
+ try {
56
+ const diff = execSync('git diff --name-only', { encoding: 'utf-8' });
57
+ const staged = execSync('git diff --cached --name-only', { encoding: 'utf-8' });
58
+
59
+ const allChanges = new Set([
60
+ ...diff.trim().split('\n').filter(Boolean),
61
+ ...staged.trim().split('\n').filter(Boolean)
62
+ ]);
63
+
64
+ if (allChanges.size > 0) {
65
+ console.log(`\n šŸ“ Modified files (${allChanges.size}):`);
66
+ Array.from(allChanges).slice(0, 10).forEach(file => {
67
+ console.log(` - ${file}`);
68
+ });
69
+
70
+ if (allChanges.size > 10) {
71
+ console.log(` ... and ${allChanges.size - 10} more`);
72
+ }
73
+ }
74
+ } catch (e) {
75
+ // Ignore git errors
76
+ }
77
+
78
+ console.log('');
79
+ console.log(' šŸ’” Next step: agentic15 commit');
80
+ } else if (pending.length > 0) {
81
+ console.log(' šŸ“Œ Next Task:');
82
+ const next = pending[0];
83
+ console.log(` ${next.id}: ${next.title}`);
84
+ console.log('');
85
+ console.log(' šŸ’” Next step: agentic15 task next');
86
+ } else if (blocked.length > 0) {
87
+ console.log(' 🚫 Blocked Tasks:');
88
+ blocked.slice(0, 3).forEach(task => {
89
+ console.log(` ${task.id}: ${task.title}`);
90
+ });
91
+
92
+ if (blocked.length > 3) {
93
+ console.log(` ... and ${blocked.length - 3} more`);
94
+ }
95
+ } else {
96
+ console.log(' šŸŽ‰ All tasks completed!');
97
+ }
98
+
99
+ console.log('');
100
+
101
+ // Recent activity
102
+ const recentCompleted = completed
103
+ .filter(t => t.completedAt)
104
+ .sort((a, b) => new Date(b.completedAt) - new Date(a.completedAt))
105
+ .slice(0, 3);
106
+
107
+ if (recentCompleted.length > 0) {
108
+ console.log(' šŸ“œ Recently Completed:');
109
+ recentCompleted.forEach(task => {
110
+ const date = new Date(task.completedAt);
111
+ const timeAgo = this.getTimeAgo(date);
112
+ console.log(` āœ“ ${task.id}: ${task.title} (${timeAgo})`);
113
+ });
114
+ console.log('');
115
+ }
116
+ }
117
+
118
+ static loadTracker() {
119
+ const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
120
+
121
+ if (!existsSync(activePlanPath)) {
122
+ return null;
123
+ }
124
+
125
+ const planId = readFileSync(activePlanPath, 'utf-8').trim();
126
+ const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
127
+
128
+ if (!existsSync(trackerPath)) {
129
+ return null;
130
+ }
131
+
132
+ return JSON.parse(readFileSync(trackerPath, 'utf-8'));
133
+ }
134
+
135
+ static getTimeAgo(date) {
136
+ const seconds = Math.floor((new Date() - date) / 1000);
137
+
138
+ const intervals = {
139
+ year: 31536000,
140
+ month: 2592000,
141
+ week: 604800,
142
+ day: 86400,
143
+ hour: 3600,
144
+ minute: 60
145
+ };
146
+
147
+ for (const [unit, secondsInUnit] of Object.entries(intervals)) {
148
+ const interval = Math.floor(seconds / secondsInUnit);
149
+ if (interval >= 1) {
150
+ return `${interval} ${unit}${interval === 1 ? '' : 's'} ago`;
151
+ }
152
+ }
153
+
154
+ return 'just now';
155
+ }
156
+ }
@@ -0,0 +1,249 @@
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
+ import { TaskIssueMapper } from '../core/TaskIssueMapper.js';
7
+
8
+ export class TaskCommand {
9
+ static async handle(action, taskId) {
10
+ switch (action) {
11
+ case 'start':
12
+ return this.startTask(taskId);
13
+ case 'next':
14
+ return this.startNext();
15
+ case 'status':
16
+ return this.showStatus();
17
+ default:
18
+ console.log(`\nāŒ Unknown action: ${action}`);
19
+ console.log(' Valid actions: start, next, status\n');
20
+ process.exit(1);
21
+ }
22
+ }
23
+
24
+ static async startTask(taskId) {
25
+ if (!taskId) {
26
+ console.log('\nāŒ Task ID required for "start" action');
27
+ console.log(' Usage: agentic15 task start TASK-001\n');
28
+ process.exit(1);
29
+ }
30
+
31
+ // Load task tracker
32
+ const tracker = this.loadTracker();
33
+ const task = tracker.taskFiles.find(t => t.id === taskId);
34
+
35
+ if (!task) {
36
+ console.log(`\nāŒ Task not found: ${taskId}\n`);
37
+ process.exit(1);
38
+ }
39
+
40
+ if (task.status === 'completed') {
41
+ console.log(`\nāš ļø Task ${taskId} is already completed\n`);
42
+ process.exit(1);
43
+ }
44
+
45
+ // Check if another task is in progress
46
+ const inProgress = tracker.taskFiles.find(t => t.status === 'in_progress');
47
+ if (inProgress && inProgress.id !== taskId) {
48
+ console.log(`\nāš ļø Task ${inProgress.id} is already in progress`);
49
+ console.log(` Complete it first with: agentic15 commit\n`);
50
+ process.exit(1);
51
+ }
52
+
53
+ // Create feature branch
54
+ const branchName = `feature/${taskId.toLowerCase()}`;
55
+ try {
56
+ execSync(`git checkout -b ${branchName}`, { stdio: 'inherit' });
57
+ } catch (error) {
58
+ // Branch might already exist
59
+ try {
60
+ execSync(`git checkout ${branchName}`, { stdio: 'inherit' });
61
+ } catch (e) {
62
+ console.log(`\nāŒ Failed to create/checkout branch: ${branchName}\n`);
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ // Update task status
68
+ task.status = 'in_progress';
69
+ task.startedAt = new Date().toISOString();
70
+ this.saveTracker(tracker);
71
+
72
+ // Create GitHub issue if enabled
73
+ let githubIssue = null;
74
+ const config = new GitHubConfig(process.cwd());
75
+ if (config.isAutoCreateEnabled()) {
76
+ githubIssue = await this.createGitHubIssue(task, config);
77
+ }
78
+
79
+ // Display task details
80
+ this.displayTaskDetails(task, githubIssue, tracker);
81
+ }
82
+
83
+ static async startNext() {
84
+ const tracker = this.loadTracker();
85
+
86
+ // Find next pending task
87
+ const nextTask = tracker.taskFiles.find(t => t.status === 'pending');
88
+
89
+ if (!nextTask) {
90
+ console.log('\nāœ… No more pending tasks!\n');
91
+ const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
92
+ const total = tracker.taskFiles.length;
93
+ console.log(` Progress: ${completed}/${total} tasks completed\n`);
94
+ process.exit(0);
95
+ }
96
+
97
+ console.log(`\nā–¶ļø Auto-starting next task: ${nextTask.id}\n`);
98
+ return this.startTask(nextTask.id);
99
+ }
100
+
101
+ static showStatus() {
102
+ const tracker = this.loadTracker();
103
+
104
+ const inProgress = tracker.taskFiles.find(t => t.status === 'in_progress');
105
+ const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
106
+ const pending = tracker.taskFiles.filter(t => t.status === 'pending').length;
107
+ const total = tracker.taskFiles.length;
108
+
109
+ console.log('\nšŸ“Š Task Status\n');
110
+ console.log(` Plan: ${tracker.planId}`);
111
+ console.log(` Progress: ${completed}/${total} completed (${pending} pending)\n`);
112
+
113
+ if (inProgress) {
114
+ console.log(` šŸ”„ Currently working on: ${inProgress.id}`);
115
+ console.log(` šŸ“Œ ${inProgress.title}`);
116
+
117
+ // Show changed files
118
+ try {
119
+ const diff = execSync('git diff --name-only', { encoding: 'utf-8' });
120
+ if (diff.trim()) {
121
+ console.log(`\n šŸ“ Changed files:`);
122
+ diff.trim().split('\n').forEach(file => {
123
+ console.log(` - ${file}`);
124
+ });
125
+ }
126
+ } catch (e) {
127
+ // Ignore
128
+ }
129
+ } else {
130
+ console.log(' ā„¹ļø No task currently in progress');
131
+ console.log(` Run: agentic15 task next\n`);
132
+ }
133
+
134
+ console.log('');
135
+ }
136
+
137
+ static async createGitHubIssue(task, config) {
138
+ try {
139
+ const client = new GitHubClient(
140
+ config.getToken(),
141
+ config.getRepoInfo().owner,
142
+ config.getRepoInfo().repo
143
+ );
144
+
145
+ if (!client.isConfigured()) {
146
+ console.log('\nāš ļø GitHub not configured. Skipping issue creation.');
147
+ console.log(' Run: agentic15 auth setup\n');
148
+ return null;
149
+ }
150
+
151
+ // Load full task details
152
+ const taskPath = this.getTaskPath(task.id);
153
+ const taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
154
+
155
+ const { title, body, labels } = TaskIssueMapper.mapTaskToIssue(taskData);
156
+ const issueNumber = await client.createIssue(title, body, labels);
157
+
158
+ if (issueNumber) {
159
+ // Save issue number to task
160
+ taskData.githubIssue = issueNumber;
161
+ writeFileSync(taskPath, JSON.stringify(taskData, null, 2));
162
+
163
+ console.log(`\nāœ“ Created GitHub issue #${issueNumber}`);
164
+ const repoInfo = config.getRepoInfo();
165
+ console.log(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/issues/${issueNumber}\n`);
166
+
167
+ return issueNumber;
168
+ }
169
+ } catch (error) {
170
+ console.log(`\nāš ļø Failed to create GitHub issue: ${error.message}\n`);
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ static displayTaskDetails(task, githubIssue, tracker) {
177
+ console.log(`\nāœ… Started task: ${task.id}`);
178
+ console.log(`šŸ“‹ Plan: ${tracker.planId}\n`);
179
+ console.log(`šŸ“Œ ${task.title}`);
180
+
181
+ if (task.description) {
182
+ console.log(`šŸ“ ${task.description}\n`);
183
+ }
184
+
185
+ if (task.phase) {
186
+ console.log(`šŸ”§ Phase: ${task.phase}`);
187
+ }
188
+
189
+ if (githubIssue) {
190
+ const config = new GitHubConfig(process.cwd());
191
+ const repoInfo = config.getRepoInfo();
192
+ console.log(`šŸ”— GitHub Issue: https://github.com/${repoInfo.owner}/${repoInfo.repo}/issues/${githubIssue}`);
193
+ }
194
+
195
+ // Load full task for completion criteria
196
+ try {
197
+ const taskPath = this.getTaskPath(task.id);
198
+ const taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
199
+
200
+ if (taskData.completionCriteria && taskData.completionCriteria.length > 0) {
201
+ console.log(`\nāœ“ Completion criteria:`);
202
+ taskData.completionCriteria.forEach((criteria, idx) => {
203
+ console.log(` ${idx + 1}. ${criteria}`);
204
+ });
205
+ }
206
+ } catch (e) {
207
+ // Ignore
208
+ }
209
+
210
+ console.log(`\nšŸ’” Next steps:`);
211
+ console.log(` 1. Tell Claude: "Write code for ${task.id}"`);
212
+ console.log(` 2. When done: agentic15 commit\n`);
213
+ }
214
+
215
+ static loadTracker() {
216
+ const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
217
+
218
+ if (!existsSync(activePlanPath)) {
219
+ console.log('\nāŒ No active plan found');
220
+ console.log(' Run: agentic15 plan "project description"\n');
221
+ process.exit(1);
222
+ }
223
+
224
+ const planId = readFileSync(activePlanPath, 'utf-8').trim();
225
+ const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
226
+
227
+ if (!existsSync(trackerPath)) {
228
+ console.log('\nāŒ Task tracker not found');
229
+ console.log(' Run: agentic15 plan\n');
230
+ process.exit(1);
231
+ }
232
+
233
+ return JSON.parse(readFileSync(trackerPath, 'utf-8'));
234
+ }
235
+
236
+ static saveTracker(tracker) {
237
+ const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
238
+ const planId = readFileSync(activePlanPath, 'utf-8').trim();
239
+ const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
240
+
241
+ writeFileSync(trackerPath, JSON.stringify(tracker, null, 2));
242
+ }
243
+
244
+ static getTaskPath(taskId) {
245
+ const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
246
+ const planId = readFileSync(activePlanPath, 'utf-8').trim();
247
+ return join(process.cwd(), '.claude', 'plans', planId, 'tasks', `${taskId}.json`);
248
+ }
249
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Copyright 2024-2025 agentic15.com
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { Octokit } from '@octokit/rest';
18
+
19
+ /**
20
+ * GitHubClient - Handles all GitHub API interactions
21
+ *
22
+ * Single Responsibility: Communicate with GitHub Issues API
23
+ *
24
+ * This class encapsulates all GitHub API calls and provides graceful
25
+ * degradation when GitHub integration is unavailable or disabled.
26
+ */
27
+ export class GitHubClient {
28
+ /**
29
+ * Initialize GitHub client
30
+ *
31
+ * @param {string|null} token - GitHub Personal Access Token
32
+ * @param {string|null} owner - Repository owner
33
+ * @param {string|null} repo - Repository name
34
+ */
35
+ constructor(token, owner, repo) {
36
+ if (!token || !owner || !repo) {
37
+ this.configured = false;
38
+ this.octokit = null;
39
+ this.owner = null;
40
+ this.repo = null;
41
+ return;
42
+ }
43
+
44
+ this.configured = true;
45
+ this.octokit = new Octokit({ auth: token });
46
+ this.owner = owner;
47
+ this.repo = repo;
48
+ }
49
+
50
+ /**
51
+ * Check if GitHub client is configured and ready
52
+ *
53
+ * @returns {boolean} True if configured, false otherwise
54
+ */
55
+ isConfigured() {
56
+ return this.configured;
57
+ }
58
+
59
+ /**
60
+ * Create a new GitHub issue
61
+ *
62
+ * @param {string} title - Issue title
63
+ * @param {string} body - Issue body (markdown supported)
64
+ * @param {string[]} labels - Array of label names
65
+ * @returns {Promise<number|null>} Issue number if created, null on failure
66
+ */
67
+ async createIssue(title, body, labels = []) {
68
+ if (!this.configured) {
69
+ return null;
70
+ }
71
+
72
+ try {
73
+ const response = await this.octokit.issues.create({
74
+ owner: this.owner,
75
+ repo: this.repo,
76
+ title,
77
+ body,
78
+ labels
79
+ });
80
+ return response.data.number;
81
+ } catch (error) {
82
+ console.warn('⚠ Failed to create GitHub issue:', error.message);
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Update labels on an existing GitHub issue
89
+ *
90
+ * @param {number} issueNumber - Issue number
91
+ * @param {string[]} labels - Array of label names to set
92
+ * @returns {Promise<boolean>} True if updated, false on failure
93
+ */
94
+ async updateIssueLabels(issueNumber, labels) {
95
+ if (!this.configured || !issueNumber) {
96
+ return false;
97
+ }
98
+
99
+ try {
100
+ await this.octokit.issues.update({
101
+ owner: this.owner,
102
+ repo: this.repo,
103
+ issue_number: issueNumber,
104
+ labels
105
+ });
106
+ return true;
107
+ } catch (error) {
108
+ console.warn('⚠ Failed to update issue labels:', error.message);
109
+ return false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Add a comment to an existing GitHub issue
115
+ *
116
+ * @param {number} issueNumber - Issue number
117
+ * @param {string} comment - Comment text (markdown supported)
118
+ * @returns {Promise<boolean>} True if added, false on failure
119
+ */
120
+ async addIssueComment(issueNumber, comment) {
121
+ if (!this.configured || !issueNumber) {
122
+ return false;
123
+ }
124
+
125
+ try {
126
+ await this.octokit.issues.createComment({
127
+ owner: this.owner,
128
+ repo: this.repo,
129
+ issue_number: issueNumber,
130
+ body: comment
131
+ });
132
+ return true;
133
+ } catch (error) {
134
+ console.warn('⚠ Failed to add issue comment:', error.message);
135
+ return false;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Close a GitHub issue with optional comment
141
+ *
142
+ * @param {number} issueNumber - Issue number
143
+ * @param {string|null} comment - Optional closing comment
144
+ * @returns {Promise<boolean>} True if closed, false on failure
145
+ */
146
+ async closeIssue(issueNumber, comment = null) {
147
+ if (!this.configured || !issueNumber) {
148
+ return false;
149
+ }
150
+
151
+ try {
152
+ // Add comment first if provided
153
+ if (comment) {
154
+ await this.addIssueComment(issueNumber, comment);
155
+ }
156
+
157
+ // Close the issue
158
+ await this.octokit.issues.update({
159
+ owner: this.owner,
160
+ repo: this.repo,
161
+ issue_number: issueNumber,
162
+ state: 'closed'
163
+ });
164
+ return true;
165
+ } catch (error) {
166
+ console.warn('⚠ Failed to close issue:', error.message);
167
+ return false;
168
+ }
169
+ }
170
+ }