Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 85 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ A self-improving multi-agent system that coordinates AI agents to work on tasks
- **Role-Based Permissions**: Manager, Specialist, and Support roles with different capabilities
- **Release Stuck Tasks**: One-click release for tasks stuck in progress
- **Slack Integration**: Creates dedicated channels for each task, enabling real-time communication
- **Git Branch Workflow**: Automatic branch creation per task with PR generation for code review

## Architecture

Expand Down Expand Up @@ -115,6 +116,10 @@ Open http://localhost:3000 to access the Kanban dashboard.
| `PORT` | No | Server port (default: 3000) |
| `SLACK_BOT_TOKEN` | No | Slack Bot Token for task channels |
| `SLACK_CHANNEL_ID` | No | Default Slack channel for notifications |
| `GITHUB_TOKEN` | No | GitHub personal access token for PR creation |
| `GITHUB_OWNER` | No | GitHub repository owner (default: Othentic-Labs) |
| `GITHUB_REPO` | No | GitHub repository name (default: ai-team) |
| `GITHUB_WEBHOOK_SECRET` | No | Secret for webhook signature verification |

### Agent Definitions

Expand Down Expand Up @@ -299,13 +304,90 @@ Status: In Progress
Use this channel to communicate with the agent about this task.
```

## Git Branch Workflow

When GitHub is configured, agents work on isolated branches and create PRs for code review:

### How It Works

1. **Branch Creation**: When an agent starts a task, a branch is created: `task/{taskId}-{slug}`
2. **Isolated Work**: Agent commits changes to the task branch, not main/master
3. **PR Creation**: When task completes, a PR is automatically created
4. **Human Review**: Team reviews the PR and merges when ready
5. **Auto-Cleanup**: After merge, the task branch is automatically deleted

### Setting Up GitHub

1. Create a Personal Access Token at https://github.com/settings/tokens
2. Required scopes: `repo` (full control of private repositories)
3. Set environment variables:
```bash
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
GITHUB_OWNER=your-org # default: Othentic-Labs
GITHUB_REPO=your-repo # default: ai-team
```

### GitHub Webhook (Optional)

For automatic task status updates when PRs are merged:

1. Go to your repo Settings → Webhooks
2. Add webhook:
- URL: `https://your-domain.com/api/github/webhook`
- Content type: `application/json`
- Events: `Pull requests`
3. (Optional) Set `GITHUB_WEBHOOK_SECRET` for signature verification

### Branch Naming Convention

```
task/{taskId}-{slug}

Examples:
- task/abc123-implement-user-auth
- task/xyz789-fix-login-bug
- task/def456-add-dashboard-chart
```

### PR Template

PRs are created with:
- Title: `[{taskId}] {task title}`
- Body: Task description + auto-generated summary
- Base branch: `master` (configurable via `DEFAULT_BASE_BRANCH`)

### Task Status Flow

| Status | Description |
|--------|-------------|
| `backlog` | Task not started |
| `ready` | Ready to work on |
| `in_progress` | Agent working (branch exists) |
| `pr_created` | PR open for review |
| `done` | PR merged |

### Dashboard Features

- **PR Review Column**: Tasks awaiting review in dedicated Kanban column
- **PR Badge**: Shows PR number and status on task cards
- **Branch Display**: Shows branch name on task cards
- **PR Links**: Click to open PR in GitHub

## Task Workflow

1. Tasks start in **Backlog**
2. Move to **Ready** when unblocked
3. Agent picks up and moves to **In Progress** (Slack channel created)
4. Agent completes and moves to **Done** (Slack channel updated)
5. PM evaluates and creates follow-up tasks
3. Agent picks up and moves to **In Progress** (Slack channel + Git branch created)
4. Agent completes and creates PR, moves to **PR Review**
5. Human reviews and merges PR → moves to **Done**
6. PM evaluates and creates follow-up tasks

```
Backlog → Ready → In Progress → PR Review → Done
↓ ↓
Branch created PR created
#task-agent-id task/{id}-{slug}
```

## Deployment

Expand Down
83 changes: 82 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@
.column-title .dot.backlog { background: var(--text-muted); }
.column-title .dot.ready { background: var(--accent-cyan); box-shadow: 0 0 8px var(--accent-cyan); }
.column-title .dot.in_progress { background: var(--accent-yellow); box-shadow: 0 0 8px var(--accent-yellow); }
.column-title .dot.pr_created { background: #9b59b6; box-shadow: 0 0 8px #9b59b6; }
.column-title .dot.done { background: var(--accent-green); box-shadow: 0 0 8px var(--accent-green); }

.column-count {
Expand Down Expand Up @@ -430,6 +431,43 @@

.task-owner { color: var(--accent-blue); }

/* AG-10: PR and branch styles */
.task-pr {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}

.pr-badge {
font-size: 10px;
padding: 0.125rem 0.375rem;
border-radius: 3px;
cursor: pointer;
font-weight: 500;
}

.pr-open { background: #238636; color: #fff; }
.pr-approved { background: #8957e5; color: #fff; }
.pr-merged { background: #a371f7; color: #fff; }
.pr-closed { background: #da3633; color: #fff; }

.branch-name {
font-size: 9px;
color: var(--text-muted);
background: rgba(255,255,255,0.05);
padding: 0.125rem 0.375rem;
border-radius: 3px;
font-family: 'JetBrains Mono', monospace;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.add-task-btn {
width: 100%;
padding: 0.5rem;
Expand Down Expand Up @@ -1416,6 +1454,13 @@ <h2 class="sidebar-title">> AGENTS ONLINE</h2>
</div>
<div class="column-body" id="tasks-in_progress"></div>
</div>
<div class="column" data-status="pr_created">
<div class="column-header">
<div class="column-title"><span class="dot pr_created"></span> PR REVIEW</div>
<span class="column-count" id="count-pr_created">0</span>
</div>
<div class="column-body" id="tasks-pr_created"></div>
</div>
<div class="column" data-status="done">
<div class="column-header">
<div class="column-title"><span class="dot done"></span> DONE</div>
Expand Down Expand Up @@ -1456,11 +1501,20 @@ <h2 class="detail-title" id="detail-title"></h2>
<span class="meta-label">COMPLETED:</span>
<span class="meta-value" id="detail-completed">-</span>
</div>
<div class="meta-item" id="detail-branch-row" style="display:none;">
<span class="meta-label">BRANCH:</span>
<span class="meta-value" id="detail-branch" style="font-family:'JetBrains Mono',monospace;font-size:10px;">-</span>
</div>
<div class="meta-item" id="detail-pr-row" style="display:none;">
<span class="meta-label">PR:</span>
<span class="meta-value" id="detail-pr">-</span>
</div>
</div>
<div class="status-buttons" id="detail-status-buttons">
<button class="status-btn" data-status="backlog">BACKLOG</button>
<button class="status-btn" data-status="ready">READY</button>
<button class="status-btn" data-status="in_progress">IN PROGRESS</button>
<button class="status-btn" data-status="pr_created">PR REVIEW</button>
<button class="status-btn" data-status="done">DONE</button>
</div>
</div>
Expand Down Expand Up @@ -2035,7 +2089,7 @@ <h2 class="modal-title">> EDIT AGENT</h2>
const filteredTasks = applyFilters(tasks);
updateFilterCount(filteredTasks.length, tasks.length);

const statuses = ['backlog', 'ready', 'in_progress', 'done'];
const statuses = ['backlog', 'ready', 'in_progress', 'pr_created', 'done'];
statuses.forEach(status => {
const container = document.getElementById(`tasks-${status}`);
const statusTasks = filteredTasks.filter(t => t.status === status);
Expand All @@ -2059,6 +2113,12 @@ <h2 class="modal-title">> EDIT AGENT</h2>
<span class="task-owner">${task.owner || '-'}</span>
<span>${task.output ? '✓' : ''}</span>
</div>
${task.prUrl ? `
<div class="task-pr" onclick="event.stopPropagation(); window.open('${task.prUrl}', '_blank')">
<span class="pr-badge pr-${task.prStatus || 'open'}">PR #${task.prNumber || ''}</span>
${task.branch ? `<span class="branch-name">${task.branch}</span>` : ''}
</div>
` : (task.branch ? `<div class="task-pr"><span class="branch-name">${task.branch}</span></div>` : '')}
</div>
`).join('');
});
Expand Down Expand Up @@ -2163,6 +2223,27 @@ <h2 class="modal-title">> EDIT AGENT</h2>
document.getElementById('detail-completed').textContent = formatDate(task.completedAt);
document.getElementById('detail-description').textContent = task.description || 'No description provided.';

// AG-10: PR and branch info
const branchRow = document.getElementById('detail-branch-row');
const prRow = document.getElementById('detail-pr-row');

if (task.branch) {
branchRow.style.display = 'flex';
document.getElementById('detail-branch').textContent = task.branch;
} else {
branchRow.style.display = 'none';
}

if (task.prUrl) {
prRow.style.display = 'flex';
const prEl = document.getElementById('detail-pr');
prEl.innerHTML = `<a href="${task.prUrl}" target="_blank" style="color:var(--accent-cyan);text-decoration:none;">
<span class="pr-badge pr-${task.prStatus || 'open'}">PR #${task.prNumber || ''}</span>
</a>`;
} else {
prRow.style.display = 'none';
}

const outputEl = document.getElementById('detail-output');
outputEl.innerHTML = task.output
? `<span style="color:var(--text-primary)">${escapeHtml(task.output)}</span>`
Expand Down
58 changes: 53 additions & 5 deletions src/controllers/tasks.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Body, Controller, Delete, Get, Patch, Path, Post, Query, Response, Rout
import { Task, taskDb } from '../taskdb';
import { ErrorResponse } from '../types/api';
import { getOrchestrator } from './orchestration.controller';
import { gitService } from '../git-service';

interface CreateTaskRequest {
title: string;
Expand All @@ -13,12 +14,17 @@ interface CreateTaskRequest {
interface UpdateTaskRequest {
title?: string;
description?: string;
status?: 'backlog' | 'ready' | 'in_progress' | 'done';
status?: 'backlog' | 'ready' | 'in_progress' | 'pr_created' | 'done';
owner?: string;
priority?: 'P0' | 'P1' | 'P2';
output?: string;
startedAt?: string;
completedAt?: string;
// Git workflow fields (AG-10)
branch?: string;
prUrl?: string;
prNumber?: number;
prStatus?: 'open' | 'approved' | 'merged' | 'closed';
}

@Route('api')
Expand Down Expand Up @@ -90,10 +96,23 @@ export class TasksController extends Controller {
if (body.status === 'ready' && currentTask?.status !== 'ready' && task.owner) {
console.log(`[TasksController] Task ${taskId} moved to ready - triggering ${task.owner} agent`);

// Update status to in_progress immediately
// AG-10: Create a branch for this task
let branchName: string | undefined;
if (gitService.isConfigured()) {
const branchResult = await gitService.createBranch(taskId, task.title);
if (branchResult.success && branchResult.branch) {
branchName = branchResult.branch;
console.log(`[TasksController] Created branch: ${branchName}`);
} else {
console.warn(`[TasksController] Branch creation failed: ${branchResult.error}`);
}
}

// Update status to in_progress with branch info
await taskDb.updateTask(taskId, {
status: 'in_progress',
startedAt: new Date().toISOString()
startedAt: new Date().toISOString(),
branch: branchName,
});

// Run agent asynchronously (don't wait for completion)
Expand All @@ -102,9 +121,38 @@ export class TasksController extends Controller {
orchestrator.runAgent(
task.owner,
`${task.title}${task.description ? `: ${task.description}` : ''}`,
{ taskId }
{ taskId, branch: branchName }
).then(async (result) => {
// Mark task as done when agent completes
// AG-10: Create PR when agent completes (if git is configured)
if (branchName && gitService.isConfigured()) {
// Commit any changes made by agent
await gitService.createCommit(taskId, `Complete: ${task.title}`);

// Create PR
const prResult = await gitService.createPR(
taskId,
task.title,
task.description || result.substring(0, 500),
branchName
);

if (prResult.success && prResult.prUrl && prResult.prNumber) {
// Update task with PR info and set status to pr_created
await taskDb.updateTask(taskId, {
status: 'pr_created',
output: result.substring(0, 10000),
prUrl: prResult.prUrl,
prNumber: prResult.prNumber,
prStatus: 'open',
});
console.log(`[TasksController] Task ${taskId} PR created: ${prResult.prUrl}`);
return;
} else {
console.warn(`[TasksController] PR creation failed: ${prResult.error}`);
}
}

// Fallback: Mark task as done if no PR workflow
await taskDb.updateTask(taskId, {
status: 'done',
output: result.substring(0, 10000),
Expand Down
Loading