@trentapps/manager-protocol 1.3.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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +639 -0
  3. package/dist/analyzers/ArchitectureDetector.d.ts +44 -0
  4. package/dist/analyzers/ArchitectureDetector.d.ts.map +1 -0
  5. package/dist/analyzers/ArchitectureDetector.js +218 -0
  6. package/dist/analyzers/ArchitectureDetector.js.map +1 -0
  7. package/dist/analyzers/CSSAnalyzer.d.ts +284 -0
  8. package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -0
  9. package/dist/analyzers/CSSAnalyzer.js +1180 -0
  10. package/dist/analyzers/CSSAnalyzer.js.map +1 -0
  11. package/dist/analyzers/index.d.ts +5 -0
  12. package/dist/analyzers/index.d.ts.map +1 -0
  13. package/dist/analyzers/index.js +5 -0
  14. package/dist/analyzers/index.js.map +1 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +174 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/design-system/index.d.ts +6 -0
  20. package/dist/design-system/index.d.ts.map +1 -0
  21. package/dist/design-system/index.js +6 -0
  22. package/dist/design-system/index.js.map +1 -0
  23. package/dist/design-system/tokens.d.ts +106 -0
  24. package/dist/design-system/tokens.d.ts.map +1 -0
  25. package/dist/design-system/tokens.js +554 -0
  26. package/dist/design-system/tokens.js.map +1 -0
  27. package/dist/engine/AuditLogger.d.ts +506 -0
  28. package/dist/engine/AuditLogger.d.ts.map +1 -0
  29. package/dist/engine/AuditLogger.js +1491 -0
  30. package/dist/engine/AuditLogger.js.map +1 -0
  31. package/dist/engine/GitHubApprovalManager.d.ts +123 -0
  32. package/dist/engine/GitHubApprovalManager.d.ts.map +1 -0
  33. package/dist/engine/GitHubApprovalManager.js +347 -0
  34. package/dist/engine/GitHubApprovalManager.js.map +1 -0
  35. package/dist/engine/GitHubClient.d.ts +183 -0
  36. package/dist/engine/GitHubClient.d.ts.map +1 -0
  37. package/dist/engine/GitHubClient.js +411 -0
  38. package/dist/engine/GitHubClient.js.map +1 -0
  39. package/dist/engine/RateLimiter.d.ts +81 -0
  40. package/dist/engine/RateLimiter.d.ts.map +1 -0
  41. package/dist/engine/RateLimiter.js +215 -0
  42. package/dist/engine/RateLimiter.js.map +1 -0
  43. package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
  44. package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
  45. package/dist/engine/RuleDependencyAnalyzer.js +475 -0
  46. package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
  47. package/dist/engine/RulesEngine.d.ts +176 -0
  48. package/dist/engine/RulesEngine.d.ts.map +1 -0
  49. package/dist/engine/RulesEngine.js +705 -0
  50. package/dist/engine/RulesEngine.js.map +1 -0
  51. package/dist/engine/TaskManager.d.ts +174 -0
  52. package/dist/engine/TaskManager.d.ts.map +1 -0
  53. package/dist/engine/TaskManager.js +663 -0
  54. package/dist/engine/TaskManager.js.map +1 -0
  55. package/dist/engine/index.d.ts +11 -0
  56. package/dist/engine/index.d.ts.map +1 -0
  57. package/dist/engine/index.js +13 -0
  58. package/dist/engine/index.js.map +1 -0
  59. package/dist/index.d.ts +21 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +29 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/rules/architecture.d.ts +9 -0
  64. package/dist/rules/architecture.d.ts.map +1 -0
  65. package/dist/rules/architecture.js +322 -0
  66. package/dist/rules/architecture.js.map +1 -0
  67. package/dist/rules/azure.d.ts +7 -0
  68. package/dist/rules/azure.d.ts.map +1 -0
  69. package/dist/rules/azure.js +136 -0
  70. package/dist/rules/azure.js.map +1 -0
  71. package/dist/rules/compliance.d.ts +9 -0
  72. package/dist/rules/compliance.d.ts.map +1 -0
  73. package/dist/rules/compliance.js +286 -0
  74. package/dist/rules/compliance.js.map +1 -0
  75. package/dist/rules/condition-optimizer.d.ts +151 -0
  76. package/dist/rules/condition-optimizer.d.ts.map +1 -0
  77. package/dist/rules/condition-optimizer.js +479 -0
  78. package/dist/rules/condition-optimizer.js.map +1 -0
  79. package/dist/rules/css.d.ts +10 -0
  80. package/dist/rules/css.d.ts.map +1 -0
  81. package/dist/rules/css.js +1777 -0
  82. package/dist/rules/css.js.map +1 -0
  83. package/dist/rules/field-standards.d.ts +1172 -0
  84. package/dist/rules/field-standards.d.ts.map +1 -0
  85. package/dist/rules/field-standards.js +908 -0
  86. package/dist/rules/field-standards.js.map +1 -0
  87. package/dist/rules/flask.d.ts +7 -0
  88. package/dist/rules/flask.d.ts.map +1 -0
  89. package/dist/rules/flask.js +142 -0
  90. package/dist/rules/flask.js.map +1 -0
  91. package/dist/rules/index.d.ts +827 -0
  92. package/dist/rules/index.d.ts.map +1 -0
  93. package/dist/rules/index.js +556 -0
  94. package/dist/rules/index.js.map +1 -0
  95. package/dist/rules/ml-ai.d.ts +7 -0
  96. package/dist/rules/ml-ai.d.ts.map +1 -0
  97. package/dist/rules/ml-ai.js +148 -0
  98. package/dist/rules/ml-ai.js.map +1 -0
  99. package/dist/rules/operational.d.ts +9 -0
  100. package/dist/rules/operational.d.ts.map +1 -0
  101. package/dist/rules/operational.js +318 -0
  102. package/dist/rules/operational.js.map +1 -0
  103. package/dist/rules/patterns.d.ts +568 -0
  104. package/dist/rules/patterns.d.ts.map +1 -0
  105. package/dist/rules/patterns.js +1359 -0
  106. package/dist/rules/patterns.js.map +1 -0
  107. package/dist/rules/security.d.ts +9 -0
  108. package/dist/rules/security.d.ts.map +1 -0
  109. package/dist/rules/security.js +848 -0
  110. package/dist/rules/security.js.map +1 -0
  111. package/dist/rules/shared-patterns.d.ts +268 -0
  112. package/dist/rules/shared-patterns.d.ts.map +1 -0
  113. package/dist/rules/shared-patterns.js +556 -0
  114. package/dist/rules/shared-patterns.js.map +1 -0
  115. package/dist/rules/storage.d.ts +13 -0
  116. package/dist/rules/storage.d.ts.map +1 -0
  117. package/dist/rules/storage.js +672 -0
  118. package/dist/rules/storage.js.map +1 -0
  119. package/dist/rules/stripe.d.ts +7 -0
  120. package/dist/rules/stripe.d.ts.map +1 -0
  121. package/dist/rules/stripe.js +133 -0
  122. package/dist/rules/stripe.js.map +1 -0
  123. package/dist/rules/testing.d.ts +7 -0
  124. package/dist/rules/testing.d.ts.map +1 -0
  125. package/dist/rules/testing.js +135 -0
  126. package/dist/rules/testing.js.map +1 -0
  127. package/dist/rules/ux.d.ts +9 -0
  128. package/dist/rules/ux.d.ts.map +1 -0
  129. package/dist/rules/ux.js +280 -0
  130. package/dist/rules/ux.js.map +1 -0
  131. package/dist/rules/websocket.d.ts +7 -0
  132. package/dist/rules/websocket.d.ts.map +1 -0
  133. package/dist/rules/websocket.js +128 -0
  134. package/dist/rules/websocket.js.map +1 -0
  135. package/dist/server.d.ts +43 -0
  136. package/dist/server.d.ts.map +1 -0
  137. package/dist/server.js +1967 -0
  138. package/dist/server.js.map +1 -0
  139. package/dist/supervisor/AgentSupervisor.d.ts +195 -0
  140. package/dist/supervisor/AgentSupervisor.d.ts.map +1 -0
  141. package/dist/supervisor/AgentSupervisor.js +569 -0
  142. package/dist/supervisor/AgentSupervisor.js.map +1 -0
  143. package/dist/supervisor/ManagedServerRegistry.d.ts +185 -0
  144. package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -0
  145. package/dist/supervisor/ManagedServerRegistry.js +729 -0
  146. package/dist/supervisor/ManagedServerRegistry.js.map +1 -0
  147. package/dist/supervisor/ProjectTracker.d.ts +210 -0
  148. package/dist/supervisor/ProjectTracker.d.ts.map +1 -0
  149. package/dist/supervisor/ProjectTracker.js +709 -0
  150. package/dist/supervisor/ProjectTracker.js.map +1 -0
  151. package/dist/supervisor/index.d.ts +6 -0
  152. package/dist/supervisor/index.d.ts.map +1 -0
  153. package/dist/supervisor/index.js +6 -0
  154. package/dist/supervisor/index.js.map +1 -0
  155. package/dist/testing/index.d.ts +11 -0
  156. package/dist/testing/index.d.ts.map +1 -0
  157. package/dist/testing/index.js +12 -0
  158. package/dist/testing/index.js.map +1 -0
  159. package/dist/testing/rule-tester.d.ts +217 -0
  160. package/dist/testing/rule-tester.d.ts.map +1 -0
  161. package/dist/testing/rule-tester.examples.d.ts +57 -0
  162. package/dist/testing/rule-tester.examples.d.ts.map +1 -0
  163. package/dist/testing/rule-tester.examples.js +375 -0
  164. package/dist/testing/rule-tester.examples.js.map +1 -0
  165. package/dist/testing/rule-tester.js +381 -0
  166. package/dist/testing/rule-tester.js.map +1 -0
  167. package/dist/testing/rule-validator.d.ts +141 -0
  168. package/dist/testing/rule-validator.d.ts.map +1 -0
  169. package/dist/testing/rule-validator.js +640 -0
  170. package/dist/testing/rule-validator.js.map +1 -0
  171. package/dist/types/index.d.ts +1282 -0
  172. package/dist/types/index.d.ts.map +1 -0
  173. package/dist/types/index.js +386 -0
  174. package/dist/types/index.js.map +1 -0
  175. package/dist/utils/errors.d.ts +86 -0
  176. package/dist/utils/errors.d.ts.map +1 -0
  177. package/dist/utils/errors.js +171 -0
  178. package/dist/utils/errors.js.map +1 -0
  179. package/dist/utils/index.d.ts +7 -0
  180. package/dist/utils/index.d.ts.map +1 -0
  181. package/dist/utils/index.js +7 -0
  182. package/dist/utils/index.js.map +1 -0
  183. package/dist/utils/rate-limiting.d.ts +268 -0
  184. package/dist/utils/rate-limiting.d.ts.map +1 -0
  185. package/dist/utils/rate-limiting.js +403 -0
  186. package/dist/utils/rate-limiting.js.map +1 -0
  187. package/dist/utils/shared.d.ts +306 -0
  188. package/dist/utils/shared.d.ts.map +1 -0
  189. package/dist/utils/shared.js +464 -0
  190. package/dist/utils/shared.js.map +1 -0
  191. package/dist/utils/shell.d.ts +22 -0
  192. package/dist/utils/shell.d.ts.map +1 -0
  193. package/dist/utils/shell.js +29 -0
  194. package/dist/utils/shell.js.map +1 -0
  195. package/package.json +67 -0
@@ -0,0 +1,663 @@
1
+ /**
2
+ * Enterprise Agent Supervisor - Task Manager
3
+ *
4
+ * Manages project tasks using GitHub Issues via the Octokit API.
5
+ * Tasks are stored as GitHub Issues, providing persistence and visibility.
6
+ *
7
+ * Features:
8
+ * - Auto-detects repo from current directory if not specified
9
+ * - Creates priority/status labels automatically
10
+ * - Caches repo detection for performance
11
+ * - Full GitHub Issues integration via Octokit
12
+ */
13
+ import { auditLogger } from './AuditLogger.js';
14
+ import { gitHubClient } from './GitHubClient.js';
15
+ export class TaskManager {
16
+ priorityPrefix;
17
+ statusPrefix;
18
+ cachedRepo = null;
19
+ initializedLabels = new Set();
20
+ client;
21
+ constructor(options = {}) {
22
+ this.priorityPrefix = options.priorityLabelPrefix || 'priority:';
23
+ this.statusPrefix = options.statusLabelPrefix || 'status:';
24
+ this.client = gitHubClient;
25
+ }
26
+ /**
27
+ * Verify GitHub API authentication
28
+ */
29
+ async verifyGh() {
30
+ return this.client.verifyAuth();
31
+ }
32
+ /**
33
+ * Get the current repo from git remote
34
+ */
35
+ async getCurrentRepo() {
36
+ if (this.cachedRepo)
37
+ return this.cachedRepo;
38
+ const repo = await this.client.getCurrentRepo();
39
+ if (repo) {
40
+ this.cachedRepo = `${repo.owner}/${repo.repo}`;
41
+ return this.cachedRepo;
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Resolve project name - use provided or auto-detect
47
+ */
48
+ async resolveRepo(projectName) {
49
+ if (projectName)
50
+ return projectName;
51
+ const currentRepo = await this.getCurrentRepo();
52
+ if (currentRepo)
53
+ return currentRepo;
54
+ throw new Error('No repository specified and could not auto-detect from current directory. ' +
55
+ 'Either provide projectName or run from within a git repository.');
56
+ }
57
+ /**
58
+ * Parse repo string into owner/repo components
59
+ */
60
+ parseRepo(repoString) {
61
+ return this.client.parseRepo(repoString);
62
+ }
63
+ /**
64
+ * Ensure a task exists before operating on it
65
+ * @throws Error if task is not found
66
+ */
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}`);
71
+ }
72
+ return task;
73
+ }
74
+ /**
75
+ * Ensure a label exists in the repo, create if not
76
+ */
77
+ async ensureLabel(repo, labelName) {
78
+ const cacheKey = `${repo}:${labelName}`;
79
+ if (this.initializedLabels.has(cacheKey))
80
+ return;
81
+ // Define colors for our labels
82
+ const labelColors = {
83
+ [`${this.priorityPrefix}critical`]: 'B60205',
84
+ [`${this.priorityPrefix}high`]: 'D93F0B',
85
+ [`${this.priorityPrefix}medium`]: 'FBCA04',
86
+ [`${this.priorityPrefix}low`]: '0E8A16',
87
+ [`${this.statusPrefix}in_progress`]: '1D76DB',
88
+ [`${this.statusPrefix}blocked`]: 'E99695',
89
+ [`${this.statusPrefix}cancelled`]: '808080',
90
+ 'needs-approval': 'FF6B6B',
91
+ };
92
+ const { owner, repo: repoName } = this.parseRepo(repo);
93
+ try {
94
+ // Check if label exists
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);
110
+ this.initializedLabels.add(cacheKey);
111
+ }
112
+ catch {
113
+ // Label creation failed, might be permissions - continue anyway
114
+ this.initializedLabels.add(cacheKey);
115
+ }
116
+ }
117
+ /**
118
+ * Convert GitHub Issue to ProjectTask
119
+ */
120
+ issueToTask(issue, projectName) {
121
+ const labels = issue.labels.map(l => l.name);
122
+ // Extract priority from labels
123
+ const priorityLabel = labels.find(l => l.startsWith(this.priorityPrefix));
124
+ const priority = (priorityLabel?.replace(this.priorityPrefix, '') || 'medium');
125
+ // Extract status from labels or state
126
+ let status = issue.state === 'open' ? 'pending' : 'completed';
127
+ const statusLabel = labels.find(l => l.startsWith(this.statusPrefix));
128
+ if (statusLabel) {
129
+ const labelStatus = statusLabel.replace(this.statusPrefix, '');
130
+ if (['pending', 'in_progress', 'completed', 'blocked', 'cancelled'].includes(labelStatus)) {
131
+ status = labelStatus;
132
+ }
133
+ }
134
+ else if (issue.state === 'open') {
135
+ if (labels.includes('in-progress') || labels.includes('wip')) {
136
+ status = 'in_progress';
137
+ }
138
+ }
139
+ // Filter out priority and status labels
140
+ const cleanLabels = labels.filter(l => !l.startsWith(this.priorityPrefix) &&
141
+ !l.startsWith(this.statusPrefix) &&
142
+ l !== 'in-progress' &&
143
+ l !== 'wip');
144
+ return {
145
+ id: String(issue.number),
146
+ projectName,
147
+ title: issue.title,
148
+ description: issue.body || undefined,
149
+ status,
150
+ priority,
151
+ assignee: issue.assignees?.[0]?.login,
152
+ labels: cleanLabels.length > 0 ? cleanLabels : undefined,
153
+ dueDate: issue.milestone?.title,
154
+ createdAt: issue.created_at,
155
+ updatedAt: issue.updated_at,
156
+ completedAt: issue.closed_at || undefined,
157
+ metadata: { url: issue.html_url }
158
+ };
159
+ }
160
+ /**
161
+ * Build labels array for issue
162
+ */
163
+ buildLabels(priority, status, labels) {
164
+ const allLabels = [];
165
+ if (priority) {
166
+ allLabels.push(`${this.priorityPrefix}${priority}`);
167
+ }
168
+ if (status && status !== 'pending' && status !== 'completed') {
169
+ allLabels.push(`${this.statusPrefix}${status}`);
170
+ }
171
+ if (labels) {
172
+ allLabels.push(...labels);
173
+ }
174
+ return allLabels;
175
+ }
176
+ /**
177
+ * Create a new task (GitHub Issue) for a project
178
+ *
179
+ * @param params.projectName - Optional repo in "owner/repo" format. Auto-detects if not provided.
180
+ * @param params.needsApproval - Flag for significant changes requiring approval before implementation
181
+ */
182
+ async createTask(params) {
183
+ const repo = await this.resolveRepo(params.projectName);
184
+ const { owner, repo: repoName } = this.parseRepo(repo);
185
+ const allLabels = this.buildLabels(params.priority, 'pending', params.labels);
186
+ // Add needs-approval label if flagged
187
+ if (params.needsApproval) {
188
+ allLabels.push('needs-approval');
189
+ }
190
+ // Ensure labels exist
191
+ for (const label of allLabels) {
192
+ if (label.startsWith(this.priorityPrefix) || label.startsWith(this.statusPrefix) || label === 'needs-approval') {
193
+ await this.ensureLabel(repo, label);
194
+ }
195
+ }
196
+ // Handle @me assignee - need to resolve to actual username
197
+ let assignees;
198
+ if (params.assignee) {
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
+ }
208
+ }
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);
219
+ await auditLogger.log({
220
+ eventType: 'action_executed',
221
+ action: 'task_created',
222
+ outcome: 'success',
223
+ details: {
224
+ taskId: task.id,
225
+ projectName: repo,
226
+ title: params.title,
227
+ priority: task.priority,
228
+ ghIssueUrl: issue.html_url
229
+ }
230
+ });
231
+ return task;
232
+ }
233
+ /**
234
+ * Get all tasks for a project
235
+ */
236
+ async getTasksByProject(projectName, filter) {
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
+ });
283
+ }
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 [];
288
+ }
289
+ }
290
+ /**
291
+ * Get pending tasks for a project
292
+ */
293
+ async getPendingTasks(projectName) {
294
+ return this.getTasksByProject(projectName, { status: 'pending' });
295
+ }
296
+ /**
297
+ * Get in-progress tasks
298
+ */
299
+ async getInProgressTasks(projectName) {
300
+ return this.getTasksByProject(projectName, { status: 'in_progress' });
301
+ }
302
+ /**
303
+ * Get a specific task by ID (issue number)
304
+ */
305
+ async getTask(projectName, taskId) {
306
+ try {
307
+ const repo = await this.resolveRepo(projectName);
308
+ const { owner, repo: repoName } = this.parseRepo(repo);
309
+ const issue = await this.client.getIssue(owner, repoName, parseInt(taskId, 10));
310
+ return this.issueToTask(issue, repo);
311
+ }
312
+ catch {
313
+ return null;
314
+ }
315
+ }
316
+ /**
317
+ * Update a task
318
+ */
319
+ async updateTask(projectName, taskId, updates) {
320
+ try {
321
+ const repo = await this.resolveRepo(projectName);
322
+ const { owner, repo: repoName } = this.parseRepo(repo);
323
+ const issueNumber = parseInt(taskId, 10);
324
+ // Verify task exists before attempting update
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
+ };
332
+ if (updates.title) {
333
+ updateParams.title = updates.title;
334
+ }
335
+ if (updates.description !== undefined) {
336
+ updateParams.body = updates.description || '';
337
+ }
338
+ if (updates.priority || updates.labels) {
339
+ const newLabels = this.buildLabels(updates.priority || existingTask.priority, updates.status || existingTask.status, updates.labels || existingTask.labels);
340
+ for (const label of newLabels) {
341
+ if (label.startsWith(this.priorityPrefix) || label.startsWith(this.statusPrefix)) {
342
+ await this.ensureLabel(repo, label);
343
+ }
344
+ }
345
+ // Get existing labels and merge with new ones
346
+ updateParams.labels = newLabels;
347
+ }
348
+ if (updates.assignee) {
349
+ updateParams.assignees = [updates.assignee];
350
+ }
351
+ await this.client.updateIssue(updateParams);
352
+ // Add comment if provided (with optional commit links)
353
+ if (updates.comment || updates.commits?.length) {
354
+ await this.addComment(repo, taskId, updates.comment, updates.commits);
355
+ }
356
+ if (updates.status) {
357
+ await this.updateTaskStatus(repo, taskId, updates.status);
358
+ }
359
+ else if (updates.closeWithComment) {
360
+ await this.updateTaskStatus(repo, taskId, 'completed');
361
+ }
362
+ const task = await this.getTask(repo, taskId);
363
+ if (task) {
364
+ await auditLogger.log({
365
+ eventType: 'action_executed',
366
+ action: 'task_updated',
367
+ outcome: 'success',
368
+ details: {
369
+ taskId,
370
+ projectName: repo,
371
+ updates: Object.keys(updates),
372
+ hasComment: !!updates.comment,
373
+ commitCount: updates.commits?.length || 0
374
+ }
375
+ });
376
+ }
377
+ return task;
378
+ }
379
+ catch (error) {
380
+ console.error(`[TaskManager] Failed to update task ${taskId}:`, error);
381
+ return null;
382
+ }
383
+ }
384
+ /**
385
+ * Add a comment to a task/issue
386
+ */
387
+ async addComment(projectName, taskId, comment, commits) {
388
+ try {
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);
394
+ // Build comment body
395
+ let body = '';
396
+ if (comment) {
397
+ body += comment;
398
+ }
399
+ // Add commit references
400
+ if (commits && commits.length > 0) {
401
+ if (body)
402
+ body += '\n\n';
403
+ body += '### Related Commits\n';
404
+ for (const commit of commits) {
405
+ // Short SHA for display, full for linking
406
+ const shortSha = commit.substring(0, 7);
407
+ body += `- ${shortSha}\n`;
408
+ }
409
+ }
410
+ if (!body) {
411
+ return false;
412
+ }
413
+ await this.client.addComment(owner, repoName, issueNumber, body);
414
+ await auditLogger.log({
415
+ eventType: 'action_executed',
416
+ action: 'task_comment_added',
417
+ outcome: 'success',
418
+ details: { taskId, projectName: repo, hasCommits: (commits?.length || 0) > 0 }
419
+ });
420
+ return true;
421
+ }
422
+ catch {
423
+ return false;
424
+ }
425
+ }
426
+ /**
427
+ * Link commits to a task by adding a comment
428
+ */
429
+ async linkCommits(projectName, taskId, commits, message) {
430
+ return this.addComment(projectName, taskId, message, commits);
431
+ }
432
+ /**
433
+ * Close a task with a resolution comment
434
+ */
435
+ async closeWithComment(projectName, taskId, resolution, commits) {
436
+ const repo = await this.resolveRepo(projectName);
437
+ // Verify task exists before attempting to close
438
+ await this.ensureTaskExists(repo, taskId);
439
+ // Add the resolution comment
440
+ await this.addComment(repo, taskId, `**Resolution:** ${resolution}`, commits);
441
+ // Close the issue
442
+ return this.updateTaskStatus(repo, taskId, 'completed');
443
+ }
444
+ /**
445
+ * Update task status
446
+ */
447
+ async updateTaskStatus(projectName, taskId, status) {
448
+ try {
449
+ const repo = await this.resolveRepo(projectName);
450
+ const { owner, repo: repoName } = this.parseRepo(repo);
451
+ const issueNumber = parseInt(taskId, 10);
452
+ // Close or reopen based on status
453
+ if (status === 'completed' || status === 'cancelled') {
454
+ const reason = status === 'cancelled' ? 'not_planned' : 'completed';
455
+ await this.client.closeIssue(owner, repoName, issueNumber, reason);
456
+ }
457
+ else {
458
+ const task = await this.getTask(repo, taskId);
459
+ if (task?.status === 'completed' || task?.status === 'cancelled') {
460
+ await this.client.reopenIssue(owner, repoName, issueNumber);
461
+ }
462
+ }
463
+ // Update status label
464
+ if (status !== 'pending' && status !== 'completed') {
465
+ const statusLabel = `${this.statusPrefix}${status}`;
466
+ await this.ensureLabel(repo, statusLabel);
467
+ await this.client.addLabels(owner, repoName, issueNumber, [statusLabel]);
468
+ }
469
+ // Remove old status labels
470
+ const oldStatuses = ['in_progress', 'blocked', 'cancelled'].filter(s => s !== status);
471
+ for (const oldStatus of oldStatuses) {
472
+ try {
473
+ await this.client.removeLabel(owner, repoName, issueNumber, `${this.statusPrefix}${oldStatus}`);
474
+ }
475
+ catch {
476
+ // Ignore - label might not exist
477
+ }
478
+ }
479
+ return this.getTask(repo, taskId);
480
+ }
481
+ catch {
482
+ return null;
483
+ }
484
+ }
485
+ /**
486
+ * Start a task (set to in_progress)
487
+ */
488
+ async startTask(projectName, taskId) {
489
+ return this.updateTaskStatus(projectName, taskId, 'in_progress');
490
+ }
491
+ /**
492
+ * Complete a task
493
+ */
494
+ async completeTask(projectName, taskId) {
495
+ return this.updateTaskStatus(projectName, taskId, 'completed');
496
+ }
497
+ /**
498
+ * Block a task
499
+ */
500
+ async blockTask(projectName, taskId, reason) {
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);
506
+ if (reason) {
507
+ await this.client.addComment(owner, repoName, issueNumber, `Blocked: ${reason}`);
508
+ }
509
+ return this.updateTaskStatus(repo, taskId, 'blocked');
510
+ }
511
+ /**
512
+ * Delete a task (close as "not planned")
513
+ */
514
+ async deleteTask(projectName, taskId) {
515
+ try {
516
+ const repo = await this.resolveRepo(projectName);
517
+ const { owner, repo: repoName } = this.parseRepo(repo);
518
+ const issueNumber = parseInt(taskId, 10);
519
+ await this.client.closeIssue(owner, repoName, issueNumber, 'not_planned');
520
+ await auditLogger.log({
521
+ eventType: 'action_executed',
522
+ action: 'task_deleted',
523
+ outcome: 'success',
524
+ details: { taskId, projectName: repo }
525
+ });
526
+ return true;
527
+ }
528
+ catch {
529
+ return false;
530
+ }
531
+ }
532
+ /**
533
+ * List projects with issues
534
+ */
535
+ async listProjects() {
536
+ try {
537
+ const repos = await this.client.listRepos(20);
538
+ const projects = [];
539
+ const results = await Promise.allSettled(repos.slice(0, 10).map(async (repo) => {
540
+ try {
541
+ const { owner, repo: repoName } = this.parseRepo(repo.nameWithOwner);
542
+ const [openIssues, closedIssues] = await Promise.all([
543
+ this.client.listIssues({ owner, repo: repoName, state: 'open', per_page: 100 }),
544
+ this.client.listIssues({ owner, repo: repoName, state: 'closed', per_page: 100 })
545
+ ]);
546
+ const inProgressCount = openIssues.filter(i => i.labels?.some(l => l.name === `${this.statusPrefix}in_progress` ||
547
+ l.name === 'in-progress' ||
548
+ l.name === 'wip')).length;
549
+ return {
550
+ name: repo.nameWithOwner,
551
+ taskCount: openIssues.length + closedIssues.length,
552
+ pendingCount: openIssues.length - inProgressCount,
553
+ inProgressCount,
554
+ completedCount: closedIssues.length
555
+ };
556
+ }
557
+ catch {
558
+ return null;
559
+ }
560
+ }));
561
+ for (const result of results) {
562
+ if (result.status === 'fulfilled' && result.value && result.value.taskCount > 0) {
563
+ projects.push(result.value);
564
+ }
565
+ }
566
+ return projects.sort((a, b) => b.taskCount - a.taskCount);
567
+ }
568
+ catch {
569
+ return [];
570
+ }
571
+ }
572
+ /**
573
+ * Get task statistics for a project
574
+ */
575
+ async getProjectStats(projectName) {
576
+ try {
577
+ const repo = await this.resolveRepo(projectName);
578
+ const { owner, repo: repoName } = this.parseRepo(repo);
579
+ // Fetch issues for stats
580
+ const [openIssues, closedIssues] = await Promise.all([
581
+ this.client.listIssues({ owner, repo: repoName, state: 'open', per_page: 100 }),
582
+ this.client.listIssues({ owner, repo: repoName, state: 'closed', per_page: 100 })
583
+ ]);
584
+ const byStatus = {};
585
+ const byPriority = {};
586
+ let completedThisWeek = 0;
587
+ const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
588
+ // Process open issues
589
+ for (const issue of openIssues) {
590
+ const labels = issue.labels?.map(l => l.name) || [];
591
+ // Extract status from labels
592
+ const status = labels.find(l => l.startsWith(this.statusPrefix))?.replace(this.statusPrefix, '') || 'pending';
593
+ byStatus[status] = (byStatus[status] || 0) + 1;
594
+ // Extract priority from labels
595
+ const priority = labels.find(l => l.startsWith(this.priorityPrefix))?.replace(this.priorityPrefix, '') || 'medium';
596
+ byPriority[priority] = (byPriority[priority] || 0) + 1;
597
+ }
598
+ // Process closed issues
599
+ for (const issue of closedIssues) {
600
+ const labels = issue.labels?.map(l => l.name) || [];
601
+ // Extract status from labels
602
+ const status = labels.find(l => l.startsWith(this.statusPrefix))?.replace(this.statusPrefix, '') || 'completed';
603
+ byStatus[status] = (byStatus[status] || 0) + 1;
604
+ // Extract priority from labels
605
+ const priority = labels.find(l => l.startsWith(this.priorityPrefix))?.replace(this.priorityPrefix, '') || 'medium';
606
+ byPriority[priority] = (byPriority[priority] || 0) + 1;
607
+ // Count completed this week
608
+ if (issue.closed_at && new Date(issue.closed_at) >= oneWeekAgo) {
609
+ completedThisWeek++;
610
+ }
611
+ }
612
+ const total = openIssues.length + closedIssues.length;
613
+ return {
614
+ total,
615
+ byStatus,
616
+ byPriority,
617
+ overdue: 0,
618
+ completedThisWeek
619
+ };
620
+ }
621
+ catch (error) {
622
+ console.error('[TaskManager] Failed to get project stats:', error);
623
+ return null;
624
+ }
625
+ }
626
+ /**
627
+ * Search tasks
628
+ */
629
+ async searchTasks(query, projectName) {
630
+ try {
631
+ let searchQuery = query;
632
+ if (projectName) {
633
+ searchQuery = `repo:${projectName} ${query}`;
634
+ }
635
+ const issues = await this.client.searchIssues({ query: searchQuery, per_page: 50 });
636
+ return issues.map(issue => {
637
+ const repoMatch = issue.html_url.match(/github\.com\/([^/]+\/[^/]+)\//);
638
+ const repo = repoMatch ? repoMatch[1] : projectName || 'unknown';
639
+ return this.issueToTask(issue, repo);
640
+ });
641
+ }
642
+ catch {
643
+ return [];
644
+ }
645
+ }
646
+ /**
647
+ * Get count of completed tasks (GitHub doesn't support bulk delete)
648
+ */
649
+ async clearCompletedTasks(projectName) {
650
+ const repo = await this.resolveRepo(projectName);
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
+ });
658
+ return closedIssues.length;
659
+ }
660
+ }
661
+ // Export singleton instance
662
+ export const taskManager = new TaskManager();
663
+ //# sourceMappingURL=TaskManager.js.map