diff --git a/docs/api-reference/create-issue.mdx b/docs/api-reference/create-issue.mdx new file mode 100644 index 00000000..f7a88c9c --- /dev/null +++ b/docs/api-reference/create-issue.mdx @@ -0,0 +1,265 @@ +--- +title: "createIssue" +description: "Create a new GitHub issue in your repository." +--- + +## Method signature + +```typescript +async createIssue( + options: CreateIssueOptions +): Promise +``` + +## Description + +The `createIssue` method allows you to create a new GitHub issue in your configured repository. This method is useful for automated issue creation from code generation workflows, error reporting, or programmatic project management. + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `options` | `CreateIssueOptions` | Yes | Configuration options for the new issue | + +### CreateIssueOptions Interface + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `title` | `string` | Yes | The title of the issue | +| `body` | `string` | No | The body content of the issue (supports Markdown) | +| `labels` | `string[]` | No | Array of label names to apply to the issue | +| `assignees` | `string[]` | No | Array of GitHub usernames to assign to the issue | +| `milestone` | `number` | No | The number of the milestone to associate with this issue | + +## Return type + +| Type | Description | +|------|-------------| +| `Promise` | Promise that resolves to an issue result object | + +### IssueResult Interface + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `number` | The unique identifier of the issue | +| `number` | `number` | The issue number | +| `title` | `string` | The title of the issue | +| `body` | `string \| null` | The body content of the issue | +| `state` | `'open' \| 'closed'` | The current state of the issue | +| `state_reason` | `'completed' \| 'not_planned' \| 'reopened' \| null` | The reason for the current state | +| `html_url` | `string` | The URL to view the issue on GitHub | +| `user` | `object` | Information about the user who created the issue | +| `labels` | `array` | Array of labels applied to the issue | +| `assignees` | `array` | Array of users assigned to the issue | +| `created_at` | `string` | ISO timestamp when the issue was created | +| `updated_at` | `string` | ISO timestamp when the issue was last updated | +| `closed_at` | `string` | ISO timestamp when the issue was closed (if applicable) | +| `comments` | `number` | Number of comments on the issue | +| `milestone` | `object` | Milestone information (if associated) | + +## Requirements + +- **GitHub Configuration**: A valid GitHub token and repository URL must be configured using `withGithub()` +- **Permissions**: The GitHub token must have `repo` scope for private repositories, or `public_repo` scope for public repositories + +## Error handling + +The method throws errors in the following scenarios: + +### Configuration Error +```typescript +throw new Error("GitHub configuration is required for creating issues. Please use withGithub() to configure GitHub credentials.") +``` +- **When**: GitHub configuration is missing +- **Resolution**: Configure GitHub credentials using `withGithub({ token, repository })` + +### Validation Error +```typescript +throw new Error("Issue title is required") +``` +- **When**: The title parameter is empty or undefined +- **Resolution**: Provide a valid title in the options + +### Repository Error +```typescript +throw new Error("Repository owner/repo not found") +``` +- **When**: The repository doesn't exist or the token lacks access +- **Resolution**: Verify repository name and token permissions + +### API Error +```typescript +throw new Error("Invalid issue parameters: ...") +``` +- **When**: GitHub API validation fails (invalid labels, assignees, etc.) +- **Resolution**: Check that labels exist, assignees are valid users, and milestone exists + +## Usage examples + +### Basic Usage + +```typescript +import { VibeKit } from 'vibekit'; + +const vibekit = new VibeKit() + .withGithub({ + token: process.env.GITHUB_TOKEN!, + repository: "your-org/your-repo" + }); + +// Create a simple issue +try { + const issue = await vibekit.createIssue({ + title: "Bug: Login form validation error", + body: "The login form shows incorrect validation messages when email is empty." + }); + + console.log(`Issue created: ${issue.html_url}`); + console.log(`Issue #${issue.number}: ${issue.title}`); + console.log(`State: ${issue.state}`); + console.log(`Created: ${issue.created_at}`); +} catch (error) { + console.error("Failed to create issue:", error.message); +} +``` + +### Advanced Usage with Labels and Assignees + +```typescript +import { VibeKit, CreateIssueOptions } from 'vibekit'; + +const vibekit = new VibeKit() + .withGithub({ + token: process.env.GITHUB_TOKEN!, + repository: "your-org/your-repo" + }); + +// Create issue with full options +const issueOptions: CreateIssueOptions = { + title: "Feature Request: Add dark mode support", + body: `## Description +Add dark mode toggle to the application. + +## Requirements +- [ ] Toggle button in settings +- [ ] Dark theme CSS variables +- [ ] Persistent user preference +- [ ] System preference detection + +## Acceptance Criteria +- User can switch between light and dark modes +- Theme preference persists across sessions +- Respects system dark mode setting by default`, + labels: ["enhancement", "ui", "good first issue"], + assignees: ["frontend-dev", "ux-designer"], + milestone: 5 +}; + +try { + const issue = await vibekit.createIssue(issueOptions); + + console.log(`Feature request created: ${issue.html_url}`); + console.log(`Issue #${issue.number}: ${issue.title}`); + console.log(`Labels: ${issue.labels.map(l => l.name).join(', ')}`); + console.log(`Assignees: ${issue.assignees.map(a => a.login).join(', ')}`); + console.log(`Milestone: ${issue.milestone?.title || 'None'}`); +} catch (error) { + console.error("Failed to create feature request:", error.message); +} +``` + +### Creating Issues from Code Generation + +```typescript +import { VibeKit } from 'vibekit'; + +const vibekit = new VibeKit() + .withGithub({ + token: process.env.GITHUB_TOKEN!, + repository: "your-org/your-repo" + }); + +// Example: Create issue when code generation identifies a problem +async function reportCodeIssue(errorDetails: string) { + try { + const issue = await vibekit.createIssue({ + title: "AI Code Generation Error", + body: `## Error Details +${errorDetails} + +## Context +This issue was automatically created during AI code generation. + +## Next Steps +- [ ] Review the error details +- [ ] Implement fix +- [ ] Update code generation rules`, + labels: ["bug", "ai-generated", "priority-high"], + assignees: ["tech-lead"] + }); + + return issue; + } catch (error) { + console.error("Failed to create issue:", error.message); + throw error; + } +} +``` + +### Bulk Issue Creation + +```typescript +import { VibeKit } from 'vibekit'; + +const vibekit = new VibeKit() + .withGithub({ + token: process.env.GITHUB_TOKEN!, + repository: "your-org/your-repo" + }); + +// Create multiple issues from a list +const issueTemplates = [ + { + title: "Setup CI/CD pipeline", + body: "Configure automated testing and deployment", + labels: ["devops", "enhancement"] + }, + { + title: "Add unit tests for auth module", + body: "Increase test coverage for authentication functionality", + labels: ["testing", "auth"] + }, + { + title: "Update API documentation", + body: "Sync docs with recent API changes", + labels: ["documentation"] + } +]; + +async function createProjectIssues() { + const createdIssues = []; + + for (const template of issueTemplates) { + try { + const issue = await vibekit.createIssue(template); + createdIssues.push(issue); + console.log(`Created issue #${issue.number}: ${issue.title}`); + } catch (error) { + console.error(`Failed to create issue "${template.title}":`, error.message); + } + } + + return createdIssues; +} +``` + +## Notes + +- The method requires only GitHub configuration - no agent or sandbox setup needed +- Labels will be created if they don't exist in the repository +- Assignees must be valid GitHub usernames with repository access +- Milestone must exist in the repository before referencing by number +- The issue body supports full GitHub Flavored Markdown syntax +- Issue numbers are auto-generated by GitHub and cannot be specified +- Created issues are immediately visible in the GitHub repository +- The response includes the complete GitHub API issue data \ No newline at end of file diff --git a/docs/api-reference/update-issue.mdx b/docs/api-reference/update-issue.mdx new file mode 100644 index 00000000..503969d0 --- /dev/null +++ b/docs/api-reference/update-issue.mdx @@ -0,0 +1,289 @@ +--- +title: 'updateIssue' +description: 'Update or close an existing GitHub issue' +--- + +## Overview + +The `updateIssue` method allows you to programmatically update any aspect of an existing GitHub issue, including closing, reopening, modifying content, and managing labels and assignees. It's particularly useful for automated issue management workflows. + +## Method Signature + +```typescript +async updateIssue( + issueNumber: number, + options: UpdateIssueOptions +): Promise +``` + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `issueNumber` | `number` | Yes | The number of the issue to update | +| `options` | `UpdateIssueOptions` | Yes | Configuration options for the update | + +### UpdateIssueOptions + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `title` | `string` | No | Update the title of the issue | +| `body` | `string` | No | Update the body content of the issue (supports Markdown) | +| `state` | `'open' \| 'closed'` | No | Change the state of the issue | +| `state_reason` | `'completed' \| 'not_planned' \| 'reopened'` | No | Reason for state change (when closing/reopening) | +| `labels` | `string[]` | No | Replace all labels with this array | +| `assignees` | `string[]` | No | Replace all assignees with this array | +| `milestone` | `number \| null` | No | Set milestone by number, or null to remove | + +### State Reasons + +- **`completed`**: Issue was resolved/fixed +- **`not_planned`**: Issue is valid but won't be addressed +- **`reopened`**: Previously closed issue is being reopened + +## Return Value + +### IssueResult + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `number` | The unique identifier of the issue | +| `number` | `number` | The issue number | +| `title` | `string` | The updated title of the issue | +| `body` | `string \| null` | The updated body content | +| `state` | `'open' \| 'closed'` | The current state of the issue | +| `state_reason` | `'completed' \| 'not_planned' \| 'reopened' \| null` | The reason for the current state | +| `html_url` | `string` | The URL to view the issue on GitHub | +| `user` | `object` | Information about the original issue creator | +| `labels` | `array` | Array of labels currently applied to the issue | +| `assignees` | `array` | Array of users currently assigned to the issue | +| `created_at` | `string` | ISO timestamp when the issue was created | +| `updated_at` | `string` | ISO timestamp when the issue was last updated | +| `closed_at` | `string` | ISO timestamp when the issue was closed (if applicable) | +| `comments` | `number` | Number of comments on the issue | +| `milestone` | `object` | Current milestone information (if associated) | + +## Prerequisites + +Before using this method, ensure: + +1. **GitHub Configuration**: You only need to configure GitHub credentials: + ```typescript + vibekit.withGithub({ + token: "your-github-token", + repository: "owner/repo" + }) + ``` + Note: Unlike code generation methods, `updateIssue` does NOT require agent or sandbox configuration. + +2. **Issue Existence**: The issue must exist in the specified repository + +3. **Permissions**: The GitHub token must have appropriate repository permissions + +## Usage Examples + +### Close an Issue + +```typescript +import { VibeKit } from "vibekit"; + +const vibekit = new VibeKit() + .withGithub({ + token: process.env.GITHUB_TOKEN!, + repository: "myorg/myrepo" + }); + +// Close issue as completed +const updatedIssue = await vibekit.updateIssue(42, { + state: "closed", + state_reason: "completed" +}); + +console.log(`Issue #${updatedIssue.number} closed: ${updatedIssue.html_url}`); +``` + +### Update Issue Content + +```typescript +// Update title and body +const updatedIssue = await vibekit.updateIssue(42, { + title: "Updated: Login form validation improvements", + body: `## Problem +The login form validation has been improved. + +## Solution +- Added real-time validation +- Improved error messages +- Added accessibility features + +## Status +Ready for testing in staging environment.` +}); + +console.log(`Issue updated: ${updatedIssue.title}`); +``` + +### Manage Labels and Assignees + +```typescript +// Update labels and assignees +const updatedIssue = await vibekit.updateIssue(42, { + labels: ["bug", "priority-high", "in-progress"], + assignees: ["developer1", "qa-tester"] +}); + +console.log(`Labels: ${updatedIssue.labels.map(l => l.name).join(', ')}`); +console.log(`Assignees: ${updatedIssue.assignees.map(a => a.login).join(', ')}`); +``` + +### Complete Workflow Examples + +```typescript +// Example 1: Close multiple issues after PR merge +async function closePRIssues(prNumber: number, relatedIssues: number[]) { + for (const issueNumber of relatedIssues) { + try { + await vibekit.updateIssue(issueNumber, { + state: "closed", + state_reason: "completed", + body: await getCurrentIssueBody(issueNumber) + `\n\n---\nClosed by PR #${prNumber}` + }); + console.log(`Closed issue #${issueNumber}`); + } catch (error) { + console.error(`Failed to close issue #${issueNumber}:`, error.message); + } + } +} + +// Example 2: Reopen and reassign issue +async function escalateIssue(issueNumber: number, assignee: string) { + const updatedIssue = await vibekit.updateIssue(issueNumber, { + state: "open", + state_reason: "reopened", + assignees: [assignee], + labels: ["priority-urgent", "escalated"], + body: await getCurrentIssueBody(issueNumber) + `\n\n---\n**Issue escalated and reassigned to @${assignee}**` + }); + + return updatedIssue; +} + +// Example 3: Progressive issue management +async function progressIssue(issueNumber: number, stage: 'triage' | 'in-progress' | 'review' | 'done') { + const stageConfig = { + 'triage': { + labels: ["needs-triage"], + assignees: ["project-manager"] + }, + 'in-progress': { + labels: ["in-progress"], + assignees: ["developer"] + }, + 'review': { + labels: ["needs-review", "in-progress"], + assignees: ["reviewer", "developer"] + }, + 'done': { + state: "closed" as const, + state_reason: "completed" as const, + labels: ["completed"] + } + }; + + return await vibekit.updateIssue(issueNumber, stageConfig[stage]); +} +``` + +### Bulk Operations + +```typescript +// Update multiple issues with same labels +async function bulkLabelIssues(issueNumbers: number[], labels: string[]) { + const results = await Promise.allSettled( + issueNumbers.map(num => + vibekit.updateIssue(num, { labels }) + ) + ); + + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + console.log(`Updated issue #${issueNumbers[index]}`); + } else { + console.error(`Failed to update issue #${issueNumbers[index]}:`, result.reason); + } + }); +} + +// Close stale issues +async function closeStaleIssues(issueNumbers: number[]) { + for (const issueNumber of issueNumbers) { + try { + await vibekit.updateIssue(issueNumber, { + state: "closed", + state_reason: "not_planned", + labels: ["stale", "auto-closed"], + body: await getCurrentIssueBody(issueNumber) + `\n\n---\n**Auto-closed due to inactivity**` + }); + } catch (error) { + console.error(`Failed to close stale issue #${issueNumber}:`, error.message); + } + } +} +``` + +### Integration with Code Generation + +```typescript +// Update issue after successful code generation +async function updateIssueAfterCodegen(issueNumber: number, branchName: string) { + const updatedIssue = await vibekit.updateIssue(issueNumber, { + labels: ["in-progress", "code-generated"], + body: await getCurrentIssueBody(issueNumber) + `\n\n---\n## Progress Update +Code has been generated and pushed to branch: \`${branchName}\` + +**Next Steps:** +- [ ] Review generated code +- [ ] Test functionality +- [ ] Create pull request +- [ ] Close issue after merge` + }); + + return updatedIssue; +} +``` + +## Error Handling + +### Common Error Scenarios + +```typescript +// Handle different error cases +try { + const updatedIssue = await vibekit.updateIssue(999, { + state: "closed", + state_reason: "completed" + }); +} catch (error) { + if (error.message.includes('not found')) { + console.log('Issue does not exist'); + } else if (error.message.includes('locked')) { + console.log('Issue is locked and cannot be updated'); + } else if (error.message.includes('Invalid update parameters')) { + console.log('Check labels, assignees, and other parameters'); + } else { + console.log('Unexpected error:', error.message); + } +} +``` + +## Notes + +- The method requires only GitHub configuration - no agent or sandbox setup needed +- All fields in `UpdateIssueOptions` are optional - only specify fields you want to change +- Labels array replaces all existing labels - include existing labels if you want to keep them +- Assignees array replaces all existing assignees - include existing assignees if you want to keep them +- Set `milestone` to `null` to remove the milestone from an issue +- State changes to 'closed' should include an appropriate `state_reason` +- The updated timestamp is automatically set by GitHub +- Locked issues cannot be updated and will return a 410 error +- The response includes the complete updated issue data from GitHub's API \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json index e01233da..71e34d9b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -73,6 +73,8 @@ "api-reference/create-pull-request", "api-reference/merge-pull-request", "api-reference/push-to-branch", + "api-reference/create-issue", + "api-reference/update-issue", "api-reference/run-tests", "api-reference/kill-sandbox", "api-reference/get-host", diff --git a/docs/sdk/github-integration.mdx b/docs/sdk/github-integration.mdx index 05e017b9..8aeb362f 100644 --- a/docs/sdk/github-integration.mdx +++ b/docs/sdk/github-integration.mdx @@ -118,4 +118,154 @@ const squashResult = await vibekit.mergePullRequest({ - **`squash`**: Squashes all commits into a single commit before merging - **`rebase`**: Rebases the commits onto the base branch -This comprehensive GitHub integration allows you to build powerful conversational UIs where users can iteratively request code changes, see them applied in real-time, create pull requests, and merge them programmatically when they're satisfied with the results. \ No newline at end of file +## Issue Management + +VibeKit provides comprehensive GitHub Issues integration for automated project management. You can create issues, update them, close them, and manage labels and assignees - all programmatically. + +### Creating Issues + +Create issues programmatically for bug reports, feature requests, or automated project management: + +```typescript +// Create a simple issue +const issue = await vibekit.createIssue({ + title: "Bug: Login form validation error", + body: "The login form shows incorrect validation messages when email is empty." +}); + +console.log(`Issue created: ${issue.html_url}`); +console.log(`Issue #${issue.number}: ${issue.title}`); +``` + +### Creating Issues with Labels and Assignees + +```typescript +// Create issue with full options +const featureRequest = await vibekit.createIssue({ + title: "Feature Request: Add dark mode support", + body: `## Description +Add dark mode toggle to the application. + +## Requirements +- [ ] Toggle button in settings +- [ ] Dark theme CSS variables +- [ ] Persistent user preference + +## Acceptance Criteria +- User can switch between light and dark modes +- Theme preference persists across sessions`, + labels: ["enhancement", "ui", "good first issue"], + assignees: ["frontend-dev", "ux-designer"], + milestone: 5 +}); +``` + +### Updating Issues + +Update any aspect of existing issues, including closing, reopening, or modifying content: + +```typescript +// Close an issue as completed +const updatedIssue = await vibekit.updateIssue(42, { + state: "closed", + state_reason: "completed" +}); + +// Update issue content and labels +await vibekit.updateIssue(42, { + title: "Updated: Login form validation improvements", + labels: ["bug", "priority-high", "in-progress"], + assignees: ["developer1", "qa-tester"] +}); + +// Reopen and escalate an issue +await vibekit.updateIssue(42, { + state: "open", + state_reason: "reopened", + labels: ["priority-urgent", "escalated"] +}); +``` + +### Issue Workflow Integration + +Combine issues with code generation and PR workflows: + +```typescript +// 1. Create issue for new feature +const issue = await vibekit.createIssue({ + title: "Add user profile page", + body: "Create a user profile page with editable fields", + labels: ["enhancement"], + assignees: ["developer1"] +}); + +// 2. Generate code for the feature +const codeResult = await vibeKit.generateCode({ + prompt: "Create a user profile page component with editable name, email, and bio fields", + mode: "code" +}); + +// 3. Create PR for the generated code +const pr = await vibeKit.createPullRequest({ + name: "feature", + color: "0366d6", + description: "New feature implementation" +}, "feature"); + +// 4. Update issue with progress +await vibekit.updateIssue(issue.number, { + labels: ["enhancement", "in-progress", "code-generated"], + body: issue.body + `\n\n---\n## Progress Update +Code has been generated and PR created: ${pr.html_url} + +**Next Steps:** +- [ ] Review generated code +- [ ] Test functionality +- [ ] Merge PR +- [ ] Close issue` +}); + +// 5. After PR is merged, close the issue +await vibekit.updateIssue(issue.number, { + state: "closed", + state_reason: "completed", + body: issue.body + `\n\n---\nCompleted in PR #${pr.number}` +}); +``` + +### Automated Issue Management + +Use issues for automated project management workflows: + +```typescript +// Bulk close stale issues +async function closeStaleIssues(staleIssueNumbers: number[]) { + for (const issueNumber of staleIssueNumbers) { + await vibekit.updateIssue(issueNumber, { + state: "closed", + state_reason: "not_planned", + labels: ["stale", "auto-closed"] + }); + } +} + +// Create issues from code generation errors +async function reportGenerationError(errorDetails: string) { + return await vibekit.createIssue({ + title: "AI Code Generation Error", + body: `## Error Details\n${errorDetails}\n\n## Context\nThis issue was automatically created during AI code generation.`, + labels: ["bug", "ai-generated", "priority-high"], + assignees: ["tech-lead"] + }); +} + +// Progressive issue management +async function moveIssueToReview(issueNumber: number) { + return await vibekit.updateIssue(issueNumber, { + labels: ["needs-review", "in-progress"], + assignees: ["reviewer", "developer"] + }); +} +``` + +This comprehensive GitHub integration allows you to build powerful conversational UIs where users can iteratively request code changes, see them applied in real-time, create pull requests and issues, and manage the entire development workflow programmatically. \ No newline at end of file diff --git a/packages/sdk/src/core/vibekit.ts b/packages/sdk/src/core/vibekit.ts index e551a109..16aea22f 100644 --- a/packages/sdk/src/core/vibekit.ts +++ b/packages/sdk/src/core/vibekit.ts @@ -8,6 +8,9 @@ import type { LabelOptions, MergePullRequestOptions, MergePullRequestResult, + CreateIssueOptions, + UpdateIssueOptions, + IssueResult, } from "../types"; import { AGENT_TYPES } from "../constants/agents"; import { AgentResponse, ExecuteCommandOptions, PullRequestResult } from "../agents/base"; @@ -248,6 +251,129 @@ export class VibeKit extends EventEmitter { }; } + async createIssue(options: CreateIssueOptions): Promise { + const { github } = this.options; + + if (!github?.token || !github?.repository) { + throw new Error( + "GitHub configuration is required for creating issues. Please use withGithub() to configure GitHub credentials." + ); + } + + if (!options.title) { + throw new Error("Issue title is required"); + } + + const [owner, repo] = github.repository?.split("/") || []; + + if (!owner || !repo) { + throw new Error("Invalid repository URL format. Expected format: owner/repo"); + } + + // Create the issue using GitHub API + const createResponse = await fetch( + `https://api.github.com/repos/${owner}/${repo}/issues`, + { + method: "POST", + headers: { + Authorization: `token ${github.token}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title: options.title, + body: options.body, + labels: options.labels, + assignees: options.assignees, + milestone: options.milestone, + }), + } + ); + + const responseData = await createResponse.json(); + + if (!createResponse.ok) { + // Handle specific error cases + if (createResponse.status === 404) { + throw new Error(`Repository ${github.repository} not found`); + } else if (createResponse.status === 422) { + throw new Error(`Invalid issue parameters: ${responseData.message || 'Unknown validation error'}`); + } else { + throw new Error( + `Failed to create issue: ${createResponse.status} ${responseData.message || createResponse.statusText}` + ); + } + } + + return responseData as IssueResult; + } + + async updateIssue( + issueNumber: number, + options: UpdateIssueOptions + ): Promise { + const { github } = this.options; + + if (!github?.token || !github?.repository) { + throw new Error( + "GitHub configuration is required for updating issues. Please use withGithub() to configure GitHub credentials." + ); + } + + if (!issueNumber || typeof issueNumber !== 'number') { + throw new Error("Issue number is required and must be a number"); + } + + const [owner, repo] = github.repository?.split("/") || []; + + if (!owner || !repo) { + throw new Error("Invalid repository URL format. Expected format: owner/repo"); + } + + // Update the issue using GitHub API + const updateResponse = await fetch( + `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`, + { + method: "PATCH", + headers: { + Authorization: `token ${github.token}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title: options.title, + body: options.body, + state: options.state, + state_reason: options.state_reason, + labels: options.labels, + assignees: options.assignees, + milestone: options.milestone, + }), + } + ); + + const responseData = await updateResponse.json(); + + if (!updateResponse.ok) { + // Handle specific error cases + if (updateResponse.status === 404) { + throw new Error(`Issue #${issueNumber} not found in ${github.repository}`); + } else if (updateResponse.status === 422) { + throw new Error(`Invalid update parameters: ${responseData.message || 'Unknown validation error'}`); + } else if (updateResponse.status === 410) { + throw new Error(`Issue #${issueNumber} is locked and cannot be updated`); + } else { + throw new Error( + `Failed to update issue #${issueNumber}: ${updateResponse.status} ${responseData.message || updateResponse.statusText}` + ); + } + } + + return responseData as IssueResult; + } + async runTests(): Promise { if (!this.agent) { await this.initializeAgent(); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 3802e4bf..cf089a8c 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -21,6 +21,9 @@ export type { LabelOptions, MergePullRequestOptions, MergePullRequestResult, + CreateIssueOptions, + UpdateIssueOptions, + IssueResult, CodexStreamCallbacks, ClaudeStreamCallbacks, OpenCodeStreamCallbacks, diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 335f6f84..d161f9f8 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -103,6 +103,59 @@ export interface MergePullRequestResult { message: string; } +// GITHUB ISSUES +// Issue creation options +export interface CreateIssueOptions { + title: string; + body?: string; + labels?: string[]; + assignees?: string[]; + milestone?: number; +} + +// Issue update options +export interface UpdateIssueOptions { + title?: string; + body?: string; + state?: 'open' | 'closed'; + state_reason?: 'completed' | 'not_planned' | 'reopened'; + labels?: string[]; + assignees?: string[]; + milestone?: number | null; +} + +// Issue result type +export interface IssueResult { + id: number; + number: number; + title: string; + body: string | null; + state: 'open' | 'closed'; + state_reason: 'completed' | 'not_planned' | 'reopened' | null; + html_url: string; + user: { + login: string; + avatar_url: string; + }; + labels: Array<{ + name: string; + color: string; + description?: string; + }>; + assignees: Array<{ + login: string; + avatar_url: string; + }>; + created_at: string; + updated_at: string; + closed_at?: string; + comments: number; + milestone?: { + number: number; + title: string; + }; +} + // STREAMING CALLBACKS export interface CodexStreamCallbacks { onUpdate?: (message: string) => void;