@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.
Files changed (150) hide show
  1. package/README.md +29 -1
  2. package/dist/analyzers/CSSAnalyzer.d.ts +188 -8
  3. package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -1
  4. package/dist/analyzers/CSSAnalyzer.js +794 -192
  5. package/dist/analyzers/CSSAnalyzer.js.map +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/config/dashboard.d.ts +55 -0
  8. package/dist/config/dashboard.d.ts.map +1 -0
  9. package/dist/config/dashboard.js +103 -0
  10. package/dist/config/dashboard.js.map +1 -0
  11. package/dist/config/index.d.ts +7 -0
  12. package/dist/config/index.d.ts.map +1 -0
  13. package/dist/config/index.js +7 -0
  14. package/dist/config/index.js.map +1 -0
  15. package/dist/dashboard/httpDashboard.d.ts +100 -0
  16. package/dist/dashboard/httpDashboard.d.ts.map +1 -0
  17. package/dist/dashboard/httpDashboard.js +1276 -0
  18. package/dist/dashboard/httpDashboard.js.map +1 -0
  19. package/dist/dashboard/index.d.ts +6 -0
  20. package/dist/dashboard/index.d.ts.map +1 -0
  21. package/dist/dashboard/index.js +7 -0
  22. package/dist/dashboard/index.js.map +1 -0
  23. package/dist/engine/AuditLogger.d.ts +370 -2
  24. package/dist/engine/AuditLogger.d.ts.map +1 -1
  25. package/dist/engine/AuditLogger.js +1067 -24
  26. package/dist/engine/AuditLogger.js.map +1 -1
  27. package/dist/engine/GitHubApprovalManager.d.ts +13 -0
  28. package/dist/engine/GitHubApprovalManager.d.ts.map +1 -1
  29. package/dist/engine/GitHubApprovalManager.js +72 -46
  30. package/dist/engine/GitHubApprovalManager.js.map +1 -1
  31. package/dist/engine/GitHubClient.d.ts +183 -0
  32. package/dist/engine/GitHubClient.d.ts.map +1 -0
  33. package/dist/engine/GitHubClient.js +411 -0
  34. package/dist/engine/GitHubClient.js.map +1 -0
  35. package/dist/engine/RateLimiter.d.ts +5 -3
  36. package/dist/engine/RateLimiter.d.ts.map +1 -1
  37. package/dist/engine/RateLimiter.js +53 -70
  38. package/dist/engine/RateLimiter.js.map +1 -1
  39. package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
  40. package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
  41. package/dist/engine/RuleDependencyAnalyzer.js +475 -0
  42. package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
  43. package/dist/engine/RulesEngine.d.ts +102 -3
  44. package/dist/engine/RulesEngine.d.ts.map +1 -1
  45. package/dist/engine/RulesEngine.js +326 -21
  46. package/dist/engine/RulesEngine.js.map +1 -1
  47. package/dist/engine/TaskManager.d.ts +11 -10
  48. package/dist/engine/TaskManager.d.ts.map +1 -1
  49. package/dist/engine/TaskManager.js +180 -195
  50. package/dist/engine/TaskManager.js.map +1 -1
  51. package/dist/engine/index.d.ts +3 -0
  52. package/dist/engine/index.d.ts.map +1 -1
  53. package/dist/engine/index.js +5 -0
  54. package/dist/engine/index.js.map +1 -1
  55. package/dist/rules/azure.d.ts.map +1 -1
  56. package/dist/rules/azure.js +12 -14
  57. package/dist/rules/azure.js.map +1 -1
  58. package/dist/rules/compliance.d.ts.map +1 -1
  59. package/dist/rules/compliance.js +23 -41
  60. package/dist/rules/compliance.js.map +1 -1
  61. package/dist/rules/condition-optimizer.d.ts +151 -0
  62. package/dist/rules/condition-optimizer.d.ts.map +1 -0
  63. package/dist/rules/condition-optimizer.js +479 -0
  64. package/dist/rules/condition-optimizer.js.map +1 -0
  65. package/dist/rules/css.d.ts.map +1 -1
  66. package/dist/rules/css.js +538 -0
  67. package/dist/rules/css.js.map +1 -1
  68. package/dist/rules/field-standards.d.ts +1172 -0
  69. package/dist/rules/field-standards.d.ts.map +1 -0
  70. package/dist/rules/field-standards.js +908 -0
  71. package/dist/rules/field-standards.js.map +1 -0
  72. package/dist/rules/flask.d.ts.map +1 -1
  73. package/dist/rules/flask.js +18 -31
  74. package/dist/rules/flask.js.map +1 -1
  75. package/dist/rules/index.d.ts +220 -0
  76. package/dist/rules/index.d.ts.map +1 -1
  77. package/dist/rules/index.js +155 -0
  78. package/dist/rules/index.js.map +1 -1
  79. package/dist/rules/ml-ai.d.ts.map +1 -1
  80. package/dist/rules/ml-ai.js +11 -13
  81. package/dist/rules/ml-ai.js.map +1 -1
  82. package/dist/rules/patterns.d.ts +568 -0
  83. package/dist/rules/patterns.d.ts.map +1 -0
  84. package/dist/rules/patterns.js +1359 -0
  85. package/dist/rules/patterns.js.map +1 -0
  86. package/dist/rules/security.d.ts.map +1 -1
  87. package/dist/rules/security.js +580 -19
  88. package/dist/rules/security.js.map +1 -1
  89. package/dist/rules/shared-patterns.d.ts +268 -0
  90. package/dist/rules/shared-patterns.d.ts.map +1 -0
  91. package/dist/rules/shared-patterns.js +556 -0
  92. package/dist/rules/shared-patterns.js.map +1 -0
  93. package/dist/rules/storage.d.ts +8 -2
  94. package/dist/rules/storage.d.ts.map +1 -1
  95. package/dist/rules/storage.js +541 -3
  96. package/dist/rules/storage.js.map +1 -1
  97. package/dist/rules/stripe.d.ts.map +1 -1
  98. package/dist/rules/stripe.js +19 -26
  99. package/dist/rules/stripe.js.map +1 -1
  100. package/dist/rules/websocket.d.ts.map +1 -1
  101. package/dist/rules/websocket.js +32 -40
  102. package/dist/rules/websocket.js.map +1 -1
  103. package/dist/server.d.ts.map +1 -1
  104. package/dist/server.js +96 -17
  105. package/dist/server.js.map +1 -1
  106. package/dist/supervisor/AgentSupervisor.d.ts +52 -0
  107. package/dist/supervisor/AgentSupervisor.d.ts.map +1 -1
  108. package/dist/supervisor/AgentSupervisor.js +120 -1
  109. package/dist/supervisor/AgentSupervisor.js.map +1 -1
  110. package/dist/supervisor/ManagedServerRegistry.d.ts +139 -2
  111. package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -1
  112. package/dist/supervisor/ManagedServerRegistry.js +590 -6
  113. package/dist/supervisor/ManagedServerRegistry.js.map +1 -1
  114. package/dist/supervisor/ProjectTracker.d.ts +24 -2
  115. package/dist/supervisor/ProjectTracker.d.ts.map +1 -1
  116. package/dist/supervisor/ProjectTracker.js +151 -59
  117. package/dist/supervisor/ProjectTracker.js.map +1 -1
  118. package/dist/testing/index.d.ts +11 -0
  119. package/dist/testing/index.d.ts.map +1 -0
  120. package/dist/testing/index.js +12 -0
  121. package/dist/testing/index.js.map +1 -0
  122. package/dist/testing/rule-tester.d.ts +217 -0
  123. package/dist/testing/rule-tester.d.ts.map +1 -0
  124. package/dist/testing/rule-tester.examples.d.ts +57 -0
  125. package/dist/testing/rule-tester.examples.d.ts.map +1 -0
  126. package/dist/testing/rule-tester.examples.js +375 -0
  127. package/dist/testing/rule-tester.examples.js.map +1 -0
  128. package/dist/testing/rule-tester.js +381 -0
  129. package/dist/testing/rule-tester.js.map +1 -0
  130. package/dist/testing/rule-validator.d.ts +141 -0
  131. package/dist/testing/rule-validator.d.ts.map +1 -0
  132. package/dist/testing/rule-validator.js +640 -0
  133. package/dist/testing/rule-validator.js.map +1 -0
  134. package/dist/types/index.d.ts +265 -4
  135. package/dist/types/index.d.ts.map +1 -1
  136. package/dist/types/index.js +57 -2
  137. package/dist/types/index.js.map +1 -1
  138. package/dist/utils/index.d.ts +2 -0
  139. package/dist/utils/index.d.ts.map +1 -1
  140. package/dist/utils/index.js +2 -0
  141. package/dist/utils/index.js.map +1 -1
  142. package/dist/utils/rate-limiting.d.ts +268 -0
  143. package/dist/utils/rate-limiting.d.ts.map +1 -0
  144. package/dist/utils/rate-limiting.js +403 -0
  145. package/dist/utils/rate-limiting.js.map +1 -0
  146. package/dist/utils/shared.d.ts +306 -0
  147. package/dist/utils/shared.d.ts.map +1 -0
  148. package/dist/utils/shared.js +464 -0
  149. package/dist/utils/shared.js.map +1 -0
  150. 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 `gh` CLI.
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 { escapeForShell } from '../utils/shell.js';
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
- ghVerified = false;
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 gh CLI is installed and authenticated
27
+ * Verify GitHub API authentication
30
28
  */
31
29
  async verifyGh() {
32
- if (this.ghVerified)
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 or gh CLI
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
- try {
60
- // Try to get repo from current directory
61
- const { stdout } = await execAsync('gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null');
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
- catch {
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
- * Execute a gh command and return parsed JSON output
58
+ * Parse repo string into owner/repo components
83
59
  */
84
- async execGh(command) {
85
- try {
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
- * Execute a gh command without JSON output
64
+ * Ensure a task exists before operating on it
65
+ * @throws Error if task is not found
111
66
  */
112
- async execGhRaw(command) {
113
- try {
114
- const { stdout } = await execAsync(`gh ${command}`);
115
- return stdout.trim();
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.execGh(`label view "${labelName}" --repo "${repo}" --json name`);
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 doesn't exist, create it
147
- try {
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 === 'OPEN' ? 'pending' : 'completed';
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 === 'OPEN') {
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.createdAt,
203
- updatedAt: issue.updatedAt,
204
- completedAt: issue.closedAt || undefined,
205
- metadata: { url: issue.url }
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 gh command
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
- // Build the gh issue create command
244
- let cmd = `issue create --repo "${repo}" --title ${escapeForShell(params.title)}`;
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
- // Handle @me specially
253
- const assignee = params.assignee === '@me' ? '@me' : params.assignee;
254
- cmd += ` --assignee ${escapeForShell(assignee)}`;
255
- }
256
- // Create the issue - gh issue create returns the URL, not JSON
257
- const issueUrl = await this.execGhRaw(cmd);
258
- // Extract issue number from URL (e.g., https://github.com/owner/repo/issues/123)
259
- const issueNumberMatch = issueUrl.match(/\/issues\/(\d+)/);
260
- if (!issueNumberMatch) {
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
- const issueNumber = issueNumberMatch[1];
264
- // Fetch the full issue details
265
- const result = await this.execGh(`issue view ${issueNumber} --repo "${repo}" --json number,title,body,state,labels,assignees,createdAt,updatedAt,closedAt,url`);
266
- const task = this.issueToTask(result, repo);
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: result.url
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
- const repo = await this.resolveRepo(projectName);
286
- let stateFilter = 'all';
287
- if (filter?.status === 'completed' || filter?.status === 'cancelled') {
288
- stateFilter = 'closed';
289
- }
290
- else if (filter?.status) {
291
- // Status is pending, in_progress, or blocked - use open issues
292
- stateFilter = 'open';
293
- }
294
- let cmd = `issue list --repo "${repo}" --state ${stateFilter} --json number,title,body,state,labels,assignees,createdAt,updatedAt,closedAt,url --limit 100`;
295
- const labelFilters = [];
296
- if (filter?.priority) {
297
- labelFilters.push(`${this.priorityPrefix}${filter.priority}`);
298
- }
299
- if (filter?.status && !['pending', 'completed'].includes(filter.status)) {
300
- labelFilters.push(`${this.statusPrefix}${filter.status}`);
301
- }
302
- if (filter?.labels) {
303
- labelFilters.push(...filter.labels);
304
- }
305
- if (labelFilters.length > 0) {
306
- cmd += ` --label ${escapeForShell(labelFilters.join(','))}`;
307
- }
308
- if (filter?.assignee) {
309
- cmd += ` --assignee ${escapeForShell(filter.assignee)}`;
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
- const issues = await this.execGh(cmd);
312
- let tasks = issues.map(issue => this.issueToTask(issue, repo));
313
- if (filter?.status) {
314
- tasks = tasks.filter(t => t.status === filter.status);
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 issue = await this.execGh(`issue view ${taskId} --repo "${repo}" --json number,title,body,state,labels,assignees,createdAt,updatedAt,closedAt,url`);
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.getTask(repo, taskId);
362
- if (!existingTask) {
363
- console.error(`[TaskManager] Cannot update task ${taskId}: task not found in ${repo}`);
364
- return null;
365
- }
366
- let cmd = `issue edit ${taskId} --repo "${repo}"`;
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
- cmd += ` --title ${escapeForShell(updates.title)}`;
333
+ updateParams.title = updates.title;
369
334
  }
370
335
  if (updates.description !== undefined) {
371
- cmd += ` --body ${escapeForShell(updates.description || '')}`;
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
- if (newLabels.length > 0) {
381
- cmd += ` --add-label ${escapeForShell(newLabels.join(','))}`;
382
- }
345
+ // Get existing labels and merge with new ones
346
+ updateParams.labels = newLabels;
383
347
  }
384
348
  if (updates.assignee) {
385
- cmd += ` --add-assignee ${escapeForShell(updates.assignee)}`;
349
+ updateParams.assignees = [updates.assignee];
386
350
  }
387
- await this.execGhRaw(cmd);
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.execGhRaw(`issue comment ${taskId} --repo "${repo}" --body ${escapeForShell(body)}`);
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' ? ' --reason "not planned"' : '';
483
- await this.execGhRaw(`issue close ${taskId} --repo "${repo}"${reason}`);
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.execGhRaw(`issue reopen ${taskId} --repo "${repo}"`);
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.execGhRaw(`issue edit ${escapeForShell(taskId)} --repo ${escapeForShell(repo)} --add-label ${escapeForShell(statusLabel)}`);
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.execGhRaw(`issue edit ${escapeForShell(taskId)} --repo ${escapeForShell(repo)} --remove-label ${escapeForShell(this.statusPrefix + oldStatus)}`);
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.execGhRaw(`issue comment ${taskId} --repo "${repo}" --body ${escapeForShell(`Blocked: ${reason}`)}`);
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
- await this.execGhRaw(`issue close ${taskId} --repo "${repo}" --reason "not planned"`);
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.execGh('repo list --limit 20 --json nameWithOwner');
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.execGh(`issue list --repo "${repo.nameWithOwner}" --state open --json number,labels --limit 100`),
565
- this.execGh(`issue list --repo "${repo.nameWithOwner}" --state closed --json number --limit 100`)
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
- // Fetch only minimal fields needed for stats (labels, closedAt)
600
- // Increased limit to 500 to capture more tasks for accurate stats
578
+ const { owner, repo: repoName } = this.parseRepo(repo);
579
+ // Fetch issues for stats
601
580
  const [openIssues, closedIssues] = await Promise.all([
602
- this.execGh(`issue list --repo "${repo}" --state open --json labels --limit 500`),
603
- this.execGh(`issue list --repo "${repo}" --state closed --json labels,closedAt --limit 500`)
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.closedAt && new Date(issue.closedAt) >= oneWeekAgo) {
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 cmd = `issue list --search ${escapeForShell(query)} --state all --json number,title,body,state,labels,assignees,createdAt,updatedAt,closedAt,url --limit 50`;
631
+ let searchQuery = query;
653
632
  if (projectName) {
654
- cmd += ` --repo ${escapeForShell(projectName)}`;
633
+ searchQuery = `repo:${projectName} ${query}`;
655
634
  }
656
- const issues = await this.execGh(cmd);
635
+ const issues = await this.client.searchIssues({ query: searchQuery, per_page: 50 });
657
636
  return issues.map(issue => {
658
- const repoMatch = issue.url.match(/github\.com\/([^/]+\/[^/]+)\//);
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 closedIssues = await this.execGh(`issue list --repo "${repo}" --state closed --json number --limit 100`);
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
  }