mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
MAESTRO: Complete Symphony Phase 6 - Registry & GitHub Integration
Add Symphony registry documentation and contribution runner service: - docs/SYMPHONY_REGISTRY.md: Registry documentation explaining how maintainers register repos - docs/SYMPHONY_ISSUES.md: Guide for creating Symphony issues with Auto Run documents - src/main/services/symphony-runner.ts: Service orchestrating contributions with draft PR claiming - symphony-registry.json: Sample registry with Maestro as the first registered project The runner service handles the full contribution flow: clone, branch, push, draft PR creation, Auto Run setup, finalization, and cancellation.
This commit is contained in:
176
docs/SYMPHONY_ISSUES.md
Normal file
176
docs/SYMPHONY_ISSUES.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Creating Symphony Issues
|
||||||
|
|
||||||
|
Maintainers create GitHub Issues to define contribution opportunities for the Symphony community.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Symphony issues are standard GitHub issues with the `runmaestro.ai` label. The issue body contains paths to Auto Run documents that define the work to be done. When a contributor starts working on an issue, a draft PR is automatically created to claim it.
|
||||||
|
|
||||||
|
## Issue Requirements
|
||||||
|
|
||||||
|
1. **Label**: Add the `runmaestro.ai` label to the issue
|
||||||
|
2. **Title**: Clear description of the contribution (e.g., "Add unit tests for user module")
|
||||||
|
3. **Body**: List the Auto Run document paths (one per line)
|
||||||
|
|
||||||
|
## Issue Body Format
|
||||||
|
|
||||||
|
Simply list the paths to your Auto Run documents:
|
||||||
|
|
||||||
|
```
|
||||||
|
.maestro/autorun/add-user-tests.md
|
||||||
|
.maestro/autorun/add-user-tests-2.md
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! No special formatting required. The system will:
|
||||||
|
- Parse the `.md` file paths from the issue body
|
||||||
|
- Clone your repository when a contributor starts
|
||||||
|
- Run each document in sequence via Auto Run
|
||||||
|
- Create a PR with all changes
|
||||||
|
|
||||||
|
### Supported Path Formats
|
||||||
|
|
||||||
|
The following formats are recognized:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Bare paths (recommended)
|
||||||
|
.maestro/autorun/task-1.md
|
||||||
|
.maestro/autorun/task-2.md
|
||||||
|
|
||||||
|
# Markdown list items
|
||||||
|
- .maestro/autorun/task-1.md
|
||||||
|
- `.maestro/autorun/task-2.md`
|
||||||
|
|
||||||
|
# Numbered lists
|
||||||
|
1. .maestro/autorun/task-1.md
|
||||||
|
2. .maestro/autorun/task-2.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Issue
|
||||||
|
|
||||||
|
**Title**: Add comprehensive tests for the authentication module
|
||||||
|
|
||||||
|
**Labels**: `runmaestro.ai`
|
||||||
|
|
||||||
|
**Body**:
|
||||||
|
```markdown
|
||||||
|
Add test coverage for the authentication module.
|
||||||
|
|
||||||
|
Documents to process:
|
||||||
|
.maestro/autorun/auth-unit-tests.md
|
||||||
|
.maestro/autorun/auth-integration-tests.md
|
||||||
|
.maestro/autorun/auth-e2e-tests.md
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The `src/auth/` module currently has low test coverage. These documents will guide the AI to add comprehensive tests following our existing patterns.
|
||||||
|
|
||||||
|
## Expected Outcome
|
||||||
|
|
||||||
|
- Unit tests for all public functions
|
||||||
|
- Integration tests for auth flow
|
||||||
|
- E2E tests for login/logout
|
||||||
|
|
||||||
|
Estimated time: ~45 minutes of AI agent time.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auto Run Document Format
|
||||||
|
|
||||||
|
Each `.md` file should be a complete Auto Run document:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Task: Add Unit Tests for Auth Module
|
||||||
|
|
||||||
|
## Context
|
||||||
|
The authentication module at `src/auth/` needs test coverage.
|
||||||
|
|
||||||
|
## Objectives
|
||||||
|
- [ ] Create `src/__tests__/auth.test.ts`
|
||||||
|
- [ ] Add tests for `login()` function
|
||||||
|
- [ ] Add tests for `logout()` function
|
||||||
|
- [ ] Add tests for `refreshToken()` function
|
||||||
|
- [ ] Ensure `npm test` passes
|
||||||
|
- [ ] Verify coverage > 80%
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Use Jest testing framework
|
||||||
|
- Follow existing test patterns in the codebase
|
||||||
|
- Do not modify production code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Document Best Practices
|
||||||
|
|
||||||
|
1. **Small, focused tasks**: Each document should be ~30-60 minutes of AI time
|
||||||
|
2. **Clear objectives**: Use checkboxes (`- [ ]`) for verification steps
|
||||||
|
3. **Provide context**: Include file paths, existing patterns, constraints
|
||||||
|
4. **Verification steps**: Include test commands, linting checks
|
||||||
|
5. **Independence**: Each document should be self-contained
|
||||||
|
|
||||||
|
## Issue Availability
|
||||||
|
|
||||||
|
An issue is **available** for contribution when:
|
||||||
|
- It has the `runmaestro.ai` label
|
||||||
|
- It is **open** (not closed)
|
||||||
|
- There is **no open PR** with "Closes #N" in the body
|
||||||
|
|
||||||
|
When a contributor starts working on an issue, a draft PR is immediately created with "Closes #N" in the body. This claims the issue and prevents duplicate work.
|
||||||
|
|
||||||
|
### Claim Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Contributor clicks "Start Symphony" on an issue
|
||||||
|
2. Repository is cloned locally
|
||||||
|
3. A new branch is created (symphony/issue-{number}-{timestamp})
|
||||||
|
4. An empty commit is made
|
||||||
|
5. The branch is pushed to origin
|
||||||
|
6. A draft PR is created with "Closes #{issue}" in the body
|
||||||
|
7. Auto Run begins processing documents
|
||||||
|
8. When complete, contributor clicks "Finalize PR"
|
||||||
|
9. Draft PR is converted to "Ready for Review"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating Good Issues
|
||||||
|
|
||||||
|
### Do
|
||||||
|
|
||||||
|
- ✅ Break large tasks into multiple smaller issues
|
||||||
|
- ✅ Include all necessary context in the documents
|
||||||
|
- ✅ Provide clear acceptance criteria
|
||||||
|
- ✅ Estimate the expected time/complexity
|
||||||
|
- ✅ Link to relevant documentation or examples
|
||||||
|
|
||||||
|
### Don't
|
||||||
|
|
||||||
|
- ❌ Create issues that require human judgment calls
|
||||||
|
- ❌ Include tasks that need external credentials/access
|
||||||
|
- ❌ Bundle unrelated tasks in a single issue
|
||||||
|
- ❌ Assume contributors know your codebase intimately
|
||||||
|
- ❌ Create documents with ambiguous requirements
|
||||||
|
|
||||||
|
## Example Document Structure
|
||||||
|
|
||||||
|
For complex tasks, organize your documents like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
.maestro/autorun/
|
||||||
|
├── feature-1-setup.md # First: Set up files/structure
|
||||||
|
├── feature-1-implement.md # Second: Implement the feature
|
||||||
|
├── feature-1-tests.md # Third: Add tests
|
||||||
|
└── feature-1-docs.md # Fourth: Update documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
Each document builds on the previous one, and contributors can see the full scope in the issue body.
|
||||||
|
|
||||||
|
## Monitoring Contributions
|
||||||
|
|
||||||
|
As a maintainer:
|
||||||
|
|
||||||
|
1. You'll receive a GitHub notification when a draft PR is created
|
||||||
|
2. Watch the PR for progress as the contributor works
|
||||||
|
3. Review and provide feedback once the PR is ready
|
||||||
|
4. Merge when satisfied
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
- See [SYMPHONY_REGISTRY.md](SYMPHONY_REGISTRY.md) for registry information
|
||||||
|
- Check the [Maestro documentation](https://docs.runmaestro.ai) for Auto Run guides
|
||||||
|
- Open an issue on the Maestro repository for support
|
||||||
158
docs/SYMPHONY_REGISTRY.md
Normal file
158
docs/SYMPHONY_REGISTRY.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# Maestro Symphony Registry
|
||||||
|
|
||||||
|
The central registry for open source projects participating in Symphony.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Symphony connects open source maintainers with AI-powered contributors. Maintainers register their repositories, create Auto Run documents, and open GitHub Issues with the `runmaestro.ai` label. Contributors browse available tasks and complete them via Maestro's Auto Run feature.
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
The registry lives in the main Maestro repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
pedramamini/Maestro/
|
||||||
|
├── symphony-registry.json # Central list of all projects
|
||||||
|
└── docs/
|
||||||
|
└── SYMPHONY_REGISTRY.md # This documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## symphony-registry.json Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": "1.0",
|
||||||
|
"lastUpdated": "2025-01-01T00:00:00Z",
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"slug": "owner/repo-name",
|
||||||
|
"name": "Human Readable Name",
|
||||||
|
"description": "Short description of the project",
|
||||||
|
"url": "https://github.com/owner/repo-name",
|
||||||
|
"category": "developer-tools",
|
||||||
|
"tags": ["cli", "productivity"],
|
||||||
|
"maintainer": {
|
||||||
|
"name": "Name",
|
||||||
|
"url": "https://..."
|
||||||
|
},
|
||||||
|
"isActive": true,
|
||||||
|
"featured": false,
|
||||||
|
"addedAt": "2025-01-01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Field Reference
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `slug` | string | Yes | Repository identifier in `owner/repo` format |
|
||||||
|
| `name` | string | Yes | Human-readable project name |
|
||||||
|
| `description` | string | Yes | Short description (max 200 chars) |
|
||||||
|
| `url` | string | Yes | Full GitHub repository URL |
|
||||||
|
| `category` | string | Yes | Primary category (see Categories below) |
|
||||||
|
| `tags` | string[] | No | Optional tags for search/filtering |
|
||||||
|
| `maintainer.name` | string | Yes | Maintainer or organization name |
|
||||||
|
| `maintainer.url` | string | No | Optional link to maintainer profile |
|
||||||
|
| `isActive` | boolean | Yes | Whether repo is accepting contributions |
|
||||||
|
| `featured` | boolean | No | Show in featured section (default: false) |
|
||||||
|
| `addedAt` | string | Yes | ISO 8601 date when registered |
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Maintainers register once** by submitting a PR to add their repo to `symphony-registry.json`
|
||||||
|
2. **Maintainers create Auto Run documents** in their repository (e.g., `.maestro/autorun/`)
|
||||||
|
3. **Maintainers open GitHub Issues** with the `runmaestro.ai` label, listing document paths
|
||||||
|
4. **Contributors browse** available issues in Maestro Symphony
|
||||||
|
5. **One-click contribution** clones the repo, creates a draft PR (claiming the issue), and runs Auto Run
|
||||||
|
6. **Finalize PR** when all documents are processed
|
||||||
|
7. **Maintainer reviews** and merges the contribution
|
||||||
|
|
||||||
|
## Categories
|
||||||
|
|
||||||
|
| ID | Label | Use Case |
|
||||||
|
|----|-------|----------|
|
||||||
|
| `ai-ml` | AI & ML | AI/ML tools and libraries |
|
||||||
|
| `developer-tools` | Developer Tools | Developer productivity tools |
|
||||||
|
| `infrastructure` | Infrastructure | DevOps, cloud, infrastructure |
|
||||||
|
| `documentation` | Documentation | Documentation projects |
|
||||||
|
| `web` | Web | Web frameworks and libraries |
|
||||||
|
| `mobile` | Mobile | Mobile development |
|
||||||
|
| `data` | Data | Data processing, databases |
|
||||||
|
| `security` | Security | Security tools |
|
||||||
|
| `other` | Other | Miscellaneous projects |
|
||||||
|
|
||||||
|
## Registering a Repository
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Before registering, ensure your repository:
|
||||||
|
|
||||||
|
- Has a clear README explaining the project
|
||||||
|
- Has contribution guidelines (CONTRIBUTING.md)
|
||||||
|
- Uses a license compatible with open source (MIT, Apache 2.0, etc.)
|
||||||
|
- Has at least one Auto Run document ready
|
||||||
|
|
||||||
|
### Registration Steps
|
||||||
|
|
||||||
|
1. **Fork** the `pedramamini/Maestro` repository
|
||||||
|
2. **Add your entry** to `symphony-registry.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"slug": "your-org/your-repo",
|
||||||
|
"name": "Your Project Name",
|
||||||
|
"description": "Brief description of your project",
|
||||||
|
"url": "https://github.com/your-org/your-repo",
|
||||||
|
"category": "developer-tools",
|
||||||
|
"tags": ["typescript", "cli"],
|
||||||
|
"maintainer": {
|
||||||
|
"name": "Your Name",
|
||||||
|
"url": "https://github.com/your-username"
|
||||||
|
},
|
||||||
|
"isActive": true,
|
||||||
|
"featured": false,
|
||||||
|
"addedAt": "2025-01-15"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Submit a PR** with your repository details
|
||||||
|
4. Once merged, **create issues** with the `runmaestro.ai` label to enable contributions
|
||||||
|
|
||||||
|
### After Registration
|
||||||
|
|
||||||
|
Once your repository is in the registry:
|
||||||
|
|
||||||
|
1. Create a `.maestro/autorun/` directory in your repo (optional, but recommended)
|
||||||
|
2. Write Auto Run documents for contribution tasks
|
||||||
|
3. Open GitHub Issues with the `runmaestro.ai` label
|
||||||
|
4. List the document paths in the issue body
|
||||||
|
|
||||||
|
See [SYMPHONY_ISSUES.md](SYMPHONY_ISSUES.md) for detailed issue formatting guidelines.
|
||||||
|
|
||||||
|
## Updating Your Entry
|
||||||
|
|
||||||
|
To update your registry entry (e.g., change category, update description):
|
||||||
|
|
||||||
|
1. Submit a PR modifying your entry in `symphony-registry.json`
|
||||||
|
2. Keep your `slug` unchanged to maintain history
|
||||||
|
|
||||||
|
## Removing Your Repository
|
||||||
|
|
||||||
|
To remove your repository from Symphony:
|
||||||
|
|
||||||
|
1. Set `isActive: false` in your registry entry, OR
|
||||||
|
2. Submit a PR removing your entry entirely
|
||||||
|
|
||||||
|
Note: Setting `isActive: false` hides your repo from the contributor UI but preserves contribution history.
|
||||||
|
|
||||||
|
## Registry Caching
|
||||||
|
|
||||||
|
The Symphony client caches the registry for 2 hours to reduce API calls. Changes to the registry may take up to 2 hours to propagate to all users.
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
- See [SYMPHONY_ISSUES.md](SYMPHONY_ISSUES.md) for issue formatting
|
||||||
|
- Check the [Maestro documentation](https://docs.runmaestro.ai) for Auto Run guides
|
||||||
|
- Open an issue on the Maestro repository for support
|
||||||
290
src/main/services/symphony-runner.ts
Normal file
290
src/main/services/symphony-runner.ts
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/**
|
||||||
|
* Symphony Runner Service
|
||||||
|
*
|
||||||
|
* Orchestrates contributions using Auto Run with draft PR claiming.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
import { execFileNoThrow } from '../utils/execFile';
|
||||||
|
// Types imported for documentation and future use
|
||||||
|
// import type { ActiveContribution, SymphonyIssue } from '../../shared/symphony-types';
|
||||||
|
|
||||||
|
const LOG_CONTEXT = '[SymphonyRunner]';
|
||||||
|
|
||||||
|
export interface SymphonyRunnerOptions {
|
||||||
|
contributionId: string;
|
||||||
|
repoSlug: string;
|
||||||
|
repoUrl: string;
|
||||||
|
issueNumber: number;
|
||||||
|
issueTitle: string;
|
||||||
|
documentPaths: string[];
|
||||||
|
localPath: string;
|
||||||
|
branchName: string;
|
||||||
|
onProgress?: (progress: { completedDocuments: number; totalDocuments: number }) => void;
|
||||||
|
onStatusChange?: (status: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone repository to local path (shallow clone for speed).
|
||||||
|
*/
|
||||||
|
async function cloneRepo(repoUrl: string, localPath: string): Promise<boolean> {
|
||||||
|
logger.info('Cloning repository', LOG_CONTEXT, { repoUrl, localPath });
|
||||||
|
const result = await execFileNoThrow('git', ['clone', '--depth=1', repoUrl, localPath]);
|
||||||
|
return result.exitCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and checkout a new branch.
|
||||||
|
*/
|
||||||
|
async function createBranch(localPath: string, branchName: string): Promise<boolean> {
|
||||||
|
const result = await execFileNoThrow('git', ['checkout', '-b', branchName], localPath);
|
||||||
|
return result.exitCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty commit to enable pushing without changes.
|
||||||
|
*/
|
||||||
|
async function createEmptyCommit(localPath: string, message: string): Promise<boolean> {
|
||||||
|
const result = await execFileNoThrow('git', ['commit', '--allow-empty', '-m', message], localPath);
|
||||||
|
return result.exitCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push branch to origin.
|
||||||
|
*/
|
||||||
|
async function pushBranch(localPath: string, branchName: string): Promise<boolean> {
|
||||||
|
const result = await execFileNoThrow('git', ['push', '-u', 'origin', branchName], localPath);
|
||||||
|
return result.exitCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a draft PR using GitHub CLI.
|
||||||
|
*/
|
||||||
|
async function createDraftPR(
|
||||||
|
localPath: string,
|
||||||
|
issueNumber: number,
|
||||||
|
issueTitle: string
|
||||||
|
): Promise<{ success: boolean; prUrl?: string; prNumber?: number; error?: string }> {
|
||||||
|
const title = `[WIP] Symphony: ${issueTitle}`;
|
||||||
|
const body = `## Symphony Contribution
|
||||||
|
|
||||||
|
This draft PR was created via Maestro Symphony.
|
||||||
|
|
||||||
|
Closes #${issueNumber}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Work in progress - will be updated when Auto Run completes*`;
|
||||||
|
|
||||||
|
const result = await execFileNoThrow(
|
||||||
|
'gh',
|
||||||
|
['pr', 'create', '--draft', '--title', title, '--body', body],
|
||||||
|
localPath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
return { success: false, error: `PR creation failed: ${result.stderr}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const prUrl = result.stdout.trim();
|
||||||
|
const prNumberMatch = prUrl.match(/\/pull\/(\d+)/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
prUrl,
|
||||||
|
prNumber: prNumberMatch ? parseInt(prNumberMatch[1], 10) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy Auto Run documents from repo to local Auto Run Docs folder.
|
||||||
|
*/
|
||||||
|
async function setupAutoRunDocs(
|
||||||
|
localPath: string,
|
||||||
|
documentPaths: string[]
|
||||||
|
): Promise<string> {
|
||||||
|
const autoRunPath = path.join(localPath, 'Auto Run Docs');
|
||||||
|
await execFileNoThrow('mkdir', ['-p', autoRunPath]);
|
||||||
|
|
||||||
|
for (const docPath of documentPaths) {
|
||||||
|
const sourcePath = path.join(localPath, docPath);
|
||||||
|
const destPath = path.join(autoRunPath, path.basename(docPath));
|
||||||
|
await execFileNoThrow('cp', [sourcePath, destPath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoRunPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a Symphony contribution.
|
||||||
|
*
|
||||||
|
* Flow:
|
||||||
|
* 1. Clone the repository (shallow)
|
||||||
|
* 2. Create a new branch
|
||||||
|
* 3. Create an empty commit
|
||||||
|
* 4. Push the branch
|
||||||
|
* 5. Create a draft PR (claims the issue via "Closes #N")
|
||||||
|
* 6. Set up Auto Run documents
|
||||||
|
*/
|
||||||
|
export async function startContribution(options: SymphonyRunnerOptions): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
draftPrUrl?: string;
|
||||||
|
draftPrNumber?: number;
|
||||||
|
autoRunPath?: string;
|
||||||
|
error?: string;
|
||||||
|
}> {
|
||||||
|
const {
|
||||||
|
repoUrl,
|
||||||
|
localPath,
|
||||||
|
branchName,
|
||||||
|
issueNumber,
|
||||||
|
issueTitle,
|
||||||
|
documentPaths,
|
||||||
|
onStatusChange,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Clone
|
||||||
|
onStatusChange?.('cloning');
|
||||||
|
if (!await cloneRepo(repoUrl, localPath)) {
|
||||||
|
return { success: false, error: 'Clone failed' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create branch
|
||||||
|
onStatusChange?.('setting_up');
|
||||||
|
if (!await createBranch(localPath, branchName)) {
|
||||||
|
return { success: false, error: 'Branch creation failed' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Empty commit
|
||||||
|
const commitMessage = `[Symphony] Start contribution for #${issueNumber}`;
|
||||||
|
if (!await createEmptyCommit(localPath, commitMessage)) {
|
||||||
|
return { success: false, error: 'Empty commit failed' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Push branch
|
||||||
|
if (!await pushBranch(localPath, branchName)) {
|
||||||
|
return { success: false, error: 'Push failed' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Create draft PR
|
||||||
|
const prResult = await createDraftPR(localPath, issueNumber, issueTitle);
|
||||||
|
if (!prResult.success) {
|
||||||
|
return { success: false, error: prResult.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Setup Auto Run docs
|
||||||
|
const autoRunPath = await setupAutoRunDocs(localPath, documentPaths);
|
||||||
|
|
||||||
|
// Ready - actual Auto Run processing happens via session
|
||||||
|
onStatusChange?.('running');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
draftPrUrl: prResult.prUrl,
|
||||||
|
draftPrNumber: prResult.prNumber,
|
||||||
|
autoRunPath,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize a contribution by converting draft PR to ready for review.
|
||||||
|
*/
|
||||||
|
export async function finalizeContribution(
|
||||||
|
localPath: string,
|
||||||
|
prNumber: number,
|
||||||
|
issueNumber: number,
|
||||||
|
issueTitle: string
|
||||||
|
): Promise<{ success: boolean; prUrl?: string; error?: string }> {
|
||||||
|
// Commit all changes
|
||||||
|
await execFileNoThrow('git', ['add', '-A'], localPath);
|
||||||
|
|
||||||
|
const commitMessage = `[Symphony] Complete contribution for #${issueNumber}
|
||||||
|
|
||||||
|
Processed all Auto Run documents for: ${issueTitle}`;
|
||||||
|
|
||||||
|
const commitResult = await execFileNoThrow('git', ['commit', '-m', commitMessage], localPath);
|
||||||
|
if (commitResult.exitCode !== 0 && !commitResult.stderr.includes('nothing to commit')) {
|
||||||
|
return { success: false, error: `Commit failed: ${commitResult.stderr}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push changes
|
||||||
|
const pushResult = await execFileNoThrow('git', ['push'], localPath);
|
||||||
|
if (pushResult.exitCode !== 0) {
|
||||||
|
return { success: false, error: `Push failed: ${pushResult.stderr}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert draft to ready for review
|
||||||
|
const readyResult = await execFileNoThrow(
|
||||||
|
'gh',
|
||||||
|
['pr', 'ready', prNumber.toString()],
|
||||||
|
localPath
|
||||||
|
);
|
||||||
|
if (readyResult.exitCode !== 0) {
|
||||||
|
return { success: false, error: `Failed to mark PR ready: ${readyResult.stderr}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update PR body with completion summary
|
||||||
|
const body = `## Symphony Contribution
|
||||||
|
|
||||||
|
This PR was created via Maestro Symphony.
|
||||||
|
|
||||||
|
Closes #${issueNumber}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Task:** ${issueTitle}
|
||||||
|
|
||||||
|
*Contributed by the Maestro Symphony community* 🎵`;
|
||||||
|
|
||||||
|
await execFileNoThrow(
|
||||||
|
'gh',
|
||||||
|
['pr', 'edit', prNumber.toString(), '--body', body],
|
||||||
|
localPath
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get final PR URL
|
||||||
|
const prInfoResult = await execFileNoThrow(
|
||||||
|
'gh',
|
||||||
|
['pr', 'view', prNumber.toString(), '--json', 'url', '-q', '.url'],
|
||||||
|
localPath
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
prUrl: prInfoResult.stdout.trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a contribution by closing the draft PR and cleaning up.
|
||||||
|
*/
|
||||||
|
export async function cancelContribution(
|
||||||
|
localPath: string,
|
||||||
|
prNumber: number,
|
||||||
|
cleanup: boolean = true
|
||||||
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
// Close the draft PR
|
||||||
|
const closeResult = await execFileNoThrow(
|
||||||
|
'gh',
|
||||||
|
['pr', 'close', prNumber.toString(), '--delete-branch'],
|
||||||
|
localPath
|
||||||
|
);
|
||||||
|
if (closeResult.exitCode !== 0) {
|
||||||
|
logger.warn('Failed to close PR', LOG_CONTEXT, { prNumber, error: closeResult.stderr });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up local directory
|
||||||
|
if (cleanup) {
|
||||||
|
await execFileNoThrow('rm', ['-rf', localPath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user