claude-autopm 2.7.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +307 -56
- package/autopm/.claude/.env +158 -0
- package/autopm/.claude/settings.local.json +9 -0
- package/bin/autopm.js +11 -2
- package/bin/commands/epic.js +23 -3
- package/bin/commands/plugin.js +395 -0
- package/bin/commands/team.js +184 -10
- package/install/install.js +223 -4
- package/lib/cli/commands/issue.js +360 -20
- package/lib/plugins/PluginManager.js +1328 -0
- package/lib/plugins/PluginManager.old.js +400 -0
- package/lib/providers/AzureDevOpsProvider.js +575 -0
- package/lib/providers/GitHubProvider.js +475 -0
- package/lib/services/EpicService.js +1092 -3
- package/lib/services/IssueService.js +991 -0
- package/package.json +9 -1
- package/scripts/publish-plugins.sh +166 -0
- package/autopm/.claude/agents/cloud/README.md +0 -55
- package/autopm/.claude/agents/cloud/aws-cloud-architect.md +0 -521
- package/autopm/.claude/agents/cloud/azure-cloud-architect.md +0 -436
- package/autopm/.claude/agents/cloud/gcp-cloud-architect.md +0 -385
- package/autopm/.claude/agents/cloud/gcp-cloud-functions-engineer.md +0 -306
- package/autopm/.claude/agents/cloud/gemini-api-expert.md +0 -880
- package/autopm/.claude/agents/cloud/kubernetes-orchestrator.md +0 -566
- package/autopm/.claude/agents/cloud/openai-python-expert.md +0 -1087
- package/autopm/.claude/agents/cloud/terraform-infrastructure-expert.md +0 -454
- package/autopm/.claude/agents/core/agent-manager.md +0 -296
- package/autopm/.claude/agents/core/code-analyzer.md +0 -131
- package/autopm/.claude/agents/core/file-analyzer.md +0 -162
- package/autopm/.claude/agents/core/test-runner.md +0 -200
- package/autopm/.claude/agents/data/airflow-orchestration-expert.md +0 -52
- package/autopm/.claude/agents/data/kedro-pipeline-expert.md +0 -50
- package/autopm/.claude/agents/data/langgraph-workflow-expert.md +0 -520
- package/autopm/.claude/agents/databases/README.md +0 -50
- package/autopm/.claude/agents/databases/bigquery-expert.md +0 -392
- package/autopm/.claude/agents/databases/cosmosdb-expert.md +0 -368
- package/autopm/.claude/agents/databases/mongodb-expert.md +0 -398
- package/autopm/.claude/agents/databases/postgresql-expert.md +0 -321
- package/autopm/.claude/agents/databases/redis-expert.md +0 -52
- package/autopm/.claude/agents/devops/README.md +0 -52
- package/autopm/.claude/agents/devops/azure-devops-specialist.md +0 -308
- package/autopm/.claude/agents/devops/docker-containerization-expert.md +0 -298
- package/autopm/.claude/agents/devops/github-operations-specialist.md +0 -335
- package/autopm/.claude/agents/devops/mcp-context-manager.md +0 -319
- package/autopm/.claude/agents/devops/observability-engineer.md +0 -574
- package/autopm/.claude/agents/devops/ssh-operations-expert.md +0 -1093
- package/autopm/.claude/agents/devops/traefik-proxy-expert.md +0 -444
- package/autopm/.claude/agents/frameworks/README.md +0 -64
- package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +0 -360
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +0 -254
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +0 -217
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +0 -226
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +0 -770
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +0 -244
- package/autopm/.claude/agents/integration/message-queue-engineer.md +0 -794
- package/autopm/.claude/agents/languages/README.md +0 -50
- package/autopm/.claude/agents/languages/bash-scripting-expert.md +0 -541
- package/autopm/.claude/agents/languages/javascript-frontend-engineer.md +0 -197
- package/autopm/.claude/agents/languages/nodejs-backend-engineer.md +0 -226
- package/autopm/.claude/agents/languages/python-backend-engineer.md +0 -214
- package/autopm/.claude/agents/languages/python-backend-expert.md +0 -289
- package/autopm/.claude/agents/testing/frontend-testing-engineer.md +0 -395
- package/autopm/.claude/commands/ai/langgraph-workflow.md +0 -65
- package/autopm/.claude/commands/ai/openai-chat.md +0 -65
- package/autopm/.claude/commands/azure/COMMANDS.md +0 -107
- package/autopm/.claude/commands/azure/COMMAND_MAPPING.md +0 -252
- package/autopm/.claude/commands/azure/INTEGRATION_FIX.md +0 -103
- package/autopm/.claude/commands/azure/README.md +0 -246
- package/autopm/.claude/commands/azure/active-work.md +0 -198
- package/autopm/.claude/commands/azure/aliases.md +0 -143
- package/autopm/.claude/commands/azure/blocked-items.md +0 -287
- package/autopm/.claude/commands/azure/clean.md +0 -93
- package/autopm/.claude/commands/azure/docs-query.md +0 -48
- package/autopm/.claude/commands/azure/feature-decompose.md +0 -380
- package/autopm/.claude/commands/azure/feature-list.md +0 -61
- package/autopm/.claude/commands/azure/feature-new.md +0 -115
- package/autopm/.claude/commands/azure/feature-show.md +0 -205
- package/autopm/.claude/commands/azure/feature-start.md +0 -130
- package/autopm/.claude/commands/azure/fix-integration-example.md +0 -93
- package/autopm/.claude/commands/azure/help.md +0 -150
- package/autopm/.claude/commands/azure/import-us.md +0 -269
- package/autopm/.claude/commands/azure/init.md +0 -211
- package/autopm/.claude/commands/azure/next-task.md +0 -262
- package/autopm/.claude/commands/azure/search.md +0 -160
- package/autopm/.claude/commands/azure/sprint-status.md +0 -235
- package/autopm/.claude/commands/azure/standup.md +0 -260
- package/autopm/.claude/commands/azure/sync-all.md +0 -99
- package/autopm/.claude/commands/azure/task-analyze.md +0 -186
- package/autopm/.claude/commands/azure/task-close.md +0 -329
- package/autopm/.claude/commands/azure/task-edit.md +0 -145
- package/autopm/.claude/commands/azure/task-list.md +0 -263
- package/autopm/.claude/commands/azure/task-new.md +0 -84
- package/autopm/.claude/commands/azure/task-reopen.md +0 -79
- package/autopm/.claude/commands/azure/task-show.md +0 -126
- package/autopm/.claude/commands/azure/task-start.md +0 -301
- package/autopm/.claude/commands/azure/task-status.md +0 -65
- package/autopm/.claude/commands/azure/task-sync.md +0 -67
- package/autopm/.claude/commands/azure/us-edit.md +0 -164
- package/autopm/.claude/commands/azure/us-list.md +0 -202
- package/autopm/.claude/commands/azure/us-new.md +0 -265
- package/autopm/.claude/commands/azure/us-parse.md +0 -253
- package/autopm/.claude/commands/azure/us-show.md +0 -188
- package/autopm/.claude/commands/azure/us-status.md +0 -320
- package/autopm/.claude/commands/azure/validate.md +0 -86
- package/autopm/.claude/commands/azure/work-item-sync.md +0 -47
- package/autopm/.claude/commands/cloud/infra-deploy.md +0 -38
- package/autopm/.claude/commands/github/workflow-create.md +0 -42
- package/autopm/.claude/commands/infrastructure/ssh-security.md +0 -65
- package/autopm/.claude/commands/infrastructure/traefik-setup.md +0 -65
- package/autopm/.claude/commands/kubernetes/deploy.md +0 -37
- package/autopm/.claude/commands/playwright/test-scaffold.md +0 -38
- package/autopm/.claude/commands/pm/blocked.md +0 -28
- package/autopm/.claude/commands/pm/clean.md +0 -119
- package/autopm/.claude/commands/pm/context-create.md +0 -136
- package/autopm/.claude/commands/pm/context-prime.md +0 -170
- package/autopm/.claude/commands/pm/context-update.md +0 -292
- package/autopm/.claude/commands/pm/context.md +0 -28
- package/autopm/.claude/commands/pm/epic-close.md +0 -86
- package/autopm/.claude/commands/pm/epic-decompose.md +0 -370
- package/autopm/.claude/commands/pm/epic-edit.md +0 -83
- package/autopm/.claude/commands/pm/epic-list.md +0 -30
- package/autopm/.claude/commands/pm/epic-merge.md +0 -222
- package/autopm/.claude/commands/pm/epic-oneshot.md +0 -119
- package/autopm/.claude/commands/pm/epic-refresh.md +0 -119
- package/autopm/.claude/commands/pm/epic-show.md +0 -28
- package/autopm/.claude/commands/pm/epic-split.md +0 -120
- package/autopm/.claude/commands/pm/epic-start.md +0 -195
- package/autopm/.claude/commands/pm/epic-status.md +0 -28
- package/autopm/.claude/commands/pm/epic-sync-modular.md +0 -338
- package/autopm/.claude/commands/pm/epic-sync-original.md +0 -473
- package/autopm/.claude/commands/pm/epic-sync.md +0 -486
- package/autopm/.claude/commands/pm/help.md +0 -28
- package/autopm/.claude/commands/pm/import.md +0 -115
- package/autopm/.claude/commands/pm/in-progress.md +0 -28
- package/autopm/.claude/commands/pm/init.md +0 -28
- package/autopm/.claude/commands/pm/issue-analyze.md +0 -202
- package/autopm/.claude/commands/pm/issue-close.md +0 -119
- package/autopm/.claude/commands/pm/issue-edit.md +0 -93
- package/autopm/.claude/commands/pm/issue-reopen.md +0 -87
- package/autopm/.claude/commands/pm/issue-show.md +0 -41
- package/autopm/.claude/commands/pm/issue-start.md +0 -234
- package/autopm/.claude/commands/pm/issue-status.md +0 -95
- package/autopm/.claude/commands/pm/issue-sync.md +0 -411
- package/autopm/.claude/commands/pm/next.md +0 -28
- package/autopm/.claude/commands/pm/prd-edit.md +0 -82
- package/autopm/.claude/commands/pm/prd-list.md +0 -28
- package/autopm/.claude/commands/pm/prd-new.md +0 -55
- package/autopm/.claude/commands/pm/prd-parse.md +0 -42
- package/autopm/.claude/commands/pm/prd-status.md +0 -28
- package/autopm/.claude/commands/pm/search.md +0 -28
- package/autopm/.claude/commands/pm/standup.md +0 -28
- package/autopm/.claude/commands/pm/status.md +0 -28
- package/autopm/.claude/commands/pm/sync.md +0 -99
- package/autopm/.claude/commands/pm/test-reference-update.md +0 -151
- package/autopm/.claude/commands/pm/validate.md +0 -28
- package/autopm/.claude/commands/pm/what-next.md +0 -28
- package/autopm/.claude/commands/python/api-scaffold.md +0 -50
- package/autopm/.claude/commands/python/docs-query.md +0 -48
- package/autopm/.claude/commands/react/app-scaffold.md +0 -50
- package/autopm/.claude/commands/testing/prime.md +0 -314
- package/autopm/.claude/commands/testing/run.md +0 -125
- package/autopm/.claude/commands/ui/bootstrap-scaffold.md +0 -65
- package/autopm/.claude/commands/ui/tailwind-system.md +0 -64
- package/autopm/.claude/rules/ai-integration-patterns.md +0 -219
- package/autopm/.claude/rules/ci-cd-kubernetes-strategy.md +0 -25
- package/autopm/.claude/rules/database-management-strategy.md +0 -17
- package/autopm/.claude/rules/database-pipeline.md +0 -94
- package/autopm/.claude/rules/devops-troubleshooting-playbook.md +0 -450
- package/autopm/.claude/rules/docker-first-development.md +0 -404
- package/autopm/.claude/rules/infrastructure-pipeline.md +0 -128
- package/autopm/.claude/rules/performance-guidelines.md +0 -403
- package/autopm/.claude/rules/ui-development-standards.md +0 -281
- package/autopm/.claude/rules/ui-framework-rules.md +0 -151
- package/autopm/.claude/rules/ux-design-rules.md +0 -209
- package/autopm/.claude/rules/visual-testing.md +0 -223
- package/autopm/.claude/scripts/azure/README.md +0 -192
- package/autopm/.claude/scripts/azure/active-work.js +0 -524
- package/autopm/.claude/scripts/azure/active-work.sh +0 -20
- package/autopm/.claude/scripts/azure/blocked.js +0 -520
- package/autopm/.claude/scripts/azure/blocked.sh +0 -20
- package/autopm/.claude/scripts/azure/daily.js +0 -533
- package/autopm/.claude/scripts/azure/daily.sh +0 -20
- package/autopm/.claude/scripts/azure/dashboard.js +0 -970
- package/autopm/.claude/scripts/azure/dashboard.sh +0 -20
- package/autopm/.claude/scripts/azure/feature-list.js +0 -254
- package/autopm/.claude/scripts/azure/feature-list.sh +0 -20
- package/autopm/.claude/scripts/azure/feature-show.js +0 -7
- package/autopm/.claude/scripts/azure/feature-show.sh +0 -20
- package/autopm/.claude/scripts/azure/feature-status.js +0 -604
- package/autopm/.claude/scripts/azure/feature-status.sh +0 -20
- package/autopm/.claude/scripts/azure/help.js +0 -342
- package/autopm/.claude/scripts/azure/help.sh +0 -20
- package/autopm/.claude/scripts/azure/next-task.js +0 -508
- package/autopm/.claude/scripts/azure/next-task.sh +0 -20
- package/autopm/.claude/scripts/azure/search.js +0 -469
- package/autopm/.claude/scripts/azure/search.sh +0 -20
- package/autopm/.claude/scripts/azure/setup.js +0 -745
- package/autopm/.claude/scripts/azure/setup.sh +0 -20
- package/autopm/.claude/scripts/azure/sprint-report.js +0 -1012
- package/autopm/.claude/scripts/azure/sprint-report.sh +0 -20
- package/autopm/.claude/scripts/azure/sync.js +0 -563
- package/autopm/.claude/scripts/azure/sync.sh +0 -20
- package/autopm/.claude/scripts/azure/us-list.js +0 -210
- package/autopm/.claude/scripts/azure/us-list.sh +0 -20
- package/autopm/.claude/scripts/azure/us-status.js +0 -238
- package/autopm/.claude/scripts/azure/us-status.sh +0 -20
- package/autopm/.claude/scripts/azure/validate.js +0 -626
- package/autopm/.claude/scripts/azure/validate.sh +0 -20
- package/autopm/.claude/scripts/azure/wrapper-template.sh +0 -20
- package/autopm/.claude/scripts/github/dependency-tracker.js +0 -554
- package/autopm/.claude/scripts/github/dependency-validator.js +0 -545
- package/autopm/.claude/scripts/github/dependency-visualizer.js +0 -477
- package/autopm/.claude/scripts/pm/analytics.js +0 -425
- package/autopm/.claude/scripts/pm/blocked.js +0 -164
- package/autopm/.claude/scripts/pm/blocked.sh +0 -78
- package/autopm/.claude/scripts/pm/clean.js +0 -464
- package/autopm/.claude/scripts/pm/context-create.js +0 -216
- package/autopm/.claude/scripts/pm/context-prime.js +0 -335
- package/autopm/.claude/scripts/pm/context-update.js +0 -344
- package/autopm/.claude/scripts/pm/context.js +0 -338
- package/autopm/.claude/scripts/pm/epic-close.js +0 -347
- package/autopm/.claude/scripts/pm/epic-edit.js +0 -382
- package/autopm/.claude/scripts/pm/epic-list.js +0 -273
- package/autopm/.claude/scripts/pm/epic-list.sh +0 -109
- package/autopm/.claude/scripts/pm/epic-show.js +0 -291
- package/autopm/.claude/scripts/pm/epic-show.sh +0 -105
- package/autopm/.claude/scripts/pm/epic-split.js +0 -522
- package/autopm/.claude/scripts/pm/epic-start/epic-start.js +0 -183
- package/autopm/.claude/scripts/pm/epic-start/epic-start.sh +0 -94
- package/autopm/.claude/scripts/pm/epic-status.js +0 -291
- package/autopm/.claude/scripts/pm/epic-status.sh +0 -104
- package/autopm/.claude/scripts/pm/epic-sync/README.md +0 -208
- package/autopm/.claude/scripts/pm/epic-sync/create-epic-issue.sh +0 -77
- package/autopm/.claude/scripts/pm/epic-sync/create-task-issues.sh +0 -86
- package/autopm/.claude/scripts/pm/epic-sync/update-epic-file.sh +0 -79
- package/autopm/.claude/scripts/pm/epic-sync/update-references.sh +0 -89
- package/autopm/.claude/scripts/pm/epic-sync.sh +0 -137
- package/autopm/.claude/scripts/pm/help.js +0 -92
- package/autopm/.claude/scripts/pm/help.sh +0 -90
- package/autopm/.claude/scripts/pm/in-progress.js +0 -178
- package/autopm/.claude/scripts/pm/in-progress.sh +0 -93
- package/autopm/.claude/scripts/pm/init.js +0 -321
- package/autopm/.claude/scripts/pm/init.sh +0 -178
- package/autopm/.claude/scripts/pm/issue-close.js +0 -232
- package/autopm/.claude/scripts/pm/issue-edit.js +0 -310
- package/autopm/.claude/scripts/pm/issue-show.js +0 -272
- package/autopm/.claude/scripts/pm/issue-start.js +0 -181
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +0 -468
- package/autopm/.claude/scripts/pm/issue-sync/gather-updates.sh +0 -460
- package/autopm/.claude/scripts/pm/issue-sync/post-comment.sh +0 -330
- package/autopm/.claude/scripts/pm/issue-sync/preflight-validation.sh +0 -348
- package/autopm/.claude/scripts/pm/issue-sync/update-frontmatter.sh +0 -387
- package/autopm/.claude/scripts/pm/lib/README.md +0 -85
- package/autopm/.claude/scripts/pm/lib/epic-discovery.js +0 -119
- package/autopm/.claude/scripts/pm/lib/logger.js +0 -78
- package/autopm/.claude/scripts/pm/next.js +0 -189
- package/autopm/.claude/scripts/pm/next.sh +0 -72
- package/autopm/.claude/scripts/pm/optimize.js +0 -407
- package/autopm/.claude/scripts/pm/pr-create.js +0 -337
- package/autopm/.claude/scripts/pm/pr-list.js +0 -257
- package/autopm/.claude/scripts/pm/prd-list.js +0 -242
- package/autopm/.claude/scripts/pm/prd-list.sh +0 -103
- package/autopm/.claude/scripts/pm/prd-new.js +0 -684
- package/autopm/.claude/scripts/pm/prd-parse.js +0 -547
- package/autopm/.claude/scripts/pm/prd-status.js +0 -152
- package/autopm/.claude/scripts/pm/prd-status.sh +0 -63
- package/autopm/.claude/scripts/pm/release.js +0 -460
- package/autopm/.claude/scripts/pm/search.js +0 -192
- package/autopm/.claude/scripts/pm/search.sh +0 -89
- package/autopm/.claude/scripts/pm/standup.js +0 -362
- package/autopm/.claude/scripts/pm/standup.sh +0 -95
- package/autopm/.claude/scripts/pm/status.js +0 -148
- package/autopm/.claude/scripts/pm/status.sh +0 -59
- package/autopm/.claude/scripts/pm/sync-batch.js +0 -337
- package/autopm/.claude/scripts/pm/sync.js +0 -343
- package/autopm/.claude/scripts/pm/template-list.js +0 -141
- package/autopm/.claude/scripts/pm/template-new.js +0 -366
- package/autopm/.claude/scripts/pm/validate.js +0 -274
- package/autopm/.claude/scripts/pm/validate.sh +0 -106
- package/autopm/.claude/scripts/pm/what-next.js +0 -660
- package/bin/node/azure-feature-show.js +0 -7
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Provider for ClaudeAutoPM
|
|
3
|
+
*
|
|
4
|
+
* Provides bidirectional synchronization with GitHub Issues following 2025 best practices
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Full CRUD operations for issues
|
|
8
|
+
* - Comment management
|
|
9
|
+
* - Label management
|
|
10
|
+
* - Advanced search
|
|
11
|
+
* - Rate limiting with exponential backoff
|
|
12
|
+
* - Retry logic for transient failures
|
|
13
|
+
* - GitHub Enterprise support
|
|
14
|
+
*
|
|
15
|
+
* @module lib/providers/GitHubProvider
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { Octokit } = require('@octokit/rest');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GitHub Provider Class
|
|
22
|
+
*
|
|
23
|
+
* Manages integration with GitHub Issues API using Octokit
|
|
24
|
+
*
|
|
25
|
+
* @class GitHubProvider
|
|
26
|
+
*/
|
|
27
|
+
class GitHubProvider {
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new GitHub provider instance
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} options - Configuration options
|
|
32
|
+
* @param {string} [options.token] - GitHub Personal Access Token
|
|
33
|
+
* @param {string} [options.owner] - Repository owner
|
|
34
|
+
* @param {string} [options.repo] - Repository name
|
|
35
|
+
* @param {string} [options.baseUrl] - Custom GitHub API URL (for Enterprise)
|
|
36
|
+
* @param {number} [options.timeout=30000] - Request timeout in milliseconds
|
|
37
|
+
* @param {number} [options.maxRetries=3] - Maximum retry attempts
|
|
38
|
+
*/
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.token = options.token || process.env.GITHUB_TOKEN;
|
|
41
|
+
this.owner = options.owner || process.env.GITHUB_OWNER;
|
|
42
|
+
this.repo = options.repo || process.env.GITHUB_REPO;
|
|
43
|
+
this.baseUrl = options.baseUrl;
|
|
44
|
+
this.timeout = options.timeout || 30000;
|
|
45
|
+
this.maxRetries = options.maxRetries || 3;
|
|
46
|
+
|
|
47
|
+
this.octokit = null;
|
|
48
|
+
this.rateLimitRemaining = null;
|
|
49
|
+
this.rateLimitReset = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Authenticates with GitHub API
|
|
54
|
+
*
|
|
55
|
+
* Creates Octokit instance and verifies authentication
|
|
56
|
+
* by fetching the authenticated user's info
|
|
57
|
+
*
|
|
58
|
+
* @async
|
|
59
|
+
* @returns {Promise<Object>} Authenticated user object
|
|
60
|
+
* @throws {Error} If token is missing or authentication fails
|
|
61
|
+
*/
|
|
62
|
+
async authenticate() {
|
|
63
|
+
if (!this.token) {
|
|
64
|
+
throw new Error('GitHub token is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const options = {
|
|
68
|
+
auth: this.token,
|
|
69
|
+
timeout: this.timeout
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (this.baseUrl) {
|
|
73
|
+
options.baseUrl = this.baseUrl;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.octokit = new Octokit(options);
|
|
77
|
+
|
|
78
|
+
// Verify authentication
|
|
79
|
+
const { data: user } = await this.octokit.users.getAuthenticated();
|
|
80
|
+
|
|
81
|
+
return user;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fetches a single issue by number
|
|
86
|
+
*
|
|
87
|
+
* @async
|
|
88
|
+
* @param {number} number - Issue number
|
|
89
|
+
* @returns {Promise<Object>} Issue object
|
|
90
|
+
* @throws {Error} If issue is not found or request fails
|
|
91
|
+
*/
|
|
92
|
+
async getIssue(number) {
|
|
93
|
+
await this._checkRateLimit();
|
|
94
|
+
|
|
95
|
+
const { data } = await this.octokit.rest.issues.get({
|
|
96
|
+
owner: this.owner,
|
|
97
|
+
repo: this.repo,
|
|
98
|
+
issue_number: number
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Lists issues with optional filtering
|
|
106
|
+
*
|
|
107
|
+
* @async
|
|
108
|
+
* @param {Object} [filters={}] - Filter options
|
|
109
|
+
* @param {string} [filters.state] - Issue state (open, closed, all)
|
|
110
|
+
* @param {Array<string>} [filters.labels] - Filter by labels
|
|
111
|
+
* @param {string} [filters.assignee] - Filter by assignee
|
|
112
|
+
* @param {number} [filters.page=1] - Page number
|
|
113
|
+
* @param {number} [filters.perPage=100] - Results per page
|
|
114
|
+
* @returns {Promise<Array>} Array of issue objects
|
|
115
|
+
*/
|
|
116
|
+
async listIssues(filters = {}) {
|
|
117
|
+
await this._checkRateLimit();
|
|
118
|
+
|
|
119
|
+
const params = {
|
|
120
|
+
owner: this.owner,
|
|
121
|
+
repo: this.repo,
|
|
122
|
+
per_page: filters.perPage || 100
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (filters.state) {
|
|
126
|
+
params.state = filters.state;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (filters.labels && filters.labels.length > 0) {
|
|
130
|
+
params.labels = filters.labels.join(',');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (filters.assignee) {
|
|
134
|
+
params.assignee = filters.assignee;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (filters.page) {
|
|
138
|
+
params.page = filters.page;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { data } = await this.octokit.rest.issues.list(params);
|
|
142
|
+
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Creates a new issue
|
|
148
|
+
*
|
|
149
|
+
* @async
|
|
150
|
+
* @param {Object} data - Issue data
|
|
151
|
+
* @param {string} data.title - Issue title (required)
|
|
152
|
+
* @param {string} [data.body] - Issue description
|
|
153
|
+
* @param {Array<string>} [data.labels] - Labels to add
|
|
154
|
+
* @param {Array<string>} [data.assignees] - Assignees
|
|
155
|
+
* @returns {Promise<Object>} Created issue object
|
|
156
|
+
* @throws {Error} If title is missing or creation fails
|
|
157
|
+
*/
|
|
158
|
+
async createIssue(data) {
|
|
159
|
+
if (!data.title) {
|
|
160
|
+
throw new Error('Issue title is required');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await this._checkRateLimit();
|
|
164
|
+
|
|
165
|
+
const params = {
|
|
166
|
+
owner: this.owner,
|
|
167
|
+
repo: this.repo,
|
|
168
|
+
title: data.title
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (data.body) {
|
|
172
|
+
params.body = data.body;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (data.labels) {
|
|
176
|
+
params.labels = data.labels;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (data.assignees) {
|
|
180
|
+
params.assignees = data.assignees;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { data: issue } = await this.octokit.rest.issues.create(params);
|
|
184
|
+
|
|
185
|
+
return issue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Updates an existing issue
|
|
190
|
+
*
|
|
191
|
+
* @async
|
|
192
|
+
* @param {number} number - Issue number
|
|
193
|
+
* @param {Object} data - Fields to update
|
|
194
|
+
* @param {string} [data.title] - New title
|
|
195
|
+
* @param {string} [data.body] - New body
|
|
196
|
+
* @param {string} [data.state] - New state (open, closed)
|
|
197
|
+
* @param {Array<string>} [data.labels] - New labels
|
|
198
|
+
* @returns {Promise<Object>} Updated issue object
|
|
199
|
+
*/
|
|
200
|
+
async updateIssue(number, data) {
|
|
201
|
+
await this._checkRateLimit();
|
|
202
|
+
|
|
203
|
+
const params = {
|
|
204
|
+
owner: this.owner,
|
|
205
|
+
repo: this.repo,
|
|
206
|
+
issue_number: number,
|
|
207
|
+
...data
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const { data: issue } = await this.octokit.rest.issues.update(params);
|
|
211
|
+
|
|
212
|
+
return issue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Closes an issue
|
|
217
|
+
*
|
|
218
|
+
* @async
|
|
219
|
+
* @param {number} number - Issue number
|
|
220
|
+
* @param {string} [comment] - Optional closing comment
|
|
221
|
+
* @returns {Promise<Object>} Closed issue object
|
|
222
|
+
*/
|
|
223
|
+
async closeIssue(number, comment) {
|
|
224
|
+
if (comment) {
|
|
225
|
+
await this.createComment(number, comment);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return await this.updateIssue(number, { state: 'closed' });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Creates a comment on an issue
|
|
233
|
+
*
|
|
234
|
+
* @async
|
|
235
|
+
* @param {number} number - Issue number
|
|
236
|
+
* @param {string} body - Comment body
|
|
237
|
+
* @returns {Promise<Object>} Created comment object
|
|
238
|
+
* @throws {Error} If body is empty
|
|
239
|
+
*/
|
|
240
|
+
async createComment(number, body) {
|
|
241
|
+
if (!body || body.trim() === '') {
|
|
242
|
+
throw new Error('Comment body is required');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await this._checkRateLimit();
|
|
246
|
+
|
|
247
|
+
const { data } = await this.octokit.rest.issues.createComment({
|
|
248
|
+
owner: this.owner,
|
|
249
|
+
repo: this.repo,
|
|
250
|
+
issue_number: number,
|
|
251
|
+
body
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return data;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Lists comments for an issue
|
|
259
|
+
*
|
|
260
|
+
* @async
|
|
261
|
+
* @param {number} number - Issue number
|
|
262
|
+
* @param {Object} [options={}] - Pagination options
|
|
263
|
+
* @param {number} [options.page] - Page number
|
|
264
|
+
* @param {number} [options.perPage=100] - Results per page
|
|
265
|
+
* @returns {Promise<Array>} Array of comment objects
|
|
266
|
+
*/
|
|
267
|
+
async listComments(number, options = {}) {
|
|
268
|
+
await this._checkRateLimit();
|
|
269
|
+
|
|
270
|
+
const params = {
|
|
271
|
+
owner: this.owner,
|
|
272
|
+
repo: this.repo,
|
|
273
|
+
issue_number: number,
|
|
274
|
+
per_page: options.perPage || 100
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (options.page) {
|
|
278
|
+
params.page = options.page;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const { data } = await this.octokit.rest.issues.listComments(params);
|
|
282
|
+
|
|
283
|
+
return data;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Adds labels to an issue
|
|
288
|
+
*
|
|
289
|
+
* @async
|
|
290
|
+
* @param {number} number - Issue number
|
|
291
|
+
* @param {Array<string>} labels - Labels to add
|
|
292
|
+
* @returns {Promise<Array>} Array of label objects
|
|
293
|
+
* @throws {Error} If labels array is empty
|
|
294
|
+
*/
|
|
295
|
+
async addLabels(number, labels) {
|
|
296
|
+
if (!labels || labels.length === 0) {
|
|
297
|
+
throw new Error('At least one label is required');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await this._checkRateLimit();
|
|
301
|
+
|
|
302
|
+
const { data } = await this.octokit.rest.issues.addLabels({
|
|
303
|
+
owner: this.owner,
|
|
304
|
+
repo: this.repo,
|
|
305
|
+
issue_number: number,
|
|
306
|
+
labels
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return data;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Searches issues using GitHub search syntax
|
|
314
|
+
*
|
|
315
|
+
* @async
|
|
316
|
+
* @param {string} query - Search query
|
|
317
|
+
* @param {Object} [options={}] - Additional options
|
|
318
|
+
* @param {Array<string>} [options.labels] - Filter by labels
|
|
319
|
+
* @param {string} [options.state] - Filter by state
|
|
320
|
+
* @param {number} [options.page] - Page number
|
|
321
|
+
* @param {number} [options.perPage=100] - Results per page
|
|
322
|
+
* @returns {Promise<Object>} Search results with total_count and items
|
|
323
|
+
*/
|
|
324
|
+
async searchIssues(query, options = {}) {
|
|
325
|
+
await this._checkRateLimit();
|
|
326
|
+
|
|
327
|
+
let searchQuery = query;
|
|
328
|
+
|
|
329
|
+
// Build query with filters
|
|
330
|
+
if (options.labels && options.labels.length > 0) {
|
|
331
|
+
searchQuery += ` label:${options.labels[0]}`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (options.state) {
|
|
335
|
+
searchQuery += ` state:${options.state}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Always scope to current repo
|
|
339
|
+
searchQuery += ` repo:${this.owner}/${this.repo}`;
|
|
340
|
+
|
|
341
|
+
const params = {
|
|
342
|
+
q: searchQuery,
|
|
343
|
+
per_page: options.perPage || 100
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
if (options.page) {
|
|
347
|
+
params.page = options.page;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const { data } = await this.octokit.rest.search.issuesAndPullRequests(params);
|
|
351
|
+
|
|
352
|
+
return data;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Checks current rate limit status
|
|
357
|
+
*
|
|
358
|
+
* @async
|
|
359
|
+
* @returns {Promise<Object>} Rate limit information
|
|
360
|
+
*/
|
|
361
|
+
async checkRateLimit() {
|
|
362
|
+
const { data } = await this.octokit.rest.rateLimit.get();
|
|
363
|
+
|
|
364
|
+
this.rateLimitRemaining = data.resources.core.remaining;
|
|
365
|
+
this.rateLimitReset = data.resources.core.reset;
|
|
366
|
+
|
|
367
|
+
return data.resources;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Handles rate limit errors with exponential backoff
|
|
372
|
+
*
|
|
373
|
+
* @async
|
|
374
|
+
* @param {Error} error - Rate limit error
|
|
375
|
+
* @param {number} retryCount - Current retry attempt
|
|
376
|
+
* @returns {Promise<number>} Wait time in milliseconds
|
|
377
|
+
* @throws {Error} If max retries exceeded
|
|
378
|
+
* @private
|
|
379
|
+
*/
|
|
380
|
+
async handleRateLimitError(error, retryCount) {
|
|
381
|
+
if (retryCount >= this.maxRetries) {
|
|
382
|
+
throw new Error('Maximum retry attempts exceeded');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let waitTime;
|
|
386
|
+
|
|
387
|
+
// Check if we have reset time from headers
|
|
388
|
+
if (error.response && error.response.headers) {
|
|
389
|
+
const resetTime = error.response.headers['x-ratelimit-reset'];
|
|
390
|
+
if (resetTime) {
|
|
391
|
+
const now = Math.floor(Date.now() / 1000);
|
|
392
|
+
const resetTimestamp = parseInt(resetTime, 10);
|
|
393
|
+
waitTime = Math.max(0, (resetTimestamp - now) * 1000);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Fallback to exponential backoff
|
|
398
|
+
if (!waitTime || waitTime === 0) {
|
|
399
|
+
// 2^retryCount * 1000ms (1s, 2s, 4s, 8s...)
|
|
400
|
+
waitTime = Math.pow(2, retryCount) * 1000;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return waitTime;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Makes a request with retry logic
|
|
408
|
+
*
|
|
409
|
+
* @async
|
|
410
|
+
* @param {Function} requestFn - Function that makes the request
|
|
411
|
+
* @param {Object} [options={}] - Options
|
|
412
|
+
* @param {number} [options.maxRetries] - Override max retries
|
|
413
|
+
* @returns {Promise<*>} Response data
|
|
414
|
+
* @throws {Error} If request fails permanently
|
|
415
|
+
* @private
|
|
416
|
+
*/
|
|
417
|
+
async _makeRequest(requestFn, options = {}) {
|
|
418
|
+
const maxRetries = options.maxRetries !== undefined ? options.maxRetries : this.maxRetries;
|
|
419
|
+
let lastError;
|
|
420
|
+
|
|
421
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
422
|
+
try {
|
|
423
|
+
const { data } = await requestFn();
|
|
424
|
+
return data;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
lastError = error;
|
|
427
|
+
|
|
428
|
+
// Check if error is rate limit
|
|
429
|
+
if (error.status === 403 && error.message && error.message.includes('rate limit')) {
|
|
430
|
+
const waitTime = await this.handleRateLimitError(error, attempt);
|
|
431
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Check if error is transient (5xx)
|
|
436
|
+
if (error.status >= 500 && error.status < 600) {
|
|
437
|
+
if (attempt < maxRetries) {
|
|
438
|
+
const waitTime = Math.pow(2, attempt) * 1000;
|
|
439
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Non-retryable error
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
throw lastError;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Checks rate limit before making requests
|
|
454
|
+
*
|
|
455
|
+
* @async
|
|
456
|
+
* @private
|
|
457
|
+
*/
|
|
458
|
+
async _checkRateLimit() {
|
|
459
|
+
if (!this.octokit) {
|
|
460
|
+
throw new Error('Not authenticated. Call authenticate() first.');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Only check if we haven't checked recently
|
|
464
|
+
if (this.rateLimitRemaining === null) {
|
|
465
|
+
await this.checkRateLimit();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Warn if rate limit is low
|
|
469
|
+
if (this.rateLimitRemaining !== null && this.rateLimitRemaining < 100) {
|
|
470
|
+
console.warn(`GitHub API rate limit low: ${this.rateLimitRemaining} requests remaining`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
module.exports = GitHubProvider;
|