@trentapps/manager-protocol 1.1.3 → 1.2.1

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 (142) hide show
  1. package/README.md +164 -17
  2. package/dist/analyzers/CSSAnalyzer.d.ts +180 -8
  3. package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -1
  4. package/dist/analyzers/CSSAnalyzer.js +561 -105
  5. package/dist/analyzers/CSSAnalyzer.js.map +1 -1
  6. package/dist/config/dashboard.d.ts +55 -0
  7. package/dist/config/dashboard.d.ts.map +1 -0
  8. package/dist/config/dashboard.js +103 -0
  9. package/dist/config/dashboard.js.map +1 -0
  10. package/dist/config/index.d.ts +7 -0
  11. package/dist/config/index.d.ts.map +1 -0
  12. package/dist/config/index.js +7 -0
  13. package/dist/config/index.js.map +1 -0
  14. package/dist/dashboard/httpDashboard.d.ts +100 -0
  15. package/dist/dashboard/httpDashboard.d.ts.map +1 -0
  16. package/dist/dashboard/httpDashboard.js +1276 -0
  17. package/dist/dashboard/httpDashboard.js.map +1 -0
  18. package/dist/dashboard/index.d.ts +6 -0
  19. package/dist/dashboard/index.d.ts.map +1 -0
  20. package/dist/dashboard/index.js +7 -0
  21. package/dist/dashboard/index.js.map +1 -0
  22. package/dist/engine/AuditLogger.d.ts +370 -2
  23. package/dist/engine/AuditLogger.d.ts.map +1 -1
  24. package/dist/engine/AuditLogger.js +1064 -24
  25. package/dist/engine/AuditLogger.js.map +1 -1
  26. package/dist/engine/GitHubClient.d.ts +183 -0
  27. package/dist/engine/GitHubClient.d.ts.map +1 -0
  28. package/dist/engine/GitHubClient.js +411 -0
  29. package/dist/engine/GitHubClient.js.map +1 -0
  30. package/dist/engine/RateLimiter.d.ts +5 -3
  31. package/dist/engine/RateLimiter.d.ts.map +1 -1
  32. package/dist/engine/RateLimiter.js +49 -72
  33. package/dist/engine/RateLimiter.js.map +1 -1
  34. package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
  35. package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
  36. package/dist/engine/RuleDependencyAnalyzer.js +475 -0
  37. package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
  38. package/dist/engine/RulesEngine.d.ts +102 -3
  39. package/dist/engine/RulesEngine.d.ts.map +1 -1
  40. package/dist/engine/RulesEngine.js +326 -21
  41. package/dist/engine/RulesEngine.js.map +1 -1
  42. package/dist/engine/TaskManager.d.ts +10 -14
  43. package/dist/engine/TaskManager.d.ts.map +1 -1
  44. package/dist/engine/TaskManager.js +169 -197
  45. package/dist/engine/TaskManager.js.map +1 -1
  46. package/dist/engine/index.d.ts +3 -0
  47. package/dist/engine/index.d.ts.map +1 -1
  48. package/dist/engine/index.js +5 -0
  49. package/dist/engine/index.js.map +1 -1
  50. package/dist/rules/azure.d.ts.map +1 -1
  51. package/dist/rules/azure.js +12 -14
  52. package/dist/rules/azure.js.map +1 -1
  53. package/dist/rules/compliance.d.ts.map +1 -1
  54. package/dist/rules/compliance.js +23 -41
  55. package/dist/rules/compliance.js.map +1 -1
  56. package/dist/rules/condition-optimizer.d.ts +151 -0
  57. package/dist/rules/condition-optimizer.d.ts.map +1 -0
  58. package/dist/rules/condition-optimizer.js +479 -0
  59. package/dist/rules/condition-optimizer.js.map +1 -0
  60. package/dist/rules/css.d.ts.map +1 -1
  61. package/dist/rules/css.js +538 -0
  62. package/dist/rules/css.js.map +1 -1
  63. package/dist/rules/field-standards.d.ts +1172 -0
  64. package/dist/rules/field-standards.d.ts.map +1 -0
  65. package/dist/rules/field-standards.js +908 -0
  66. package/dist/rules/field-standards.js.map +1 -0
  67. package/dist/rules/flask.d.ts.map +1 -1
  68. package/dist/rules/flask.js +18 -31
  69. package/dist/rules/flask.js.map +1 -1
  70. package/dist/rules/index.d.ts +220 -0
  71. package/dist/rules/index.d.ts.map +1 -1
  72. package/dist/rules/index.js +155 -0
  73. package/dist/rules/index.js.map +1 -1
  74. package/dist/rules/ml-ai.d.ts.map +1 -1
  75. package/dist/rules/ml-ai.js +11 -13
  76. package/dist/rules/ml-ai.js.map +1 -1
  77. package/dist/rules/patterns.d.ts +568 -0
  78. package/dist/rules/patterns.d.ts.map +1 -0
  79. package/dist/rules/patterns.js +1359 -0
  80. package/dist/rules/patterns.js.map +1 -0
  81. package/dist/rules/security.d.ts.map +1 -1
  82. package/dist/rules/security.js +580 -19
  83. package/dist/rules/security.js.map +1 -1
  84. package/dist/rules/shared-patterns.d.ts +268 -0
  85. package/dist/rules/shared-patterns.d.ts.map +1 -0
  86. package/dist/rules/shared-patterns.js +556 -0
  87. package/dist/rules/shared-patterns.js.map +1 -0
  88. package/dist/rules/storage.d.ts +8 -2
  89. package/dist/rules/storage.d.ts.map +1 -1
  90. package/dist/rules/storage.js +541 -3
  91. package/dist/rules/storage.js.map +1 -1
  92. package/dist/rules/stripe.d.ts.map +1 -1
  93. package/dist/rules/stripe.js +19 -26
  94. package/dist/rules/stripe.js.map +1 -1
  95. package/dist/rules/websocket.d.ts.map +1 -1
  96. package/dist/rules/websocket.js +32 -40
  97. package/dist/rules/websocket.js.map +1 -1
  98. package/dist/supervisor/AgentSupervisor.d.ts +52 -0
  99. package/dist/supervisor/AgentSupervisor.d.ts.map +1 -1
  100. package/dist/supervisor/AgentSupervisor.js +120 -1
  101. package/dist/supervisor/AgentSupervisor.js.map +1 -1
  102. package/dist/supervisor/ManagedServerRegistry.d.ts +139 -2
  103. package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -1
  104. package/dist/supervisor/ManagedServerRegistry.js +590 -6
  105. package/dist/supervisor/ManagedServerRegistry.js.map +1 -1
  106. package/dist/supervisor/ProjectTracker.d.ts +2 -1
  107. package/dist/supervisor/ProjectTracker.d.ts.map +1 -1
  108. package/dist/supervisor/ProjectTracker.js +5 -9
  109. package/dist/supervisor/ProjectTracker.js.map +1 -1
  110. package/dist/testing/index.d.ts +11 -0
  111. package/dist/testing/index.d.ts.map +1 -0
  112. package/dist/testing/index.js +12 -0
  113. package/dist/testing/index.js.map +1 -0
  114. package/dist/testing/rule-tester.d.ts +217 -0
  115. package/dist/testing/rule-tester.d.ts.map +1 -0
  116. package/dist/testing/rule-tester.examples.d.ts +57 -0
  117. package/dist/testing/rule-tester.examples.d.ts.map +1 -0
  118. package/dist/testing/rule-tester.examples.js +375 -0
  119. package/dist/testing/rule-tester.examples.js.map +1 -0
  120. package/dist/testing/rule-tester.js +381 -0
  121. package/dist/testing/rule-tester.js.map +1 -0
  122. package/dist/testing/rule-validator.d.ts +141 -0
  123. package/dist/testing/rule-validator.d.ts.map +1 -0
  124. package/dist/testing/rule-validator.js +640 -0
  125. package/dist/testing/rule-validator.js.map +1 -0
  126. package/dist/types/index.d.ts +265 -4
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/types/index.js +57 -2
  129. package/dist/types/index.js.map +1 -1
  130. package/dist/utils/index.d.ts +2 -0
  131. package/dist/utils/index.d.ts.map +1 -1
  132. package/dist/utils/index.js +2 -0
  133. package/dist/utils/index.js.map +1 -1
  134. package/dist/utils/rate-limiting.d.ts +268 -0
  135. package/dist/utils/rate-limiting.d.ts.map +1 -0
  136. package/dist/utils/rate-limiting.js +403 -0
  137. package/dist/utils/rate-limiting.js.map +1 -0
  138. package/dist/utils/shared.d.ts +306 -0
  139. package/dist/utils/shared.d.ts.map +1 -0
  140. package/dist/utils/shared.js +464 -0
  141. package/dist/utils/shared.js.map +1 -0
  142. package/package.json +3 -2
@@ -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
@@ -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.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);
154
110
  this.initializedLabels.add(cacheKey);
155
111
  }
156
112
  catch {
157
- // Label doesn't exist, create it
158
- try {
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 === 'OPEN' ? 'pending' : 'completed';
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 === 'OPEN') {
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.createdAt,
214
- updatedAt: issue.updatedAt,
215
- completedAt: issue.closedAt || undefined,
216
- 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 }
217
158
  };
218
159
  }
219
160
  /**
220
- * Build labels array for gh command
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
- // Build the gh issue create command
255
- let cmd = `issue create --repo "${repo}" --title ${escapeForShell(params.title)}`;
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
- // Handle @me specially
264
- const assignee = params.assignee === '@me' ? '@me' : params.assignee;
265
- cmd += ` --assignee ${escapeForShell(assignee)}`;
266
- }
267
- // Create the issue - gh issue create returns the URL, not JSON
268
- const issueUrl = await this.execGhRaw(cmd);
269
- // Extract issue number from URL (e.g., https://github.com/owner/repo/issues/123)
270
- const issueNumberMatch = issueUrl.match(/\/issues\/(\d+)/);
271
- if (!issueNumberMatch) {
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
- const issueNumber = issueNumberMatch[1];
275
- // Fetch the full issue details
276
- const result = await this.execGh(`issue view ${issueNumber} --repo "${repo}" --json number,title,body,state,labels,assignees,createdAt,updatedAt,closedAt,url`);
277
- 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);
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: result.url
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
- const repo = await this.resolveRepo(projectName);
297
- let stateFilter = 'all';
298
- if (filter?.status === 'completed' || filter?.status === 'cancelled') {
299
- stateFilter = 'closed';
300
- }
301
- else if (filter?.status) {
302
- // Status is pending, in_progress, or blocked - use open issues
303
- stateFilter = 'open';
304
- }
305
- let cmd = `issue list --repo "${repo}" --state ${stateFilter} --json number,title,body,state,labels,assignees,createdAt,updatedAt,closedAt,url --limit 100`;
306
- const labelFilters = [];
307
- if (filter?.priority) {
308
- labelFilters.push(`${this.priorityPrefix}${filter.priority}`);
309
- }
310
- if (filter?.status && !['pending', 'completed'].includes(filter.status)) {
311
- labelFilters.push(`${this.statusPrefix}${filter.status}`);
312
- }
313
- if (filter?.labels) {
314
- labelFilters.push(...filter.labels);
315
- }
316
- if (labelFilters.length > 0) {
317
- cmd += ` --label ${escapeForShell(labelFilters.join(','))}`;
318
- }
319
- if (filter?.assignee) {
320
- 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
+ });
321
283
  }
322
- const issues = await this.execGh(cmd);
323
- let tasks = issues.map(issue => this.issueToTask(issue, repo));
324
- if (filter?.status) {
325
- 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 [];
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 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));
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
- let cmd = `issue edit ${taskId} --repo "${repo}"`;
326
+ // Build update params
327
+ const updateParams = {
328
+ owner,
329
+ repo: repoName,
330
+ issue_number: issueNumber,
331
+ };
374
332
  if (updates.title) {
375
- cmd += ` --title ${escapeForShell(updates.title)}`;
333
+ updateParams.title = updates.title;
376
334
  }
377
335
  if (updates.description !== undefined) {
378
- cmd += ` --body ${escapeForShell(updates.description || '')}`;
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
- if (newLabels.length > 0) {
388
- cmd += ` --add-label ${escapeForShell(newLabels.join(','))}`;
389
- }
345
+ // Get existing labels and merge with new ones
346
+ updateParams.labels = newLabels;
390
347
  }
391
348
  if (updates.assignee) {
392
- cmd += ` --add-assignee ${escapeForShell(updates.assignee)}`;
349
+ updateParams.assignees = [updates.assignee];
393
350
  }
394
- await this.execGhRaw(cmd);
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.execGhRaw(`issue comment ${taskId} --repo "${repo}" --body ${escapeForShell(body)}`);
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' ? ' --reason "not planned"' : '';
494
- 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);
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.execGhRaw(`issue reopen ${taskId} --repo "${repo}"`);
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.execGhRaw(`issue edit ${escapeForShell(taskId)} --repo ${escapeForShell(repo)} --add-label ${escapeForShell(statusLabel)}`);
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.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}`);
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.execGhRaw(`issue comment ${taskId} --repo "${repo}" --body ${escapeForShell(`Blocked: ${reason}`)}`);
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
- 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');
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.execGh('repo list --limit 20 --json nameWithOwner');
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.execGh(`issue list --repo "${repo.nameWithOwner}" --state open --json number,labels --limit 100`),
578
- 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 })
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
- // Fetch only minimal fields needed for stats (labels, closedAt)
613
- // Increased limit to 500 to capture more tasks for accurate stats
578
+ const { owner, repo: repoName } = this.parseRepo(repo);
579
+ // Fetch issues for stats
614
580
  const [openIssues, closedIssues] = await Promise.all([
615
- this.execGh(`issue list --repo "${repo}" --state open --json labels --limit 500`),
616
- 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 })
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.closedAt && new Date(issue.closedAt) >= oneWeekAgo) {
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 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;
666
632
  if (projectName) {
667
- cmd += ` --repo ${escapeForShell(projectName)}`;
633
+ searchQuery = `repo:${projectName} ${query}`;
668
634
  }
669
- const issues = await this.execGh(cmd);
635
+ const issues = await this.client.searchIssues({ query: searchQuery, per_page: 50 });
670
636
  return issues.map(issue => {
671
- const repoMatch = issue.url.match(/github\.com\/([^/]+\/[^/]+)\//);
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 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
+ });
686
658
  return closedIssues.length;
687
659
  }
688
660
  }