@trentapps/manager-protocol 1.1.2 → 1.2.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/README.md +29 -1
- package/dist/analyzers/CSSAnalyzer.d.ts +188 -8
- package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/CSSAnalyzer.js +794 -192
- package/dist/analyzers/CSSAnalyzer.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/config/dashboard.d.ts +55 -0
- package/dist/config/dashboard.d.ts.map +1 -0
- package/dist/config/dashboard.js +103 -0
- package/dist/config/dashboard.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +7 -0
- package/dist/config/index.js.map +1 -0
- package/dist/dashboard/httpDashboard.d.ts +100 -0
- package/dist/dashboard/httpDashboard.d.ts.map +1 -0
- package/dist/dashboard/httpDashboard.js +1276 -0
- package/dist/dashboard/httpDashboard.js.map +1 -0
- package/dist/dashboard/index.d.ts +6 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +7 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/engine/AuditLogger.d.ts +370 -2
- package/dist/engine/AuditLogger.d.ts.map +1 -1
- package/dist/engine/AuditLogger.js +1067 -24
- package/dist/engine/AuditLogger.js.map +1 -1
- package/dist/engine/GitHubApprovalManager.d.ts +13 -0
- package/dist/engine/GitHubApprovalManager.d.ts.map +1 -1
- package/dist/engine/GitHubApprovalManager.js +72 -46
- package/dist/engine/GitHubApprovalManager.js.map +1 -1
- package/dist/engine/GitHubClient.d.ts +183 -0
- package/dist/engine/GitHubClient.d.ts.map +1 -0
- package/dist/engine/GitHubClient.js +411 -0
- package/dist/engine/GitHubClient.js.map +1 -0
- package/dist/engine/RateLimiter.d.ts +5 -3
- package/dist/engine/RateLimiter.d.ts.map +1 -1
- package/dist/engine/RateLimiter.js +53 -70
- package/dist/engine/RateLimiter.js.map +1 -1
- package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
- package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
- package/dist/engine/RuleDependencyAnalyzer.js +475 -0
- package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
- package/dist/engine/RulesEngine.d.ts +102 -3
- package/dist/engine/RulesEngine.d.ts.map +1 -1
- package/dist/engine/RulesEngine.js +326 -21
- package/dist/engine/RulesEngine.js.map +1 -1
- package/dist/engine/TaskManager.d.ts +11 -10
- package/dist/engine/TaskManager.d.ts.map +1 -1
- package/dist/engine/TaskManager.js +180 -195
- package/dist/engine/TaskManager.js.map +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +5 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/rules/azure.d.ts.map +1 -1
- package/dist/rules/azure.js +12 -14
- package/dist/rules/azure.js.map +1 -1
- package/dist/rules/compliance.d.ts.map +1 -1
- package/dist/rules/compliance.js +23 -41
- package/dist/rules/compliance.js.map +1 -1
- package/dist/rules/condition-optimizer.d.ts +151 -0
- package/dist/rules/condition-optimizer.d.ts.map +1 -0
- package/dist/rules/condition-optimizer.js +479 -0
- package/dist/rules/condition-optimizer.js.map +1 -0
- package/dist/rules/css.d.ts.map +1 -1
- package/dist/rules/css.js +538 -0
- package/dist/rules/css.js.map +1 -1
- package/dist/rules/field-standards.d.ts +1172 -0
- package/dist/rules/field-standards.d.ts.map +1 -0
- package/dist/rules/field-standards.js +908 -0
- package/dist/rules/field-standards.js.map +1 -0
- package/dist/rules/flask.d.ts.map +1 -1
- package/dist/rules/flask.js +18 -31
- package/dist/rules/flask.js.map +1 -1
- package/dist/rules/index.d.ts +220 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +155 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/ml-ai.d.ts.map +1 -1
- package/dist/rules/ml-ai.js +11 -13
- package/dist/rules/ml-ai.js.map +1 -1
- package/dist/rules/patterns.d.ts +568 -0
- package/dist/rules/patterns.d.ts.map +1 -0
- package/dist/rules/patterns.js +1359 -0
- package/dist/rules/patterns.js.map +1 -0
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +580 -19
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/shared-patterns.d.ts +268 -0
- package/dist/rules/shared-patterns.d.ts.map +1 -0
- package/dist/rules/shared-patterns.js +556 -0
- package/dist/rules/shared-patterns.js.map +1 -0
- package/dist/rules/storage.d.ts +8 -2
- package/dist/rules/storage.d.ts.map +1 -1
- package/dist/rules/storage.js +541 -3
- package/dist/rules/storage.js.map +1 -1
- package/dist/rules/stripe.d.ts.map +1 -1
- package/dist/rules/stripe.js +19 -26
- package/dist/rules/stripe.js.map +1 -1
- package/dist/rules/websocket.d.ts.map +1 -1
- package/dist/rules/websocket.js +32 -40
- package/dist/rules/websocket.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +96 -17
- package/dist/server.js.map +1 -1
- package/dist/supervisor/AgentSupervisor.d.ts +52 -0
- package/dist/supervisor/AgentSupervisor.d.ts.map +1 -1
- package/dist/supervisor/AgentSupervisor.js +120 -1
- package/dist/supervisor/AgentSupervisor.js.map +1 -1
- package/dist/supervisor/ManagedServerRegistry.d.ts +139 -2
- package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -1
- package/dist/supervisor/ManagedServerRegistry.js +590 -6
- package/dist/supervisor/ManagedServerRegistry.js.map +1 -1
- package/dist/supervisor/ProjectTracker.d.ts +24 -2
- package/dist/supervisor/ProjectTracker.d.ts.map +1 -1
- package/dist/supervisor/ProjectTracker.js +151 -59
- package/dist/supervisor/ProjectTracker.js.map +1 -1
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/rule-tester.d.ts +217 -0
- package/dist/testing/rule-tester.d.ts.map +1 -0
- package/dist/testing/rule-tester.examples.d.ts +57 -0
- package/dist/testing/rule-tester.examples.d.ts.map +1 -0
- package/dist/testing/rule-tester.examples.js +375 -0
- package/dist/testing/rule-tester.examples.js.map +1 -0
- package/dist/testing/rule-tester.js +381 -0
- package/dist/testing/rule-tester.js.map +1 -0
- package/dist/testing/rule-validator.d.ts +141 -0
- package/dist/testing/rule-validator.d.ts.map +1 -0
- package/dist/testing/rule-validator.js +640 -0
- package/dist/testing/rule-validator.js.map +1 -0
- package/dist/types/index.d.ts +265 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +57 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/rate-limiting.d.ts +268 -0
- package/dist/utils/rate-limiting.d.ts.map +1 -0
- package/dist/utils/rate-limiting.js +403 -0
- package/dist/utils/rate-limiting.js.map +1 -0
- package/dist/utils/shared.d.ts +306 -0
- package/dist/utils/shared.d.ts.map +1 -0
- package/dist/utils/shared.js +464 -0
- package/dist/utils/shared.js.map +1 -0
- package/package.json +2 -1
|
@@ -1,70 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Enterprise Agent Supervisor - Task Manager
|
|
3
3
|
*
|
|
4
|
-
* Manages project tasks using GitHub Issues via the
|
|
4
|
+
* Manages project tasks using GitHub Issues via the Octokit API.
|
|
5
5
|
* Tasks are stored as GitHub Issues, providing persistence and visibility.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Auto-detects repo from current directory if not specified
|
|
9
9
|
* - Creates priority/status labels automatically
|
|
10
10
|
* - Caches repo detection for performance
|
|
11
|
-
* - Full GitHub Issues integration
|
|
11
|
+
* - Full GitHub Issues integration via Octokit
|
|
12
12
|
*/
|
|
13
|
-
import { exec } from 'child_process';
|
|
14
|
-
import { promisify } from 'util';
|
|
15
13
|
import { auditLogger } from './AuditLogger.js';
|
|
16
|
-
import {
|
|
17
|
-
const execAsync = promisify(exec);
|
|
14
|
+
import { gitHubClient } from './GitHubClient.js';
|
|
18
15
|
export class TaskManager {
|
|
19
16
|
priorityPrefix;
|
|
20
17
|
statusPrefix;
|
|
21
18
|
cachedRepo = null;
|
|
22
19
|
initializedLabels = new Set();
|
|
23
|
-
|
|
20
|
+
client;
|
|
24
21
|
constructor(options = {}) {
|
|
25
22
|
this.priorityPrefix = options.priorityLabelPrefix || 'priority:';
|
|
26
23
|
this.statusPrefix = options.statusLabelPrefix || 'status:';
|
|
24
|
+
this.client = gitHubClient;
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
29
|
-
* Verify
|
|
27
|
+
* Verify GitHub API authentication
|
|
30
28
|
*/
|
|
31
29
|
async verifyGh() {
|
|
32
|
-
|
|
33
|
-
return { ok: true };
|
|
34
|
-
try {
|
|
35
|
-
const { stdout } = await execAsync('gh auth status --json user 2>&1 || gh auth status');
|
|
36
|
-
this.ghVerified = true;
|
|
37
|
-
// Try to extract user
|
|
38
|
-
try {
|
|
39
|
-
const status = JSON.parse(stdout);
|
|
40
|
-
return { ok: true, user: status.user };
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return { ok: true };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
return {
|
|
48
|
-
ok: false,
|
|
49
|
-
error: 'gh CLI not authenticated. Run: gh auth login'
|
|
50
|
-
};
|
|
51
|
-
}
|
|
30
|
+
return this.client.verifyAuth();
|
|
52
31
|
}
|
|
53
32
|
/**
|
|
54
|
-
* Get the current repo from git remote
|
|
33
|
+
* Get the current repo from git remote
|
|
55
34
|
*/
|
|
56
35
|
async getCurrentRepo() {
|
|
57
36
|
if (this.cachedRepo)
|
|
58
37
|
return this.cachedRepo;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.cachedRepo = stdout.trim();
|
|
38
|
+
const repo = await this.client.getCurrentRepo();
|
|
39
|
+
if (repo) {
|
|
40
|
+
this.cachedRepo = `${repo.owner}/${repo.repo}`;
|
|
63
41
|
return this.cachedRepo;
|
|
64
42
|
}
|
|
65
|
-
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
43
|
+
return null;
|
|
68
44
|
}
|
|
69
45
|
/**
|
|
70
46
|
* Resolve project name - use provided or auto-detect
|
|
@@ -79,45 +55,21 @@ export class TaskManager {
|
|
|
79
55
|
'Either provide projectName or run from within a git repository.');
|
|
80
56
|
}
|
|
81
57
|
/**
|
|
82
|
-
*
|
|
58
|
+
* Parse repo string into owner/repo components
|
|
83
59
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const { stdout } = await execAsync(`gh ${command}`, {
|
|
87
|
-
maxBuffer: 10 * 1024 * 1024
|
|
88
|
-
});
|
|
89
|
-
return JSON.parse(stdout || '[]');
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
if (error.stdout === '' || error.stdout === '[]') {
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
// Parse error message for better feedback
|
|
96
|
-
const errMsg = error.stderr || error.message || 'Unknown error';
|
|
97
|
-
if (errMsg.includes('Could not resolve to a Repository')) {
|
|
98
|
-
throw new Error(`Repository not found. Check the repo name format (owner/repo).`);
|
|
99
|
-
}
|
|
100
|
-
if (errMsg.includes('HTTP 404')) {
|
|
101
|
-
throw new Error(`Not found - check repository access permissions.`);
|
|
102
|
-
}
|
|
103
|
-
if (errMsg.includes('HTTP 401') || errMsg.includes('authentication')) {
|
|
104
|
-
throw new Error(`Authentication failed. Run: gh auth login`);
|
|
105
|
-
}
|
|
106
|
-
throw new Error(`gh command failed: ${errMsg}`);
|
|
107
|
-
}
|
|
60
|
+
parseRepo(repoString) {
|
|
61
|
+
return this.client.parseRepo(repoString);
|
|
108
62
|
}
|
|
109
63
|
/**
|
|
110
|
-
*
|
|
64
|
+
* Ensure a task exists before operating on it
|
|
65
|
+
* @throws Error if task is not found
|
|
111
66
|
*/
|
|
112
|
-
async
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
const errMsg = error.stderr || error.message || 'Unknown error';
|
|
119
|
-
throw new Error(`gh command failed: ${errMsg}`);
|
|
67
|
+
async ensureTaskExists(repo, taskId) {
|
|
68
|
+
const task = await this.getTask(repo, taskId);
|
|
69
|
+
if (!task) {
|
|
70
|
+
throw new Error(`Task #${taskId} not found in ${repo}`);
|
|
120
71
|
}
|
|
72
|
+
return task;
|
|
121
73
|
}
|
|
122
74
|
/**
|
|
123
75
|
* Ensure a label exists in the repo, create if not
|
|
@@ -137,29 +89,29 @@ export class TaskManager {
|
|
|
137
89
|
[`${this.statusPrefix}cancelled`]: '808080',
|
|
138
90
|
'needs-approval': 'FF6B6B',
|
|
139
91
|
};
|
|
92
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
140
93
|
try {
|
|
141
94
|
// Check if label exists
|
|
142
|
-
await this.
|
|
95
|
+
const existing = await this.client.getLabel(owner, repoName, labelName);
|
|
96
|
+
if (existing) {
|
|
97
|
+
this.initializedLabels.add(cacheKey);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Label doesn't exist, create it
|
|
101
|
+
const color = labelColors[labelName] || '666666';
|
|
102
|
+
const description = labelName.startsWith(this.priorityPrefix)
|
|
103
|
+
? `Priority: ${labelName.replace(this.priorityPrefix, '')}`
|
|
104
|
+
: labelName.startsWith(this.statusPrefix)
|
|
105
|
+
? `Status: ${labelName.replace(this.statusPrefix, '')}`
|
|
106
|
+
: labelName === 'needs-approval'
|
|
107
|
+
? 'Significant change requiring approval before implementation'
|
|
108
|
+
: '';
|
|
109
|
+
await this.client.createLabel(owner, repoName, labelName, color, description || undefined);
|
|
143
110
|
this.initializedLabels.add(cacheKey);
|
|
144
111
|
}
|
|
145
112
|
catch {
|
|
146
|
-
// Label
|
|
147
|
-
|
|
148
|
-
const color = labelColors[labelName] || '666666';
|
|
149
|
-
const description = labelName.startsWith(this.priorityPrefix)
|
|
150
|
-
? `Priority: ${labelName.replace(this.priorityPrefix, '')}`
|
|
151
|
-
: labelName.startsWith(this.statusPrefix)
|
|
152
|
-
? `Status: ${labelName.replace(this.statusPrefix, '')}`
|
|
153
|
-
: labelName === 'needs-approval'
|
|
154
|
-
? 'Significant change requiring approval before implementation'
|
|
155
|
-
: '';
|
|
156
|
-
await this.execGhRaw(`label create "${labelName}" --repo "${repo}" --color "${color}" --description "${description}" --force`);
|
|
157
|
-
this.initializedLabels.add(cacheKey);
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
// Label creation failed, might be permissions - continue anyway
|
|
161
|
-
this.initializedLabels.add(cacheKey);
|
|
162
|
-
}
|
|
113
|
+
// Label creation failed, might be permissions - continue anyway
|
|
114
|
+
this.initializedLabels.add(cacheKey);
|
|
163
115
|
}
|
|
164
116
|
}
|
|
165
117
|
/**
|
|
@@ -171,7 +123,7 @@ export class TaskManager {
|
|
|
171
123
|
const priorityLabel = labels.find(l => l.startsWith(this.priorityPrefix));
|
|
172
124
|
const priority = (priorityLabel?.replace(this.priorityPrefix, '') || 'medium');
|
|
173
125
|
// Extract status from labels or state
|
|
174
|
-
let status = issue.state === '
|
|
126
|
+
let status = issue.state === 'open' ? 'pending' : 'completed';
|
|
175
127
|
const statusLabel = labels.find(l => l.startsWith(this.statusPrefix));
|
|
176
128
|
if (statusLabel) {
|
|
177
129
|
const labelStatus = statusLabel.replace(this.statusPrefix, '');
|
|
@@ -179,7 +131,7 @@ export class TaskManager {
|
|
|
179
131
|
status = labelStatus;
|
|
180
132
|
}
|
|
181
133
|
}
|
|
182
|
-
else if (issue.state === '
|
|
134
|
+
else if (issue.state === 'open') {
|
|
183
135
|
if (labels.includes('in-progress') || labels.includes('wip')) {
|
|
184
136
|
status = 'in_progress';
|
|
185
137
|
}
|
|
@@ -196,17 +148,17 @@ export class TaskManager {
|
|
|
196
148
|
description: issue.body || undefined,
|
|
197
149
|
status,
|
|
198
150
|
priority,
|
|
199
|
-
assignee: issue.assignees[0]?.login,
|
|
151
|
+
assignee: issue.assignees?.[0]?.login,
|
|
200
152
|
labels: cleanLabels.length > 0 ? cleanLabels : undefined,
|
|
201
153
|
dueDate: issue.milestone?.title,
|
|
202
|
-
createdAt: issue.
|
|
203
|
-
updatedAt: issue.
|
|
204
|
-
completedAt: issue.
|
|
205
|
-
metadata: { url: issue.
|
|
154
|
+
createdAt: issue.created_at,
|
|
155
|
+
updatedAt: issue.updated_at,
|
|
156
|
+
completedAt: issue.closed_at || undefined,
|
|
157
|
+
metadata: { url: issue.html_url }
|
|
206
158
|
};
|
|
207
159
|
}
|
|
208
160
|
/**
|
|
209
|
-
* Build labels array for
|
|
161
|
+
* Build labels array for issue
|
|
210
162
|
*/
|
|
211
163
|
buildLabels(priority, status, labels) {
|
|
212
164
|
const allLabels = [];
|
|
@@ -229,6 +181,7 @@ export class TaskManager {
|
|
|
229
181
|
*/
|
|
230
182
|
async createTask(params) {
|
|
231
183
|
const repo = await this.resolveRepo(params.projectName);
|
|
184
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
232
185
|
const allLabels = this.buildLabels(params.priority, 'pending', params.labels);
|
|
233
186
|
// Add needs-approval label if flagged
|
|
234
187
|
if (params.needsApproval) {
|
|
@@ -240,30 +193,29 @@ export class TaskManager {
|
|
|
240
193
|
await this.ensureLabel(repo, label);
|
|
241
194
|
}
|
|
242
195
|
}
|
|
243
|
-
//
|
|
244
|
-
let
|
|
245
|
-
if (params.description) {
|
|
246
|
-
cmd += ` --body ${escapeForShell(params.description)}`;
|
|
247
|
-
}
|
|
248
|
-
if (allLabels.length > 0) {
|
|
249
|
-
cmd += ` --label ${escapeForShell(allLabels.join(','))}`;
|
|
250
|
-
}
|
|
196
|
+
// Handle @me assignee - need to resolve to actual username
|
|
197
|
+
let assignees;
|
|
251
198
|
if (params.assignee) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
throw new Error(`Failed to parse issue number from: ${issueUrl}`);
|
|
199
|
+
if (params.assignee === '@me') {
|
|
200
|
+
const auth = await this.client.verifyAuth();
|
|
201
|
+
if (auth.ok && auth.user) {
|
|
202
|
+
assignees = [auth.user];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
assignees = [params.assignee];
|
|
207
|
+
}
|
|
262
208
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
209
|
+
// Create the issue
|
|
210
|
+
const issue = await this.client.createIssue({
|
|
211
|
+
owner,
|
|
212
|
+
repo: repoName,
|
|
213
|
+
title: params.title,
|
|
214
|
+
body: params.description,
|
|
215
|
+
labels: allLabels.length > 0 ? allLabels : undefined,
|
|
216
|
+
assignees,
|
|
217
|
+
});
|
|
218
|
+
const task = this.issueToTask(issue, repo);
|
|
267
219
|
await auditLogger.log({
|
|
268
220
|
eventType: 'action_executed',
|
|
269
221
|
action: 'task_created',
|
|
@@ -273,7 +225,7 @@ export class TaskManager {
|
|
|
273
225
|
projectName: repo,
|
|
274
226
|
title: params.title,
|
|
275
227
|
priority: task.priority,
|
|
276
|
-
ghIssueUrl:
|
|
228
|
+
ghIssueUrl: issue.html_url
|
|
277
229
|
}
|
|
278
230
|
});
|
|
279
231
|
return task;
|
|
@@ -282,49 +234,58 @@ export class TaskManager {
|
|
|
282
234
|
* Get all tasks for a project
|
|
283
235
|
*/
|
|
284
236
|
async getTasksByProject(projectName, filter) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
stateFilter = '
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
labelFilters
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
237
|
+
try {
|
|
238
|
+
const repo = await this.resolveRepo(projectName);
|
|
239
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
240
|
+
let stateFilter = 'all';
|
|
241
|
+
if (filter?.status === 'completed' || filter?.status === 'cancelled') {
|
|
242
|
+
stateFilter = 'closed';
|
|
243
|
+
}
|
|
244
|
+
else if (filter?.status) {
|
|
245
|
+
// Status is pending, in_progress, or blocked - use open issues
|
|
246
|
+
stateFilter = 'open';
|
|
247
|
+
}
|
|
248
|
+
// Build labels filter
|
|
249
|
+
const labelFilters = [];
|
|
250
|
+
if (filter?.priority) {
|
|
251
|
+
labelFilters.push(`${this.priorityPrefix}${filter.priority}`);
|
|
252
|
+
}
|
|
253
|
+
if (filter?.status && !['pending', 'completed'].includes(filter.status)) {
|
|
254
|
+
labelFilters.push(`${this.statusPrefix}${filter.status}`);
|
|
255
|
+
}
|
|
256
|
+
if (filter?.labels) {
|
|
257
|
+
labelFilters.push(...filter.labels);
|
|
258
|
+
}
|
|
259
|
+
const issues = await this.client.listIssues({
|
|
260
|
+
owner,
|
|
261
|
+
repo: repoName,
|
|
262
|
+
state: stateFilter,
|
|
263
|
+
labels: labelFilters.length > 0 ? labelFilters.join(',') : undefined,
|
|
264
|
+
assignee: filter?.assignee,
|
|
265
|
+
per_page: 100,
|
|
266
|
+
});
|
|
267
|
+
let tasks = issues.map(issue => this.issueToTask(issue, repo));
|
|
268
|
+
if (filter?.status) {
|
|
269
|
+
tasks = tasks.filter(t => t.status === filter.status);
|
|
270
|
+
}
|
|
271
|
+
const priorityOrder = {
|
|
272
|
+
critical: 0,
|
|
273
|
+
high: 1,
|
|
274
|
+
medium: 2,
|
|
275
|
+
low: 3
|
|
276
|
+
};
|
|
277
|
+
return tasks.sort((a, b) => {
|
|
278
|
+
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
279
|
+
if (priorityDiff !== 0)
|
|
280
|
+
return priorityDiff;
|
|
281
|
+
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
282
|
+
});
|
|
310
283
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
284
|
+
catch (error) {
|
|
285
|
+
// Return empty array for 404 errors (repo doesn't exist or no access)
|
|
286
|
+
// This maintains backward compatibility with the gh CLI version
|
|
287
|
+
return [];
|
|
315
288
|
}
|
|
316
|
-
const priorityOrder = {
|
|
317
|
-
critical: 0,
|
|
318
|
-
high: 1,
|
|
319
|
-
medium: 2,
|
|
320
|
-
low: 3
|
|
321
|
-
};
|
|
322
|
-
return tasks.sort((a, b) => {
|
|
323
|
-
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
324
|
-
if (priorityDiff !== 0)
|
|
325
|
-
return priorityDiff;
|
|
326
|
-
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
327
|
-
});
|
|
328
289
|
}
|
|
329
290
|
/**
|
|
330
291
|
* Get pending tasks for a project
|
|
@@ -344,7 +305,8 @@ export class TaskManager {
|
|
|
344
305
|
async getTask(projectName, taskId) {
|
|
345
306
|
try {
|
|
346
307
|
const repo = await this.resolveRepo(projectName);
|
|
347
|
-
const
|
|
308
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
309
|
+
const issue = await this.client.getIssue(owner, repoName, parseInt(taskId, 10));
|
|
348
310
|
return this.issueToTask(issue, repo);
|
|
349
311
|
}
|
|
350
312
|
catch {
|
|
@@ -357,18 +319,21 @@ export class TaskManager {
|
|
|
357
319
|
async updateTask(projectName, taskId, updates) {
|
|
358
320
|
try {
|
|
359
321
|
const repo = await this.resolveRepo(projectName);
|
|
322
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
323
|
+
const issueNumber = parseInt(taskId, 10);
|
|
360
324
|
// Verify task exists before attempting update
|
|
361
|
-
const existingTask = await this.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
325
|
+
const existingTask = await this.ensureTaskExists(repo, taskId);
|
|
326
|
+
// Build update params
|
|
327
|
+
const updateParams = {
|
|
328
|
+
owner,
|
|
329
|
+
repo: repoName,
|
|
330
|
+
issue_number: issueNumber,
|
|
331
|
+
};
|
|
367
332
|
if (updates.title) {
|
|
368
|
-
|
|
333
|
+
updateParams.title = updates.title;
|
|
369
334
|
}
|
|
370
335
|
if (updates.description !== undefined) {
|
|
371
|
-
|
|
336
|
+
updateParams.body = updates.description || '';
|
|
372
337
|
}
|
|
373
338
|
if (updates.priority || updates.labels) {
|
|
374
339
|
const newLabels = this.buildLabels(updates.priority || existingTask.priority, updates.status || existingTask.status, updates.labels || existingTask.labels);
|
|
@@ -377,14 +342,13 @@ export class TaskManager {
|
|
|
377
342
|
await this.ensureLabel(repo, label);
|
|
378
343
|
}
|
|
379
344
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
345
|
+
// Get existing labels and merge with new ones
|
|
346
|
+
updateParams.labels = newLabels;
|
|
383
347
|
}
|
|
384
348
|
if (updates.assignee) {
|
|
385
|
-
|
|
349
|
+
updateParams.assignees = [updates.assignee];
|
|
386
350
|
}
|
|
387
|
-
await this.
|
|
351
|
+
await this.client.updateIssue(updateParams);
|
|
388
352
|
// Add comment if provided (with optional commit links)
|
|
389
353
|
if (updates.comment || updates.commits?.length) {
|
|
390
354
|
await this.addComment(repo, taskId, updates.comment, updates.commits);
|
|
@@ -423,6 +387,10 @@ export class TaskManager {
|
|
|
423
387
|
async addComment(projectName, taskId, comment, commits) {
|
|
424
388
|
try {
|
|
425
389
|
const repo = await this.resolveRepo(projectName);
|
|
390
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
391
|
+
const issueNumber = parseInt(taskId, 10);
|
|
392
|
+
// Verify task exists before attempting to add comment
|
|
393
|
+
await this.ensureTaskExists(repo, taskId);
|
|
426
394
|
// Build comment body
|
|
427
395
|
let body = '';
|
|
428
396
|
if (comment) {
|
|
@@ -442,7 +410,7 @@ export class TaskManager {
|
|
|
442
410
|
if (!body) {
|
|
443
411
|
return false;
|
|
444
412
|
}
|
|
445
|
-
await this.
|
|
413
|
+
await this.client.addComment(owner, repoName, issueNumber, body);
|
|
446
414
|
await auditLogger.log({
|
|
447
415
|
eventType: 'action_executed',
|
|
448
416
|
action: 'task_comment_added',
|
|
@@ -466,6 +434,8 @@ export class TaskManager {
|
|
|
466
434
|
*/
|
|
467
435
|
async closeWithComment(projectName, taskId, resolution, commits) {
|
|
468
436
|
const repo = await this.resolveRepo(projectName);
|
|
437
|
+
// Verify task exists before attempting to close
|
|
438
|
+
await this.ensureTaskExists(repo, taskId);
|
|
469
439
|
// Add the resolution comment
|
|
470
440
|
await this.addComment(repo, taskId, `**Resolution:** ${resolution}`, commits);
|
|
471
441
|
// Close the issue
|
|
@@ -477,28 +447,30 @@ export class TaskManager {
|
|
|
477
447
|
async updateTaskStatus(projectName, taskId, status) {
|
|
478
448
|
try {
|
|
479
449
|
const repo = await this.resolveRepo(projectName);
|
|
450
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
451
|
+
const issueNumber = parseInt(taskId, 10);
|
|
480
452
|
// Close or reopen based on status
|
|
481
453
|
if (status === 'completed' || status === 'cancelled') {
|
|
482
|
-
const reason = status === 'cancelled' ? '
|
|
483
|
-
await this.
|
|
454
|
+
const reason = status === 'cancelled' ? 'not_planned' : 'completed';
|
|
455
|
+
await this.client.closeIssue(owner, repoName, issueNumber, reason);
|
|
484
456
|
}
|
|
485
457
|
else {
|
|
486
458
|
const task = await this.getTask(repo, taskId);
|
|
487
459
|
if (task?.status === 'completed' || task?.status === 'cancelled') {
|
|
488
|
-
await this.
|
|
460
|
+
await this.client.reopenIssue(owner, repoName, issueNumber);
|
|
489
461
|
}
|
|
490
462
|
}
|
|
491
463
|
// Update status label
|
|
492
464
|
if (status !== 'pending' && status !== 'completed') {
|
|
493
465
|
const statusLabel = `${this.statusPrefix}${status}`;
|
|
494
466
|
await this.ensureLabel(repo, statusLabel);
|
|
495
|
-
await this.
|
|
467
|
+
await this.client.addLabels(owner, repoName, issueNumber, [statusLabel]);
|
|
496
468
|
}
|
|
497
469
|
// Remove old status labels
|
|
498
470
|
const oldStatuses = ['in_progress', 'blocked', 'cancelled'].filter(s => s !== status);
|
|
499
471
|
for (const oldStatus of oldStatuses) {
|
|
500
472
|
try {
|
|
501
|
-
await this.
|
|
473
|
+
await this.client.removeLabel(owner, repoName, issueNumber, `${this.statusPrefix}${oldStatus}`);
|
|
502
474
|
}
|
|
503
475
|
catch {
|
|
504
476
|
// Ignore - label might not exist
|
|
@@ -527,8 +499,12 @@ export class TaskManager {
|
|
|
527
499
|
*/
|
|
528
500
|
async blockTask(projectName, taskId, reason) {
|
|
529
501
|
const repo = await this.resolveRepo(projectName);
|
|
502
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
503
|
+
const issueNumber = parseInt(taskId, 10);
|
|
504
|
+
// Verify task exists before attempting to block
|
|
505
|
+
await this.ensureTaskExists(repo, taskId);
|
|
530
506
|
if (reason) {
|
|
531
|
-
await this.
|
|
507
|
+
await this.client.addComment(owner, repoName, issueNumber, `Blocked: ${reason}`);
|
|
532
508
|
}
|
|
533
509
|
return this.updateTaskStatus(repo, taskId, 'blocked');
|
|
534
510
|
}
|
|
@@ -538,7 +514,9 @@ export class TaskManager {
|
|
|
538
514
|
async deleteTask(projectName, taskId) {
|
|
539
515
|
try {
|
|
540
516
|
const repo = await this.resolveRepo(projectName);
|
|
541
|
-
|
|
517
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
518
|
+
const issueNumber = parseInt(taskId, 10);
|
|
519
|
+
await this.client.closeIssue(owner, repoName, issueNumber, 'not_planned');
|
|
542
520
|
await auditLogger.log({
|
|
543
521
|
eventType: 'action_executed',
|
|
544
522
|
action: 'task_deleted',
|
|
@@ -556,13 +534,14 @@ export class TaskManager {
|
|
|
556
534
|
*/
|
|
557
535
|
async listProjects() {
|
|
558
536
|
try {
|
|
559
|
-
const repos = await this.
|
|
537
|
+
const repos = await this.client.listRepos(20);
|
|
560
538
|
const projects = [];
|
|
561
539
|
const results = await Promise.allSettled(repos.slice(0, 10).map(async (repo) => {
|
|
562
540
|
try {
|
|
541
|
+
const { owner, repo: repoName } = this.parseRepo(repo.nameWithOwner);
|
|
563
542
|
const [openIssues, closedIssues] = await Promise.all([
|
|
564
|
-
this.
|
|
565
|
-
this.
|
|
543
|
+
this.client.listIssues({ owner, repo: repoName, state: 'open', per_page: 100 }),
|
|
544
|
+
this.client.listIssues({ owner, repo: repoName, state: 'closed', per_page: 100 })
|
|
566
545
|
]);
|
|
567
546
|
const inProgressCount = openIssues.filter(i => i.labels?.some(l => l.name === `${this.statusPrefix}in_progress` ||
|
|
568
547
|
l.name === 'in-progress' ||
|
|
@@ -596,11 +575,11 @@ export class TaskManager {
|
|
|
596
575
|
async getProjectStats(projectName) {
|
|
597
576
|
try {
|
|
598
577
|
const repo = await this.resolveRepo(projectName);
|
|
599
|
-
|
|
600
|
-
//
|
|
578
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
579
|
+
// Fetch issues for stats
|
|
601
580
|
const [openIssues, closedIssues] = await Promise.all([
|
|
602
|
-
this.
|
|
603
|
-
this.
|
|
581
|
+
this.client.listIssues({ owner, repo: repoName, state: 'open', per_page: 100 }),
|
|
582
|
+
this.client.listIssues({ owner, repo: repoName, state: 'closed', per_page: 100 })
|
|
604
583
|
]);
|
|
605
584
|
const byStatus = {};
|
|
606
585
|
const byPriority = {};
|
|
@@ -626,7 +605,7 @@ export class TaskManager {
|
|
|
626
605
|
const priority = labels.find(l => l.startsWith(this.priorityPrefix))?.replace(this.priorityPrefix, '') || 'medium';
|
|
627
606
|
byPriority[priority] = (byPriority[priority] || 0) + 1;
|
|
628
607
|
// Count completed this week
|
|
629
|
-
if (issue.
|
|
608
|
+
if (issue.closed_at && new Date(issue.closed_at) >= oneWeekAgo) {
|
|
630
609
|
completedThisWeek++;
|
|
631
610
|
}
|
|
632
611
|
}
|
|
@@ -649,13 +628,13 @@ export class TaskManager {
|
|
|
649
628
|
*/
|
|
650
629
|
async searchTasks(query, projectName) {
|
|
651
630
|
try {
|
|
652
|
-
let
|
|
631
|
+
let searchQuery = query;
|
|
653
632
|
if (projectName) {
|
|
654
|
-
|
|
633
|
+
searchQuery = `repo:${projectName} ${query}`;
|
|
655
634
|
}
|
|
656
|
-
const issues = await this.
|
|
635
|
+
const issues = await this.client.searchIssues({ query: searchQuery, per_page: 50 });
|
|
657
636
|
return issues.map(issue => {
|
|
658
|
-
const repoMatch = issue.
|
|
637
|
+
const repoMatch = issue.html_url.match(/github\.com\/([^/]+\/[^/]+)\//);
|
|
659
638
|
const repo = repoMatch ? repoMatch[1] : projectName || 'unknown';
|
|
660
639
|
return this.issueToTask(issue, repo);
|
|
661
640
|
});
|
|
@@ -669,7 +648,13 @@ export class TaskManager {
|
|
|
669
648
|
*/
|
|
670
649
|
async clearCompletedTasks(projectName) {
|
|
671
650
|
const repo = await this.resolveRepo(projectName);
|
|
672
|
-
const
|
|
651
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
652
|
+
const closedIssues = await this.client.listIssues({
|
|
653
|
+
owner,
|
|
654
|
+
repo: repoName,
|
|
655
|
+
state: 'closed',
|
|
656
|
+
per_page: 100
|
|
657
|
+
});
|
|
673
658
|
return closedIssues.length;
|
|
674
659
|
}
|
|
675
660
|
}
|