@agentic15.com/agentic15-claude-zen 1.0.0 → 1.1.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.
- package/CHANGELOG.md +60 -45
- package/LICENSE +1 -1
- package/README.md +6 -4
- package/bin/create-agentic15-claude-zen.js +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +3 -3
- package/package.json +64 -61
- package/src/core/DependencyInstaller.js +1 -1
- package/src/core/GitHubClient.js +170 -0
- package/src/core/GitHubConfig.js +192 -0
- package/src/core/GitInitializer.js +1 -1
- package/src/core/HookInstaller.js +71 -54
- package/src/core/ProjectInitializer.js +1 -1
- package/src/core/TaskIssueMapper.js +137 -0
- package/src/core/TemplateManager.js +1 -1
- package/src/index.js +1 -1
- package/src/utils/updateTaskGitHubStatus.js +81 -0
- package/templates/.claude/POST-INSTALL.md +367 -248
- package/templates/.claude/hooks/complete-task.js +224 -0
- package/templates/.claude/hooks/post-merge.js +163 -0
- package/templates/.claude/hooks/start-task.js +233 -0
- package/templates/.claude/settings.json +272 -262
- package/templates/.claude/settings.local.json.example +11 -0
|
@@ -0,0 +1,192 @@
|
|
|
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 fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { execSync } from 'child_process';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GitHubConfig - Manages GitHub integration configuration
|
|
23
|
+
*
|
|
24
|
+
* Single Responsibility: Load and validate GitHub settings
|
|
25
|
+
*
|
|
26
|
+
* Configuration sources (in order of priority):
|
|
27
|
+
* 1. Environment variables (highest)
|
|
28
|
+
* 2. .claude/settings.local.json (user-specific, gitignored)
|
|
29
|
+
* 3. .claude/settings.json (defaults)
|
|
30
|
+
* 4. Auto-detection from git remote (fallback for owner/repo)
|
|
31
|
+
*/
|
|
32
|
+
export class GitHubConfig {
|
|
33
|
+
/**
|
|
34
|
+
* Initialize configuration loader
|
|
35
|
+
*
|
|
36
|
+
* @param {string} projectRoot - Project root directory
|
|
37
|
+
*/
|
|
38
|
+
constructor(projectRoot = process.cwd()) {
|
|
39
|
+
this.projectRoot = projectRoot;
|
|
40
|
+
this.config = this.loadConfig();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load configuration from all sources
|
|
45
|
+
*
|
|
46
|
+
* @returns {Object} Merged configuration object
|
|
47
|
+
*/
|
|
48
|
+
loadConfig() {
|
|
49
|
+
const config = {
|
|
50
|
+
enabled: true,
|
|
51
|
+
token: null,
|
|
52
|
+
owner: null,
|
|
53
|
+
repo: null,
|
|
54
|
+
autoCreate: true,
|
|
55
|
+
autoUpdate: true,
|
|
56
|
+
autoClose: true
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Load from settings.json (defaults)
|
|
60
|
+
const settingsPath = path.join(this.projectRoot, '.claude', 'settings.json');
|
|
61
|
+
if (fs.existsSync(settingsPath)) {
|
|
62
|
+
try {
|
|
63
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
64
|
+
if (settings.github) {
|
|
65
|
+
Object.assign(config, settings.github);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.warn('⚠ Failed to load .claude/settings.json:', error.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Load from settings.local.json (user overrides)
|
|
73
|
+
const localSettingsPath = path.join(this.projectRoot, '.claude', 'settings.local.json');
|
|
74
|
+
if (fs.existsSync(localSettingsPath)) {
|
|
75
|
+
try {
|
|
76
|
+
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf8'));
|
|
77
|
+
if (localSettings.github) {
|
|
78
|
+
Object.assign(config, localSettings.github);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn('⚠ Failed to load .claude/settings.local.json:', error.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Override with environment variables (highest priority)
|
|
86
|
+
if (process.env.GITHUB_TOKEN) {
|
|
87
|
+
config.token = process.env.GITHUB_TOKEN;
|
|
88
|
+
}
|
|
89
|
+
if (process.env.GITHUB_OWNER) {
|
|
90
|
+
config.owner = process.env.GITHUB_OWNER;
|
|
91
|
+
}
|
|
92
|
+
if (process.env.GITHUB_REPO) {
|
|
93
|
+
config.repo = process.env.GITHUB_REPO;
|
|
94
|
+
}
|
|
95
|
+
if (process.env.GITHUB_ENABLED !== undefined) {
|
|
96
|
+
config.enabled = process.env.GITHUB_ENABLED === 'true';
|
|
97
|
+
}
|
|
98
|
+
if (process.env.GITHUB_AUTO_CREATE !== undefined) {
|
|
99
|
+
config.autoCreate = process.env.GITHUB_AUTO_CREATE === 'true';
|
|
100
|
+
}
|
|
101
|
+
if (process.env.GITHUB_AUTO_UPDATE !== undefined) {
|
|
102
|
+
config.autoUpdate = process.env.GITHUB_AUTO_UPDATE === 'true';
|
|
103
|
+
}
|
|
104
|
+
if (process.env.GITHUB_AUTO_CLOSE !== undefined) {
|
|
105
|
+
config.autoClose = process.env.GITHUB_AUTO_CLOSE === 'true';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Auto-detect owner/repo from git remote if not configured
|
|
109
|
+
if (!config.owner || !config.repo) {
|
|
110
|
+
try {
|
|
111
|
+
const remote = execSync('git remote get-url origin', {
|
|
112
|
+
cwd: this.projectRoot,
|
|
113
|
+
encoding: 'utf8'
|
|
114
|
+
}).trim();
|
|
115
|
+
|
|
116
|
+
// Parse GitHub URL formats:
|
|
117
|
+
// - https://github.com/owner/repo.git
|
|
118
|
+
// - git@github.com:owner/repo.git
|
|
119
|
+
const match = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
120
|
+
if (match) {
|
|
121
|
+
config.owner = config.owner || match[1];
|
|
122
|
+
config.repo = config.repo || match[2];
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Git remote not configured or not a git repository
|
|
126
|
+
// This is okay - GitHub integration will be disabled
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return config;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get GitHub Personal Access Token
|
|
135
|
+
*
|
|
136
|
+
* @returns {string|null} Token or null if not configured
|
|
137
|
+
*/
|
|
138
|
+
getToken() {
|
|
139
|
+
return this.config.token;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get repository owner and name
|
|
144
|
+
*
|
|
145
|
+
* @returns {Object} { owner: string|null, repo: string|null }
|
|
146
|
+
*/
|
|
147
|
+
getRepoInfo() {
|
|
148
|
+
return {
|
|
149
|
+
owner: this.config.owner,
|
|
150
|
+
repo: this.config.repo
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if GitHub integration is fully enabled and configured
|
|
156
|
+
*
|
|
157
|
+
* @returns {boolean} True if ready to use, false otherwise
|
|
158
|
+
*/
|
|
159
|
+
isEnabled() {
|
|
160
|
+
return this.config.enabled &&
|
|
161
|
+
this.config.token !== null &&
|
|
162
|
+
this.config.owner !== null &&
|
|
163
|
+
this.config.repo !== null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if auto-create issues feature is enabled
|
|
168
|
+
*
|
|
169
|
+
* @returns {boolean} True if enabled, false otherwise
|
|
170
|
+
*/
|
|
171
|
+
isAutoCreateEnabled() {
|
|
172
|
+
return this.isEnabled() && this.config.autoCreate;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if auto-update issues feature is enabled
|
|
177
|
+
*
|
|
178
|
+
* @returns {boolean} True if enabled, false otherwise
|
|
179
|
+
*/
|
|
180
|
+
isAutoUpdateEnabled() {
|
|
181
|
+
return this.isEnabled() && this.config.autoUpdate;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if auto-close issues feature is enabled
|
|
186
|
+
*
|
|
187
|
+
* @returns {boolean} True if enabled, false otherwise
|
|
188
|
+
*/
|
|
189
|
+
isAutoCloseEnabled() {
|
|
190
|
+
return this.isEnabled() && this.config.autoClose;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -1,54 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright 2024-2025
|
|
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 { writeFileSync } from 'fs';
|
|
18
|
-
import { join } from 'path';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
echo "
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 { writeFileSync, readFileSync, existsSync, chmodSync } from 'fs';
|
|
18
|
+
import { join, dirname } from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* HookInstaller - Installs git hooks
|
|
23
|
+
*
|
|
24
|
+
* Single Responsibility: Setup git hooks in project
|
|
25
|
+
*/
|
|
26
|
+
export class HookInstaller {
|
|
27
|
+
/**
|
|
28
|
+
* Install git hooks
|
|
29
|
+
*
|
|
30
|
+
* @param {string} targetDir - Target directory path
|
|
31
|
+
*/
|
|
32
|
+
async install(targetDir) {
|
|
33
|
+
console.log('\n🔗 Setting up Git hooks...');
|
|
34
|
+
try {
|
|
35
|
+
const gitHooksDir = join(targetDir, '.git', 'hooks');
|
|
36
|
+
const claudeHooksDir = join(targetDir, '.claude', 'hooks');
|
|
37
|
+
|
|
38
|
+
// Create pre-commit hook (reminder that hooks run via Claude Code)
|
|
39
|
+
const preCommitHook = `#!/bin/sh
|
|
40
|
+
# agentic15-claude-zen pre-commit hook
|
|
41
|
+
# This hook is CRITICAL for enforcement
|
|
42
|
+
|
|
43
|
+
echo "⚠️ agentic15-claude-zen git hooks are NOT yet active"
|
|
44
|
+
echo " Hooks run via Claude Code settings.json, not git hooks"
|
|
45
|
+
echo " This is just a reminder"
|
|
46
|
+
exit 0
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
writeFileSync(join(gitHooksDir, 'pre-commit'), preCommitHook, { mode: 0o755 });
|
|
50
|
+
|
|
51
|
+
// Install post-merge hook for GitHub issue auto-close
|
|
52
|
+
const postMergeSource = join(claudeHooksDir, 'post-merge.js');
|
|
53
|
+
if (existsSync(postMergeSource)) {
|
|
54
|
+
const postMergeContent = readFileSync(postMergeSource, 'utf8');
|
|
55
|
+
const postMergeHook = join(gitHooksDir, 'post-merge');
|
|
56
|
+
writeFileSync(postMergeHook, postMergeContent, { mode: 0o755 });
|
|
57
|
+
try {
|
|
58
|
+
chmodSync(postMergeHook, 0o755);
|
|
59
|
+
} catch (chmodError) {
|
|
60
|
+
// chmod might fail on some systems, but file should still be executable
|
|
61
|
+
}
|
|
62
|
+
console.log('✅ Git hooks configured (including post-merge for GitHub integration)');
|
|
63
|
+
} else {
|
|
64
|
+
console.log('✅ Git hooks configured');
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.log('⚠️ Warning: Git hooks setup failed');
|
|
68
|
+
console.log(` Error: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* TaskIssueMapper - Maps task data to GitHub issue format
|
|
19
|
+
*
|
|
20
|
+
* Single Responsibility: Convert between task and issue representations
|
|
21
|
+
*
|
|
22
|
+
* This class provides static methods for mapping task JSON structure
|
|
23
|
+
* to GitHub issue format (title, body, labels).
|
|
24
|
+
*/
|
|
25
|
+
export class TaskIssueMapper {
|
|
26
|
+
/**
|
|
27
|
+
* Generate GitHub issue title from task
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} task - Task object
|
|
30
|
+
* @returns {string} Issue title
|
|
31
|
+
*/
|
|
32
|
+
static taskToIssueTitle(task) {
|
|
33
|
+
return `[${task.id}] ${task.title}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generate GitHub issue body from task
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} task - Task object
|
|
40
|
+
* @returns {string} Issue body in markdown format
|
|
41
|
+
*/
|
|
42
|
+
static taskToIssueBody(task) {
|
|
43
|
+
let body = `## Task: ${task.title}\n\n`;
|
|
44
|
+
|
|
45
|
+
if (task.description) {
|
|
46
|
+
body += `${task.description}\n\n`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (task.phase) {
|
|
50
|
+
body += `**Phase:** ${task.phase}\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (task.estimatedHours) {
|
|
54
|
+
body += `**Estimated Hours:** ${task.estimatedHours}h\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (task.completionCriteria && task.completionCriteria.length > 0) {
|
|
58
|
+
body += `\n### Completion Criteria\n\n`;
|
|
59
|
+
task.completionCriteria.forEach(criteria => {
|
|
60
|
+
body += `- [ ] ${criteria}\n`;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (task.dependencies && task.dependencies.length > 0) {
|
|
65
|
+
body += `\n### Dependencies\n\n`;
|
|
66
|
+
task.dependencies.forEach(dep => {
|
|
67
|
+
body += `- ${dep}\n`;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (task.testCases && task.testCases.length > 0) {
|
|
72
|
+
body += `\n### Test Cases\n\n`;
|
|
73
|
+
task.testCases.forEach(test => {
|
|
74
|
+
body += `- ${test}\n`;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
body += `\n---\n*Auto-created by Agentic15 Claude Zen*`;
|
|
79
|
+
|
|
80
|
+
return body;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Map task status and phase to GitHub labels
|
|
85
|
+
*
|
|
86
|
+
* @param {string} status - Task status (pending, in_progress, completed, blocked)
|
|
87
|
+
* @param {string|null} phase - Task phase (design, implementation, testing, etc.)
|
|
88
|
+
* @returns {string[]} Array of label names
|
|
89
|
+
*/
|
|
90
|
+
static taskStatusToLabels(status, phase = null) {
|
|
91
|
+
const labels = [];
|
|
92
|
+
|
|
93
|
+
// Status labels
|
|
94
|
+
switch (status) {
|
|
95
|
+
case 'pending':
|
|
96
|
+
labels.push('status: pending');
|
|
97
|
+
break;
|
|
98
|
+
case 'in_progress':
|
|
99
|
+
labels.push('status: in-progress');
|
|
100
|
+
break;
|
|
101
|
+
case 'completed':
|
|
102
|
+
labels.push('status: completed');
|
|
103
|
+
break;
|
|
104
|
+
case 'blocked':
|
|
105
|
+
labels.push('status: blocked');
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Phase labels
|
|
110
|
+
if (phase) {
|
|
111
|
+
const phaseLabel = this.phaseToLabel(phase);
|
|
112
|
+
if (phaseLabel) {
|
|
113
|
+
labels.push(phaseLabel);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return labels;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert phase name to GitHub label
|
|
122
|
+
*
|
|
123
|
+
* @param {string} phase - Phase name
|
|
124
|
+
* @returns {string} Label name
|
|
125
|
+
*/
|
|
126
|
+
static phaseToLabel(phase) {
|
|
127
|
+
const phaseLabels = {
|
|
128
|
+
'design': 'phase: design',
|
|
129
|
+
'implementation': 'phase: implementation',
|
|
130
|
+
'testing': 'phase: testing',
|
|
131
|
+
'deployment': 'phase: deployment',
|
|
132
|
+
'documentation': 'phase: documentation'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return phaseLabels[phase] || `phase: ${phase}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
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 { GitHubConfig } from '../core/GitHubConfig.js';
|
|
18
|
+
import { GitHubClient } from '../core/GitHubClient.js';
|
|
19
|
+
import { TaskIssueMapper } from '../core/TaskIssueMapper.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Update GitHub issue status when task state changes
|
|
23
|
+
*
|
|
24
|
+
* This utility function is used by various hooks to keep GitHub issues
|
|
25
|
+
* in sync with local task status changes.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} taskData - Task JSON object
|
|
28
|
+
* @param {string} projectRoot - Path to project root directory
|
|
29
|
+
* @param {string} newStatus - New task status (pending, in_progress, completed, blocked)
|
|
30
|
+
* @param {Object} options - Optional parameters
|
|
31
|
+
* @param {string} options.comment - Optional comment to add to the issue
|
|
32
|
+
* @returns {Promise<boolean>} - Success status
|
|
33
|
+
*/
|
|
34
|
+
export async function updateTaskGitHubStatus(taskData, projectRoot, newStatus, options = {}) {
|
|
35
|
+
try {
|
|
36
|
+
// Load GitHub configuration
|
|
37
|
+
const githubConfig = new GitHubConfig(projectRoot);
|
|
38
|
+
|
|
39
|
+
// Check if auto-update is enabled
|
|
40
|
+
if (!githubConfig.isAutoUpdateEnabled()) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if task has associated GitHub issue
|
|
45
|
+
if (!taskData.githubIssue) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Initialize GitHub client
|
|
50
|
+
const { owner, repo } = githubConfig.getRepoInfo();
|
|
51
|
+
const githubClient = new GitHubClient(
|
|
52
|
+
githubConfig.getToken(),
|
|
53
|
+
owner,
|
|
54
|
+
repo
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Update issue labels based on new status
|
|
58
|
+
const labels = TaskIssueMapper.taskStatusToLabels(newStatus, taskData.phase);
|
|
59
|
+
const labelsUpdated = await githubClient.updateIssueLabels(taskData.githubIssue, labels);
|
|
60
|
+
|
|
61
|
+
if (!labelsUpdated) {
|
|
62
|
+
console.warn(`⚠ Failed to update GitHub issue #${taskData.githubIssue} labels`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add comment if provided
|
|
67
|
+
if (options.comment) {
|
|
68
|
+
const commentAdded = await githubClient.addIssueComment(taskData.githubIssue, options.comment);
|
|
69
|
+
if (!commentAdded) {
|
|
70
|
+
console.warn(`⚠ Failed to add comment to GitHub issue #${taskData.githubIssue}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`✓ Updated GitHub issue #${taskData.githubIssue} to ${newStatus}`);
|
|
75
|
+
return true;
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.warn('⚠ Failed to update GitHub issue status:', error.message);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|