@trentapps/manager-protocol 1.1.3 → 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 +28 -0
- package/dist/analyzers/CSSAnalyzer.d.ts +180 -8
- package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/CSSAnalyzer.js +561 -105
- package/dist/analyzers/CSSAnalyzer.js.map +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 +1064 -24
- package/dist/engine/AuditLogger.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 +49 -72
- 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 +10 -14
- package/dist/engine/TaskManager.d.ts.map +1 -1
- package/dist/engine/TaskManager.js +169 -197
- 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/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 +2 -1
- package/dist/supervisor/ProjectTracker.d.ts.map +1 -1
- package/dist/supervisor/ProjectTracker.js +5 -9
- 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
|
|
@@ -78,6 +54,12 @@ export class TaskManager {
|
|
|
78
54
|
throw new Error('No repository specified and could not auto-detect from current directory. ' +
|
|
79
55
|
'Either provide projectName or run from within a git repository.');
|
|
80
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse repo string into owner/repo components
|
|
59
|
+
*/
|
|
60
|
+
parseRepo(repoString) {
|
|
61
|
+
return this.client.parseRepo(repoString);
|
|
62
|
+
}
|
|
81
63
|
/**
|
|
82
64
|
* Ensure a task exists before operating on it
|
|
83
65
|
* @throws Error if task is not found
|
|
@@ -89,47 +71,6 @@ export class TaskManager {
|
|
|
89
71
|
}
|
|
90
72
|
return task;
|
|
91
73
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Execute a gh command and return parsed JSON output
|
|
94
|
-
*/
|
|
95
|
-
async execGh(command) {
|
|
96
|
-
try {
|
|
97
|
-
const { stdout } = await execAsync(`gh ${command}`, {
|
|
98
|
-
maxBuffer: 10 * 1024 * 1024
|
|
99
|
-
});
|
|
100
|
-
return JSON.parse(stdout || '[]');
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
if (error.stdout === '' || error.stdout === '[]') {
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
// Parse error message for better feedback
|
|
107
|
-
const errMsg = error.stderr || error.message || 'Unknown error';
|
|
108
|
-
if (errMsg.includes('Could not resolve to a Repository')) {
|
|
109
|
-
throw new Error(`Repository not found. Check the repo name format (owner/repo).`);
|
|
110
|
-
}
|
|
111
|
-
if (errMsg.includes('HTTP 404')) {
|
|
112
|
-
throw new Error(`Not found - check repository access permissions.`);
|
|
113
|
-
}
|
|
114
|
-
if (errMsg.includes('HTTP 401') || errMsg.includes('authentication')) {
|
|
115
|
-
throw new Error(`Authentication failed. Run: gh auth login`);
|
|
116
|
-
}
|
|
117
|
-
throw new Error(`gh command failed: ${errMsg}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Execute a gh command without JSON output
|
|
122
|
-
*/
|
|
123
|
-
async execGhRaw(command) {
|
|
124
|
-
try {
|
|
125
|
-
const { stdout } = await execAsync(`gh ${command}`);
|
|
126
|
-
return stdout.trim();
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
const errMsg = error.stderr || error.message || 'Unknown error';
|
|
130
|
-
throw new Error(`gh command failed: ${errMsg}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
74
|
/**
|
|
134
75
|
* Ensure a label exists in the repo, create if not
|
|
135
76
|
*/
|
|
@@ -148,29 +89,29 @@ export class TaskManager {
|
|
|
148
89
|
[`${this.statusPrefix}cancelled`]: '808080',
|
|
149
90
|
'needs-approval': 'FF6B6B',
|
|
150
91
|
};
|
|
92
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
151
93
|
try {
|
|
152
94
|
// Check if label exists
|
|
153
|
-
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);
|
|
154
110
|
this.initializedLabels.add(cacheKey);
|
|
155
111
|
}
|
|
156
112
|
catch {
|
|
157
|
-
// Label
|
|
158
|
-
|
|
159
|
-
const color = labelColors[labelName] || '666666';
|
|
160
|
-
const description = labelName.startsWith(this.priorityPrefix)
|
|
161
|
-
? `Priority: ${labelName.replace(this.priorityPrefix, '')}`
|
|
162
|
-
: labelName.startsWith(this.statusPrefix)
|
|
163
|
-
? `Status: ${labelName.replace(this.statusPrefix, '')}`
|
|
164
|
-
: labelName === 'needs-approval'
|
|
165
|
-
? 'Significant change requiring approval before implementation'
|
|
166
|
-
: '';
|
|
167
|
-
await this.execGhRaw(`label create "${labelName}" --repo "${repo}" --color "${color}" --description "${description}" --force`);
|
|
168
|
-
this.initializedLabels.add(cacheKey);
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// Label creation failed, might be permissions - continue anyway
|
|
172
|
-
this.initializedLabels.add(cacheKey);
|
|
173
|
-
}
|
|
113
|
+
// Label creation failed, might be permissions - continue anyway
|
|
114
|
+
this.initializedLabels.add(cacheKey);
|
|
174
115
|
}
|
|
175
116
|
}
|
|
176
117
|
/**
|
|
@@ -182,7 +123,7 @@ export class TaskManager {
|
|
|
182
123
|
const priorityLabel = labels.find(l => l.startsWith(this.priorityPrefix));
|
|
183
124
|
const priority = (priorityLabel?.replace(this.priorityPrefix, '') || 'medium');
|
|
184
125
|
// Extract status from labels or state
|
|
185
|
-
let status = issue.state === '
|
|
126
|
+
let status = issue.state === 'open' ? 'pending' : 'completed';
|
|
186
127
|
const statusLabel = labels.find(l => l.startsWith(this.statusPrefix));
|
|
187
128
|
if (statusLabel) {
|
|
188
129
|
const labelStatus = statusLabel.replace(this.statusPrefix, '');
|
|
@@ -190,7 +131,7 @@ export class TaskManager {
|
|
|
190
131
|
status = labelStatus;
|
|
191
132
|
}
|
|
192
133
|
}
|
|
193
|
-
else if (issue.state === '
|
|
134
|
+
else if (issue.state === 'open') {
|
|
194
135
|
if (labels.includes('in-progress') || labels.includes('wip')) {
|
|
195
136
|
status = 'in_progress';
|
|
196
137
|
}
|
|
@@ -207,17 +148,17 @@ export class TaskManager {
|
|
|
207
148
|
description: issue.body || undefined,
|
|
208
149
|
status,
|
|
209
150
|
priority,
|
|
210
|
-
assignee: issue.assignees[0]?.login,
|
|
151
|
+
assignee: issue.assignees?.[0]?.login,
|
|
211
152
|
labels: cleanLabels.length > 0 ? cleanLabels : undefined,
|
|
212
153
|
dueDate: issue.milestone?.title,
|
|
213
|
-
createdAt: issue.
|
|
214
|
-
updatedAt: issue.
|
|
215
|
-
completedAt: issue.
|
|
216
|
-
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 }
|
|
217
158
|
};
|
|
218
159
|
}
|
|
219
160
|
/**
|
|
220
|
-
* Build labels array for
|
|
161
|
+
* Build labels array for issue
|
|
221
162
|
*/
|
|
222
163
|
buildLabels(priority, status, labels) {
|
|
223
164
|
const allLabels = [];
|
|
@@ -240,6 +181,7 @@ export class TaskManager {
|
|
|
240
181
|
*/
|
|
241
182
|
async createTask(params) {
|
|
242
183
|
const repo = await this.resolveRepo(params.projectName);
|
|
184
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
243
185
|
const allLabels = this.buildLabels(params.priority, 'pending', params.labels);
|
|
244
186
|
// Add needs-approval label if flagged
|
|
245
187
|
if (params.needsApproval) {
|
|
@@ -251,30 +193,29 @@ export class TaskManager {
|
|
|
251
193
|
await this.ensureLabel(repo, label);
|
|
252
194
|
}
|
|
253
195
|
}
|
|
254
|
-
//
|
|
255
|
-
let
|
|
256
|
-
if (params.description) {
|
|
257
|
-
cmd += ` --body ${escapeForShell(params.description)}`;
|
|
258
|
-
}
|
|
259
|
-
if (allLabels.length > 0) {
|
|
260
|
-
cmd += ` --label ${escapeForShell(allLabels.join(','))}`;
|
|
261
|
-
}
|
|
196
|
+
// Handle @me assignee - need to resolve to actual username
|
|
197
|
+
let assignees;
|
|
262
198
|
if (params.assignee) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
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
|
+
}
|
|
273
208
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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);
|
|
278
219
|
await auditLogger.log({
|
|
279
220
|
eventType: 'action_executed',
|
|
280
221
|
action: 'task_created',
|
|
@@ -284,7 +225,7 @@ export class TaskManager {
|
|
|
284
225
|
projectName: repo,
|
|
285
226
|
title: params.title,
|
|
286
227
|
priority: task.priority,
|
|
287
|
-
ghIssueUrl:
|
|
228
|
+
ghIssueUrl: issue.html_url
|
|
288
229
|
}
|
|
289
230
|
});
|
|
290
231
|
return task;
|
|
@@ -293,49 +234,58 @@ export class TaskManager {
|
|
|
293
234
|
* Get all tasks for a project
|
|
294
235
|
*/
|
|
295
236
|
async getTasksByProject(projectName, filter) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
stateFilter = '
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
labelFilters
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
+
});
|
|
321
283
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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 [];
|
|
326
288
|
}
|
|
327
|
-
const priorityOrder = {
|
|
328
|
-
critical: 0,
|
|
329
|
-
high: 1,
|
|
330
|
-
medium: 2,
|
|
331
|
-
low: 3
|
|
332
|
-
};
|
|
333
|
-
return tasks.sort((a, b) => {
|
|
334
|
-
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
335
|
-
if (priorityDiff !== 0)
|
|
336
|
-
return priorityDiff;
|
|
337
|
-
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
338
|
-
});
|
|
339
289
|
}
|
|
340
290
|
/**
|
|
341
291
|
* Get pending tasks for a project
|
|
@@ -355,7 +305,8 @@ export class TaskManager {
|
|
|
355
305
|
async getTask(projectName, taskId) {
|
|
356
306
|
try {
|
|
357
307
|
const repo = await this.resolveRepo(projectName);
|
|
358
|
-
const
|
|
308
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
309
|
+
const issue = await this.client.getIssue(owner, repoName, parseInt(taskId, 10));
|
|
359
310
|
return this.issueToTask(issue, repo);
|
|
360
311
|
}
|
|
361
312
|
catch {
|
|
@@ -368,14 +319,21 @@ export class TaskManager {
|
|
|
368
319
|
async updateTask(projectName, taskId, updates) {
|
|
369
320
|
try {
|
|
370
321
|
const repo = await this.resolveRepo(projectName);
|
|
322
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
323
|
+
const issueNumber = parseInt(taskId, 10);
|
|
371
324
|
// Verify task exists before attempting update
|
|
372
325
|
const existingTask = await this.ensureTaskExists(repo, taskId);
|
|
373
|
-
|
|
326
|
+
// Build update params
|
|
327
|
+
const updateParams = {
|
|
328
|
+
owner,
|
|
329
|
+
repo: repoName,
|
|
330
|
+
issue_number: issueNumber,
|
|
331
|
+
};
|
|
374
332
|
if (updates.title) {
|
|
375
|
-
|
|
333
|
+
updateParams.title = updates.title;
|
|
376
334
|
}
|
|
377
335
|
if (updates.description !== undefined) {
|
|
378
|
-
|
|
336
|
+
updateParams.body = updates.description || '';
|
|
379
337
|
}
|
|
380
338
|
if (updates.priority || updates.labels) {
|
|
381
339
|
const newLabels = this.buildLabels(updates.priority || existingTask.priority, updates.status || existingTask.status, updates.labels || existingTask.labels);
|
|
@@ -384,14 +342,13 @@ export class TaskManager {
|
|
|
384
342
|
await this.ensureLabel(repo, label);
|
|
385
343
|
}
|
|
386
344
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
345
|
+
// Get existing labels and merge with new ones
|
|
346
|
+
updateParams.labels = newLabels;
|
|
390
347
|
}
|
|
391
348
|
if (updates.assignee) {
|
|
392
|
-
|
|
349
|
+
updateParams.assignees = [updates.assignee];
|
|
393
350
|
}
|
|
394
|
-
await this.
|
|
351
|
+
await this.client.updateIssue(updateParams);
|
|
395
352
|
// Add comment if provided (with optional commit links)
|
|
396
353
|
if (updates.comment || updates.commits?.length) {
|
|
397
354
|
await this.addComment(repo, taskId, updates.comment, updates.commits);
|
|
@@ -430,6 +387,8 @@ export class TaskManager {
|
|
|
430
387
|
async addComment(projectName, taskId, comment, commits) {
|
|
431
388
|
try {
|
|
432
389
|
const repo = await this.resolveRepo(projectName);
|
|
390
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
391
|
+
const issueNumber = parseInt(taskId, 10);
|
|
433
392
|
// Verify task exists before attempting to add comment
|
|
434
393
|
await this.ensureTaskExists(repo, taskId);
|
|
435
394
|
// Build comment body
|
|
@@ -451,7 +410,7 @@ export class TaskManager {
|
|
|
451
410
|
if (!body) {
|
|
452
411
|
return false;
|
|
453
412
|
}
|
|
454
|
-
await this.
|
|
413
|
+
await this.client.addComment(owner, repoName, issueNumber, body);
|
|
455
414
|
await auditLogger.log({
|
|
456
415
|
eventType: 'action_executed',
|
|
457
416
|
action: 'task_comment_added',
|
|
@@ -488,28 +447,30 @@ export class TaskManager {
|
|
|
488
447
|
async updateTaskStatus(projectName, taskId, status) {
|
|
489
448
|
try {
|
|
490
449
|
const repo = await this.resolveRepo(projectName);
|
|
450
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
451
|
+
const issueNumber = parseInt(taskId, 10);
|
|
491
452
|
// Close or reopen based on status
|
|
492
453
|
if (status === 'completed' || status === 'cancelled') {
|
|
493
|
-
const reason = status === 'cancelled' ? '
|
|
494
|
-
await this.
|
|
454
|
+
const reason = status === 'cancelled' ? 'not_planned' : 'completed';
|
|
455
|
+
await this.client.closeIssue(owner, repoName, issueNumber, reason);
|
|
495
456
|
}
|
|
496
457
|
else {
|
|
497
458
|
const task = await this.getTask(repo, taskId);
|
|
498
459
|
if (task?.status === 'completed' || task?.status === 'cancelled') {
|
|
499
|
-
await this.
|
|
460
|
+
await this.client.reopenIssue(owner, repoName, issueNumber);
|
|
500
461
|
}
|
|
501
462
|
}
|
|
502
463
|
// Update status label
|
|
503
464
|
if (status !== 'pending' && status !== 'completed') {
|
|
504
465
|
const statusLabel = `${this.statusPrefix}${status}`;
|
|
505
466
|
await this.ensureLabel(repo, statusLabel);
|
|
506
|
-
await this.
|
|
467
|
+
await this.client.addLabels(owner, repoName, issueNumber, [statusLabel]);
|
|
507
468
|
}
|
|
508
469
|
// Remove old status labels
|
|
509
470
|
const oldStatuses = ['in_progress', 'blocked', 'cancelled'].filter(s => s !== status);
|
|
510
471
|
for (const oldStatus of oldStatuses) {
|
|
511
472
|
try {
|
|
512
|
-
await this.
|
|
473
|
+
await this.client.removeLabel(owner, repoName, issueNumber, `${this.statusPrefix}${oldStatus}`);
|
|
513
474
|
}
|
|
514
475
|
catch {
|
|
515
476
|
// Ignore - label might not exist
|
|
@@ -538,10 +499,12 @@ export class TaskManager {
|
|
|
538
499
|
*/
|
|
539
500
|
async blockTask(projectName, taskId, reason) {
|
|
540
501
|
const repo = await this.resolveRepo(projectName);
|
|
502
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
503
|
+
const issueNumber = parseInt(taskId, 10);
|
|
541
504
|
// Verify task exists before attempting to block
|
|
542
505
|
await this.ensureTaskExists(repo, taskId);
|
|
543
506
|
if (reason) {
|
|
544
|
-
await this.
|
|
507
|
+
await this.client.addComment(owner, repoName, issueNumber, `Blocked: ${reason}`);
|
|
545
508
|
}
|
|
546
509
|
return this.updateTaskStatus(repo, taskId, 'blocked');
|
|
547
510
|
}
|
|
@@ -551,7 +514,9 @@ export class TaskManager {
|
|
|
551
514
|
async deleteTask(projectName, taskId) {
|
|
552
515
|
try {
|
|
553
516
|
const repo = await this.resolveRepo(projectName);
|
|
554
|
-
|
|
517
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
518
|
+
const issueNumber = parseInt(taskId, 10);
|
|
519
|
+
await this.client.closeIssue(owner, repoName, issueNumber, 'not_planned');
|
|
555
520
|
await auditLogger.log({
|
|
556
521
|
eventType: 'action_executed',
|
|
557
522
|
action: 'task_deleted',
|
|
@@ -569,13 +534,14 @@ export class TaskManager {
|
|
|
569
534
|
*/
|
|
570
535
|
async listProjects() {
|
|
571
536
|
try {
|
|
572
|
-
const repos = await this.
|
|
537
|
+
const repos = await this.client.listRepos(20);
|
|
573
538
|
const projects = [];
|
|
574
539
|
const results = await Promise.allSettled(repos.slice(0, 10).map(async (repo) => {
|
|
575
540
|
try {
|
|
541
|
+
const { owner, repo: repoName } = this.parseRepo(repo.nameWithOwner);
|
|
576
542
|
const [openIssues, closedIssues] = await Promise.all([
|
|
577
|
-
this.
|
|
578
|
-
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 })
|
|
579
545
|
]);
|
|
580
546
|
const inProgressCount = openIssues.filter(i => i.labels?.some(l => l.name === `${this.statusPrefix}in_progress` ||
|
|
581
547
|
l.name === 'in-progress' ||
|
|
@@ -609,11 +575,11 @@ export class TaskManager {
|
|
|
609
575
|
async getProjectStats(projectName) {
|
|
610
576
|
try {
|
|
611
577
|
const repo = await this.resolveRepo(projectName);
|
|
612
|
-
|
|
613
|
-
//
|
|
578
|
+
const { owner, repo: repoName } = this.parseRepo(repo);
|
|
579
|
+
// Fetch issues for stats
|
|
614
580
|
const [openIssues, closedIssues] = await Promise.all([
|
|
615
|
-
this.
|
|
616
|
-
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 })
|
|
617
583
|
]);
|
|
618
584
|
const byStatus = {};
|
|
619
585
|
const byPriority = {};
|
|
@@ -639,7 +605,7 @@ export class TaskManager {
|
|
|
639
605
|
const priority = labels.find(l => l.startsWith(this.priorityPrefix))?.replace(this.priorityPrefix, '') || 'medium';
|
|
640
606
|
byPriority[priority] = (byPriority[priority] || 0) + 1;
|
|
641
607
|
// Count completed this week
|
|
642
|
-
if (issue.
|
|
608
|
+
if (issue.closed_at && new Date(issue.closed_at) >= oneWeekAgo) {
|
|
643
609
|
completedThisWeek++;
|
|
644
610
|
}
|
|
645
611
|
}
|
|
@@ -662,13 +628,13 @@ export class TaskManager {
|
|
|
662
628
|
*/
|
|
663
629
|
async searchTasks(query, projectName) {
|
|
664
630
|
try {
|
|
665
|
-
let
|
|
631
|
+
let searchQuery = query;
|
|
666
632
|
if (projectName) {
|
|
667
|
-
|
|
633
|
+
searchQuery = `repo:${projectName} ${query}`;
|
|
668
634
|
}
|
|
669
|
-
const issues = await this.
|
|
635
|
+
const issues = await this.client.searchIssues({ query: searchQuery, per_page: 50 });
|
|
670
636
|
return issues.map(issue => {
|
|
671
|
-
const repoMatch = issue.
|
|
637
|
+
const repoMatch = issue.html_url.match(/github\.com\/([^/]+\/[^/]+)\//);
|
|
672
638
|
const repo = repoMatch ? repoMatch[1] : projectName || 'unknown';
|
|
673
639
|
return this.issueToTask(issue, repo);
|
|
674
640
|
});
|
|
@@ -682,7 +648,13 @@ export class TaskManager {
|
|
|
682
648
|
*/
|
|
683
649
|
async clearCompletedTasks(projectName) {
|
|
684
650
|
const repo = await this.resolveRepo(projectName);
|
|
685
|
-
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
|
+
});
|
|
686
658
|
return closedIssues.length;
|
|
687
659
|
}
|
|
688
660
|
}
|