@web42/stask 0.1.5
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 +100 -0
- package/bin/stask.mjs +157 -0
- package/commands/approve.mjs +43 -0
- package/commands/assign.mjs +64 -0
- package/commands/create.mjs +88 -0
- package/commands/delete.mjs +127 -0
- package/commands/heartbeat.mjs +375 -0
- package/commands/list.mjs +60 -0
- package/commands/log.mjs +34 -0
- package/commands/pr-status.mjs +33 -0
- package/commands/qa.mjs +183 -0
- package/commands/session.mjs +76 -0
- package/commands/show.mjs +61 -0
- package/commands/spec-update.mjs +61 -0
- package/commands/subtask-create.mjs +62 -0
- package/commands/subtask-done.mjs +80 -0
- package/commands/sync-daemon.mjs +134 -0
- package/commands/sync.mjs +36 -0
- package/commands/transition.mjs +143 -0
- package/config.example.json +55 -0
- package/lib/env.mjs +118 -0
- package/lib/file-uploader.mjs +179 -0
- package/lib/guards.mjs +261 -0
- package/lib/pr-create.mjs +133 -0
- package/lib/pr-status.mjs +119 -0
- package/lib/roles.mjs +85 -0
- package/lib/session-tracker.mjs +150 -0
- package/lib/slack-api.mjs +198 -0
- package/lib/slack-row.mjs +257 -0
- package/lib/slack-sync.mjs +388 -0
- package/lib/sync-daemon.mjs +117 -0
- package/lib/tracker-db.mjs +473 -0
- package/lib/tx.mjs +84 -0
- package/lib/validate.mjs +90 -0
- package/lib/worktree-cleanup.mjs +91 -0
- package/lib/worktree-create.mjs +127 -0
- package/package.json +34 -0
- package/skills/stask-general.md +113 -0
- package/skills/stask-lead.md +72 -0
- package/skills/stask-qa.md +99 -0
- package/skills/stask-worker.md +61 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* worktree-cleanup.mjs — Remove a git worktree after task completion.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node worktree-cleanup.mjs <task-id> [--force]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { findTask, updateTask, addLogEntry, parseWorktreeValue } from './tracker-db.mjs';
|
|
12
|
+
import { CONFIG } from './env.mjs';
|
|
13
|
+
|
|
14
|
+
const REPO_PATH = CONFIG.projectRepoPath;
|
|
15
|
+
|
|
16
|
+
const taskId = process.argv[2];
|
|
17
|
+
const forceFlag = process.argv.includes('--force');
|
|
18
|
+
|
|
19
|
+
if (!taskId) {
|
|
20
|
+
console.error('Usage: node worktree-cleanup.mjs <task-id> [--force]');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const task = findTask(taskId);
|
|
25
|
+
|
|
26
|
+
if (!task) {
|
|
27
|
+
console.error(`ERROR: Task ${taskId} not found`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const wt = parseWorktreeValue(task['Worktree']);
|
|
32
|
+
if (!wt) {
|
|
33
|
+
console.log(`No worktree set for ${taskId}. Nothing to clean up.`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { branch, path: wtPath } = wt;
|
|
38
|
+
|
|
39
|
+
// Check for uncommitted changes
|
|
40
|
+
if (fs.existsSync(wtPath)) {
|
|
41
|
+
try {
|
|
42
|
+
const status = execSync('git status --porcelain', { cwd: wtPath, encoding: 'utf-8' }).trim();
|
|
43
|
+
if (status && !forceFlag) {
|
|
44
|
+
console.error(`ERROR: Worktree at ${wtPath} has uncommitted changes:`);
|
|
45
|
+
console.error(status);
|
|
46
|
+
console.error('Use --force to remove anyway.');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Remove worktree
|
|
53
|
+
if (fs.existsSync(wtPath)) {
|
|
54
|
+
try {
|
|
55
|
+
execSync(`git worktree remove "${wtPath}" ${forceFlag ? '--force' : ''}`, { cwd: REPO_PATH, stdio: 'pipe' });
|
|
56
|
+
console.log(`Removed worktree: ${wtPath}`);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
try {
|
|
59
|
+
fs.rmSync(wtPath, { recursive: true, force: true });
|
|
60
|
+
console.log(`Removed worktree directory manually: ${wtPath}`);
|
|
61
|
+
execSync('git worktree prune', { cwd: REPO_PATH, stdio: 'pipe' });
|
|
62
|
+
} catch (rmErr) {
|
|
63
|
+
console.error(`ERROR: Could not remove worktree directory: ${rmErr.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
try { execSync('git worktree prune', { cwd: REPO_PATH, stdio: 'pipe' }); } catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Delete local branch
|
|
71
|
+
try {
|
|
72
|
+
execSync(`git branch -d "${branch}"`, { cwd: REPO_PATH, stdio: 'pipe' });
|
|
73
|
+
console.log(`Deleted local branch: ${branch}`);
|
|
74
|
+
} catch {
|
|
75
|
+
if (forceFlag) {
|
|
76
|
+
try {
|
|
77
|
+
execSync(`git branch -D "${branch}"`, { cwd: REPO_PATH, stdio: 'pipe' });
|
|
78
|
+
console.log(`Force-deleted local branch: ${branch}`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(`WARNING: Could not delete branch ${branch}: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`Branch ${branch} not deleted (has unmerged changes). Use --force to delete.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update DB
|
|
88
|
+
updateTask(taskId, { worktree: null });
|
|
89
|
+
addLogEntry(taskId, `${taskId} "${task['Task Name']}": Worktree cleaned up. Branch: ${branch}.`);
|
|
90
|
+
|
|
91
|
+
console.log(`${taskId}: Worktree cleaned up | Branch: ${branch}`);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* worktree-create.mjs — Create a git worktree for a parent task.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node worktree-create.mjs <task-id>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import {
|
|
12
|
+
findTask, updateTask, addLogEntry, formatWorktreeValue,
|
|
13
|
+
} from './tracker-db.mjs';
|
|
14
|
+
import { slugifyTaskName, branchPrefixForType } from './validate.mjs';
|
|
15
|
+
import { CONFIG } from './env.mjs';
|
|
16
|
+
|
|
17
|
+
const REPO_PATH = CONFIG.projectRepoPath;
|
|
18
|
+
const WORKTREE_BASE = CONFIG.worktreeBaseDir;
|
|
19
|
+
|
|
20
|
+
const taskId = process.argv[2];
|
|
21
|
+
|
|
22
|
+
if (!taskId) {
|
|
23
|
+
console.error('Usage: node worktree-create.mjs <task-id>');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const task = findTask(taskId);
|
|
28
|
+
|
|
29
|
+
if (!task) {
|
|
30
|
+
console.error(`ERROR: Task ${taskId} not found`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (task['Parent'] !== 'None') {
|
|
35
|
+
console.error(`ERROR: ${taskId} is a subtask. Worktrees are only created for parent tasks.`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (task['Worktree'] !== 'None') {
|
|
40
|
+
console.log(`Worktree already exists for ${taskId}: ${task['Worktree']}. Skipping.`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Derive branch name and path ────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
const slug = slugifyTaskName(task['Task Name']);
|
|
47
|
+
const prefix = branchPrefixForType(task['Type']);
|
|
48
|
+
let branchName = `${prefix}/${slug}`;
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(WORKTREE_BASE)) {
|
|
51
|
+
fs.mkdirSync(WORKTREE_BASE, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const worktreePath = path.join(WORKTREE_BASE, slug);
|
|
55
|
+
|
|
56
|
+
// Check for branch name collision
|
|
57
|
+
try {
|
|
58
|
+
const existing = execSync(`git branch --list "${branchName}"`, { cwd: REPO_PATH, encoding: 'utf-8' }).trim();
|
|
59
|
+
if (existing) {
|
|
60
|
+
try {
|
|
61
|
+
const worktrees = execSync('git worktree list --porcelain', { cwd: REPO_PATH, encoding: 'utf-8' });
|
|
62
|
+
if (worktrees.includes(branchName)) {
|
|
63
|
+
console.log(`Branch ${branchName} already has a worktree. Reusing.`);
|
|
64
|
+
const lines = worktrees.split('\n');
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
if (lines[i].includes(`branch refs/heads/${branchName}`)) {
|
|
67
|
+
const wtPath = lines[i - 2]?.replace('worktree ', '') || worktreePath;
|
|
68
|
+
const wtValue = formatWorktreeValue(branchName, wtPath);
|
|
69
|
+
updateTask(taskId, { worktree: wtValue });
|
|
70
|
+
addLogEntry(taskId, `${taskId}: Reusing existing worktree for branch ${branchName}.`);
|
|
71
|
+
console.log(`${taskId}: Worktree reused | Branch: ${branchName} | Path: ${wtPath}`);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
branchName = `${prefix}/${slug}-${taskId.toLowerCase()}`;
|
|
78
|
+
console.log(`Branch name collision. Using: ${branchName}`);
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
|
|
82
|
+
// ─── Create worktree ────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(worktreePath)) {
|
|
85
|
+
try {
|
|
86
|
+
execSync(`git worktree remove "${worktreePath}" --force`, { cwd: REPO_PATH, stdio: 'pipe' });
|
|
87
|
+
} catch {
|
|
88
|
+
fs.rmSync(worktreePath, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
let baseBranch = 'dev';
|
|
94
|
+
try {
|
|
95
|
+
execSync('git rev-parse --verify dev', { cwd: REPO_PATH, stdio: 'pipe' });
|
|
96
|
+
} catch {
|
|
97
|
+
try {
|
|
98
|
+
execSync('git rev-parse --verify main', { cwd: REPO_PATH, stdio: 'pipe' });
|
|
99
|
+
baseBranch = 'main';
|
|
100
|
+
} catch {
|
|
101
|
+
console.error('ERROR: Neither dev nor main branch found.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
execSync(`git fetch origin ${baseBranch}`, { cwd: REPO_PATH, stdio: 'pipe' });
|
|
108
|
+
} catch {
|
|
109
|
+
console.error(`WARNING: Could not fetch origin/${baseBranch}.`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
execSync(`git worktree add "${worktreePath}" -b "${branchName}" ${baseBranch}`, {
|
|
113
|
+
cwd: REPO_PATH, stdio: 'pipe',
|
|
114
|
+
});
|
|
115
|
+
console.log(`Based on: ${baseBranch}`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(`ERROR: Failed to create worktree: ${err.message}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Update DB ──────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
const wtValue = formatWorktreeValue(branchName, worktreePath);
|
|
124
|
+
updateTask(taskId, { worktree: wtValue });
|
|
125
|
+
addLogEntry(taskId, `${taskId} "${task['Task Name']}": Worktree created. Branch: ${branchName}, Path: ${worktreePath}.`);
|
|
126
|
+
|
|
127
|
+
console.log(`${taskId}: Worktree created | Branch: ${branchName} | Path: ${worktreePath}`);
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@web42/stask",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "SQLite-backed task lifecycle CLI with Slack sync for AI agent teams",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"stask": "./bin/stask.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"commands/",
|
|
13
|
+
"skills/",
|
|
14
|
+
"config.example.json"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test test/*.test.mjs"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"cli",
|
|
21
|
+
"task",
|
|
22
|
+
"slack",
|
|
23
|
+
"sqlite",
|
|
24
|
+
"ai-agents",
|
|
25
|
+
"lifecycle"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"better-sqlite3": "^12.8.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stask-general
|
|
3
|
+
description: Task lifecycle framework — spec-first workflow, Slack sync, status transitions, worktree isolation, and PR-based review. SQLite is the single source of truth. All operations go through the `stask` CLI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# stask — Task Lifecycle Framework
|
|
7
|
+
|
|
8
|
+
SQLite-backed task lifecycle management with Slack sync. Every operation goes through the `stask` CLI — the database enforces all lifecycle rules via triggers and constraints, and every mutation syncs to Slack atomically.
|
|
9
|
+
|
|
10
|
+
## Core Rules
|
|
11
|
+
|
|
12
|
+
1. **No task exists without a spec uploaded to Slack.** Every task must have a Spec value: `specs/<name>.md (F0XXXXXXXXX)`.
|
|
13
|
+
2. **SQLite is the single source of truth.** Never edit `tracker.db` directly — use `stask` commands only.
|
|
14
|
+
3. **Every parent task gets its own worktree.** The guard system creates it automatically on In-Progress.
|
|
15
|
+
4. **PR merge = Done.** The Human merges on GitHub, the system auto-completes the task.
|
|
16
|
+
5. **DB + Slack are transactional.** If Slack sync fails, the DB rolls back.
|
|
17
|
+
|
|
18
|
+
## Roles
|
|
19
|
+
|
|
20
|
+
| Role | Responsibility |
|
|
21
|
+
|------|---------------|
|
|
22
|
+
| **Human** | Approves specs (via Slack checkbox), reviews PRs on GitHub, merges |
|
|
23
|
+
| **Lead** | Creates subtasks, delegates, coordinates fixes |
|
|
24
|
+
| **Worker(s)** | Implements subtasks in worktrees, marks them done |
|
|
25
|
+
| **QA** | Tests against acceptance criteria, submits pass/fail verdict |
|
|
26
|
+
|
|
27
|
+
## Task Lifecycle
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Lead writes spec -> uploads to Slack -> creates task (assigned to Human)
|
|
31
|
+
-> Human approves spec -> assigned to Lead
|
|
32
|
+
-> Lead creates subtasks -> delegates to Workers
|
|
33
|
+
-> Lead transitions to In-Progress -> worktree created automatically (guard)
|
|
34
|
+
-> Workers work in the task worktree (feature branch)
|
|
35
|
+
-> All subtasks Done -> auto Testing (guard: worktree clean + pushed)
|
|
36
|
+
-> QA submits verdict:
|
|
37
|
+
PASS -> Ready for Human Review -> draft PR created (guard)
|
|
38
|
+
-> Human reviews on GitHub, leaves comments
|
|
39
|
+
-> Human merges PR -> task auto-completes to Done
|
|
40
|
+
FAIL (1st/2nd) -> back to In-Progress (Lead re-delegates)
|
|
41
|
+
FAIL (3rd) -> Blocked -> escalated to Human
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Guards
|
|
45
|
+
|
|
46
|
+
Guards run automatically before transitions. Checks run first (read-only); if all pass, setup guards run (side effects).
|
|
47
|
+
|
|
48
|
+
| Transition | Guard | Type | What it does |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| -> In-Progress | `require_approved` | check | Task must not be assigned to Human |
|
|
51
|
+
| -> In-Progress | `setup_worktree` | setup | Creates git worktree + feature branch |
|
|
52
|
+
| -> Testing | `all_subtasks_done` | check | Every subtask must be Done |
|
|
53
|
+
| -> Testing | `worktree_clean` | check | No uncommitted changes in worktree |
|
|
54
|
+
| -> Testing | `worktree_pushed` | check | No unpushed commits |
|
|
55
|
+
| -> Ready for Human Review | `worktree_clean` | check | No uncommitted changes |
|
|
56
|
+
| -> Ready for Human Review | `worktree_pushed` | check | No unpushed commits |
|
|
57
|
+
| -> Ready for Human Review | `require_pr` | check | PR must exist |
|
|
58
|
+
|
|
59
|
+
## Status Definitions
|
|
60
|
+
|
|
61
|
+
| Status | Meaning | Assigned To |
|
|
62
|
+
|--------|---------|-------------|
|
|
63
|
+
| To-Do (Human) | Spec written, awaiting approval | Human |
|
|
64
|
+
| To-Do (Lead) | Spec approved, Lead creating subtasks | Lead |
|
|
65
|
+
| In-Progress | Workers building subtasks in worktree | Parent: Lead, Subtasks: Workers |
|
|
66
|
+
| Testing | QA testing with evidence | QA |
|
|
67
|
+
| Ready for Human Review | QA PASSED, awaiting final sign-off + PR review | Human |
|
|
68
|
+
| Done | PR merged, shipped | -- |
|
|
69
|
+
| Blocked | Halted — escalated to Human | Human |
|
|
70
|
+
|
|
71
|
+
## CLI Reference
|
|
72
|
+
|
|
73
|
+
### Mutation commands (DB + Slack transaction)
|
|
74
|
+
|
|
75
|
+
| Command | Purpose |
|
|
76
|
+
|---------|---------|
|
|
77
|
+
| `stask create --spec <path> --name "..." [--type Feature\|Bug\|Task]` | Create task (auto-uploads spec to Slack) |
|
|
78
|
+
| `stask approve <task-id>` | Human approves spec (reassigns to Lead) |
|
|
79
|
+
| `stask transition <task-id> <status>` | Transition status (guards enforce prerequisites) |
|
|
80
|
+
| `stask subtask create --parent <id> --name "..." --assign <agent>` | Create subtask under parent |
|
|
81
|
+
| `stask subtask done <subtask-id>` | Worker marks subtask Done (auto-cascades parent) |
|
|
82
|
+
| `stask qa <task-id> --report <path> --verdict PASS\|FAIL` | Submit QA verdict with report |
|
|
83
|
+
| `stask assign <task-id> <name>` | Reassign a task |
|
|
84
|
+
| `stask spec-update <task-id> --spec <path>` | Re-upload edited spec |
|
|
85
|
+
|
|
86
|
+
### Read-only commands
|
|
87
|
+
|
|
88
|
+
| Command | Purpose |
|
|
89
|
+
|---------|---------|
|
|
90
|
+
| `stask list [--status X] [--assignee Y] [--json]` | List tasks (filterable) |
|
|
91
|
+
| `stask show <task-id> [--log]` | Show task details + subtasks + audit log |
|
|
92
|
+
| `stask log [<task-id>] [--limit N]` | View audit log |
|
|
93
|
+
| `stask heartbeat <agent-name>` | Returns pending work for an agent (JSON) |
|
|
94
|
+
| `stask pr-status <task-id>` | Poll PR for comments/merge status |
|
|
95
|
+
| `stask session claim\|release\|status <task-id>` | Manage session locks |
|
|
96
|
+
|
|
97
|
+
### Sync commands
|
|
98
|
+
|
|
99
|
+
| Command | Purpose |
|
|
100
|
+
|---------|---------|
|
|
101
|
+
| `stask sync` | Run one bidirectional sync cycle |
|
|
102
|
+
| `stask sync-daemon start\|stop\|status` | Manage background sync daemon |
|
|
103
|
+
|
|
104
|
+
## Rules for All Agents
|
|
105
|
+
|
|
106
|
+
1. **Never edit tracker.db directly.** Use `stask` commands only.
|
|
107
|
+
2. **Every task needs a spec.** No exceptions.
|
|
108
|
+
3. **Work in the task worktree.** Never in the main repo checkout.
|
|
109
|
+
4. **Commit and push before marking done.** Guards will block Testing if you don't.
|
|
110
|
+
5. **PR merge = Done.** Never manually transition to Done.
|
|
111
|
+
6. **All PR comments go through the Human.** External comments need explicit triage.
|
|
112
|
+
7. **Workers mark their own subtasks Done** via `stask subtask done <id>`.
|
|
113
|
+
8. **Reference specs by Slack file ID** (e.g., `F0XXXXXXXXX`), never by local path.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stask-lead
|
|
3
|
+
description: Lead agent workflow — creates subtasks, delegates work, triages PR feedback, coordinates the full task lifecycle from spec approval to PR merge.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lead Agent Workflow
|
|
7
|
+
|
|
8
|
+
You are the **Lead**. You own each task from spec approval through PR merge. You never write code directly — you delegate to Workers and coordinate the lifecycle.
|
|
9
|
+
|
|
10
|
+
## Your Responsibilities
|
|
11
|
+
|
|
12
|
+
1. **Read the spec** when a task is approved and assigned to you
|
|
13
|
+
2. **Create subtasks** breaking the spec into implementable units
|
|
14
|
+
3. **Delegate** each subtask to a Worker agent
|
|
15
|
+
4. **Transition to In-Progress** (auto-creates worktree)
|
|
16
|
+
5. **Triage QA failures** — review reports, create fix subtasks, re-delegate
|
|
17
|
+
6. **Create the PR** after QA passes — write a rich description with summary, changes, QA results
|
|
18
|
+
7. **Triage PR feedback** — decide if code change or cosmetic fix
|
|
19
|
+
8. **Transition to Done** when the Human merges the PR
|
|
20
|
+
|
|
21
|
+
## Commands You Use
|
|
22
|
+
|
|
23
|
+
| Command | When |
|
|
24
|
+
|---------|------|
|
|
25
|
+
| `stask heartbeat <your-name>` | Check what work you have pending |
|
|
26
|
+
| `stask show <task-id>` | View task details, subtasks, and status |
|
|
27
|
+
| `stask subtask create --parent <id> --name "..." --assign <worker>` | Break work into subtasks |
|
|
28
|
+
| `stask transition <task-id> In-Progress` | Start work (auto-creates worktree) |
|
|
29
|
+
| `stask transition <task-id> "Ready for Human Review"` | After QA pass + PR created |
|
|
30
|
+
| `stask transition <task-id> Done` | After Human merges the PR |
|
|
31
|
+
| `stask pr-status <task-id>` | Check PR comments and merge status |
|
|
32
|
+
| `stask assign <task-id> <name>` | Reassign a task |
|
|
33
|
+
|
|
34
|
+
## When You Receive Work
|
|
35
|
+
|
|
36
|
+
### Spec Approved (To-Do, assigned to you)
|
|
37
|
+
1. Read the spec (use the Slack file ID from `stask show`)
|
|
38
|
+
2. Create subtasks: `stask subtask create --parent T-XXX --name "..." --assign <worker>`
|
|
39
|
+
3. Transition: `stask transition T-XXX In-Progress`
|
|
40
|
+
|
|
41
|
+
### QA Passed (Testing, reassigned to you)
|
|
42
|
+
1. Read the spec, QA report, git log, and diff
|
|
43
|
+
2. Create a draft PR: `gh pr create --draft` in the worktree
|
|
44
|
+
3. Write a rich PR description (summary, changes, QA results, screenshots)
|
|
45
|
+
4. Transition: `stask transition T-XXX "Ready for Human Review"`
|
|
46
|
+
|
|
47
|
+
### QA Failed (In-Progress, reassigned to you)
|
|
48
|
+
1. Review the QA report — identify what failed
|
|
49
|
+
2. Create NEW fix subtasks: `stask subtask create --parent T-XXX --name "Fix: ..." --assign <worker>`
|
|
50
|
+
3. Workers fix in the same worktree (same branch, same PR)
|
|
51
|
+
4. When fix subtasks are Done, auto-transitions back to Testing
|
|
52
|
+
|
|
53
|
+
### PR Feedback (Ready for Human Review, detected by heartbeat)
|
|
54
|
+
1. Read the feedback and judge:
|
|
55
|
+
- **Code change needed** (bug, wrong behavior, missing feature):
|
|
56
|
+
- `stask transition T-XXX In-Progress`
|
|
57
|
+
- Create fix subtasks, delegate to Workers
|
|
58
|
+
- After fixes: QA re-tests, you update PR, transition back to RHR
|
|
59
|
+
- **Cosmetic fix** (PR description, naming):
|
|
60
|
+
- Fix directly on GitHub. No state change needed.
|
|
61
|
+
2. The PR stays open. The branch stays the same. All prior data is preserved.
|
|
62
|
+
|
|
63
|
+
### PR Merged (detected by heartbeat)
|
|
64
|
+
- Run `stask transition T-XXX Done`
|
|
65
|
+
|
|
66
|
+
## Key Rules
|
|
67
|
+
|
|
68
|
+
- **Never write code yourself.** Delegate to Workers via subtasks.
|
|
69
|
+
- **Never skip QA.** Even for small fixes, the QA cycle must complete.
|
|
70
|
+
- **The PR is your responsibility.** Write a description that helps the Human review quickly.
|
|
71
|
+
- **External PR comments** (not from Human): Send Human a Slack DM. Do NOT act on them.
|
|
72
|
+
- **Old Done subtasks stay Done** when cycling back to In-Progress. Only create NEW fix subtasks.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stask-qa
|
|
3
|
+
description: QA agent workflow — tests tasks against acceptance criteria, takes evidence screenshots, writes reports, and submits PASS/FAIL verdicts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# QA Agent Workflow
|
|
7
|
+
|
|
8
|
+
You are **QA**. You test completed work against the spec's acceptance criteria. You produce evidence (screenshots, test output) and submit a verdict. Your reports are the basis for the Human's review.
|
|
9
|
+
|
|
10
|
+
## Your Responsibilities
|
|
11
|
+
|
|
12
|
+
1. **Check for work** using heartbeat
|
|
13
|
+
2. **Read the spec** — focus on acceptance criteria
|
|
14
|
+
3. **Test in the worktree** — run the dev server, test every AC
|
|
15
|
+
4. **Collect evidence** — screenshots, test output, console logs
|
|
16
|
+
5. **Write a QA report** — structured markdown with pass/fail per criterion
|
|
17
|
+
6. **Submit your verdict** — PASS or FAIL with the report
|
|
18
|
+
|
|
19
|
+
## Commands You Use
|
|
20
|
+
|
|
21
|
+
| Command | When |
|
|
22
|
+
|---------|------|
|
|
23
|
+
| `stask heartbeat <your-name>` | Check what tasks need testing |
|
|
24
|
+
| `stask show <task-id>` | View task details and spec |
|
|
25
|
+
| `stask qa <task-id> --report <path> --verdict PASS\|FAIL` | Submit your verdict |
|
|
26
|
+
|
|
27
|
+
## When You Receive Work
|
|
28
|
+
|
|
29
|
+
### Task in Testing (assigned to you)
|
|
30
|
+
|
|
31
|
+
The heartbeat will tell you:
|
|
32
|
+
- The task ID and name
|
|
33
|
+
- The worktree path
|
|
34
|
+
- The spec file ID with acceptance criteria
|
|
35
|
+
|
|
36
|
+
**Steps:**
|
|
37
|
+
1. `cd` to the worktree path
|
|
38
|
+
2. Read the spec — identify every acceptance criterion (AC)
|
|
39
|
+
3. Start the dev server or test environment
|
|
40
|
+
4. Test each AC systematically
|
|
41
|
+
5. Take screenshots as evidence for each AC
|
|
42
|
+
6. Write your QA report (see format below)
|
|
43
|
+
7. Submit: `stask qa T-XXX --report <path> --verdict PASS` or `FAIL`
|
|
44
|
+
|
|
45
|
+
## QA Report Format
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
# QA Report: T-XXX — Task Name
|
|
49
|
+
|
|
50
|
+
## Environment
|
|
51
|
+
- Branch: feature/xxx
|
|
52
|
+
- Worktree: /path/to/worktree
|
|
53
|
+
- Date: YYYY-MM-DD
|
|
54
|
+
|
|
55
|
+
## Acceptance Criteria Results
|
|
56
|
+
|
|
57
|
+
### AC 1: [Description from spec]
|
|
58
|
+
- **Result:** PASS / FAIL
|
|
59
|
+
- **Evidence:** [Screenshot path or description]
|
|
60
|
+
- **Notes:** [Any observations]
|
|
61
|
+
|
|
62
|
+
### AC 2: [Description from spec]
|
|
63
|
+
- **Result:** PASS / FAIL
|
|
64
|
+
- **Evidence:** [Screenshot path or description]
|
|
65
|
+
- **Notes:** [Any observations]
|
|
66
|
+
|
|
67
|
+
## Summary
|
|
68
|
+
- Total ACs: X
|
|
69
|
+
- Passed: X
|
|
70
|
+
- Failed: X
|
|
71
|
+
|
|
72
|
+
## Verdict: PASS / FAIL
|
|
73
|
+
|
|
74
|
+
## Additional Notes
|
|
75
|
+
[Any bugs found, edge cases, suggestions]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Key Rules
|
|
79
|
+
|
|
80
|
+
- **Test every acceptance criterion.** Don't skip any, even obvious ones.
|
|
81
|
+
- **Take screenshots.** Evidence is required for the Human's review.
|
|
82
|
+
- **Be specific in failure reports.** The Lead needs to know exactly what failed and how to reproduce it.
|
|
83
|
+
- **Don't fix bugs yourself.** Report them. The Lead will delegate fixes to Workers.
|
|
84
|
+
- **3 failures = escalation.** After 3 consecutive FAIL verdicts, the task gets Blocked and escalated to the Human.
|
|
85
|
+
|
|
86
|
+
## QA Retry Slots
|
|
87
|
+
|
|
88
|
+
- 1st attempt: `qa_report_1`
|
|
89
|
+
- 2nd attempt (after 1st fail + fixes): `qa_report_2`
|
|
90
|
+
- 3rd attempt (after 2nd fail + fixes): `qa_report_3`
|
|
91
|
+
- 3rd fail: Task goes to Blocked, escalated to Human
|
|
92
|
+
|
|
93
|
+
## On Re-test (After Fixes)
|
|
94
|
+
|
|
95
|
+
When testing a task that previously failed:
|
|
96
|
+
1. Focus on the previously failed ACs first
|
|
97
|
+
2. Still verify all other ACs haven't regressed
|
|
98
|
+
3. Reference the previous report in your new one
|
|
99
|
+
4. Note what was fixed and what changed
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stask-worker
|
|
3
|
+
description: Worker agent workflow — implements subtasks in git worktrees, commits, pushes, and marks subtasks done. Workers write all the code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Worker Agent Workflow
|
|
7
|
+
|
|
8
|
+
You are a **Worker**. You implement subtasks assigned to you by the Lead. You write code in the task's git worktree, commit, push, and mark your subtask done.
|
|
9
|
+
|
|
10
|
+
## Your Responsibilities
|
|
11
|
+
|
|
12
|
+
1. **Check for work** using heartbeat
|
|
13
|
+
2. **Read the spec** to understand what you're building
|
|
14
|
+
3. **Work in the task worktree** (never the main repo checkout)
|
|
15
|
+
4. **Implement** the subtask according to the spec
|
|
16
|
+
5. **Commit and push** your changes
|
|
17
|
+
6. **Mark your subtask done** when implementation is complete
|
|
18
|
+
|
|
19
|
+
## Commands You Use
|
|
20
|
+
|
|
21
|
+
| Command | When |
|
|
22
|
+
|---------|------|
|
|
23
|
+
| `stask heartbeat <your-name>` | Check what subtasks are assigned to you |
|
|
24
|
+
| `stask show <task-id>` | View task/subtask details and spec |
|
|
25
|
+
| `stask subtask done <subtask-id>` | Mark your subtask as Done |
|
|
26
|
+
| `stask session claim <task-id>` | Claim a session lock (prevents conflicts) |
|
|
27
|
+
| `stask session release <task-id>` | Release your session lock |
|
|
28
|
+
|
|
29
|
+
## When You Receive Work
|
|
30
|
+
|
|
31
|
+
### Subtask Assigned (In-Progress, assigned to you)
|
|
32
|
+
|
|
33
|
+
The heartbeat will tell you:
|
|
34
|
+
- The subtask ID and name
|
|
35
|
+
- The parent task's worktree path and branch
|
|
36
|
+
- The spec file ID
|
|
37
|
+
|
|
38
|
+
**Steps:**
|
|
39
|
+
1. `cd` to the worktree path from the heartbeat
|
|
40
|
+
2. Read the spec to understand the full context and your specific subtask
|
|
41
|
+
3. Implement the changes
|
|
42
|
+
4. Test your changes locally
|
|
43
|
+
5. `git add` + `git commit` with a clear message
|
|
44
|
+
6. `git push` to the remote branch
|
|
45
|
+
7. `stask subtask done <your-subtask-id>`
|
|
46
|
+
|
|
47
|
+
## Key Rules
|
|
48
|
+
|
|
49
|
+
- **Always work in the worktree.** The path is in the heartbeat output. Never work in the main repo.
|
|
50
|
+
- **Commit AND push before marking done.** The Testing guards check for both uncommitted changes and unpushed commits. If you don't push, the task will get stuck.
|
|
51
|
+
- **One branch per task.** All Workers on the same parent task share one worktree and one branch. Coordinate with other Workers if needed.
|
|
52
|
+
- **Don't transition the parent task.** The system auto-transitions to Testing when all subtasks are Done.
|
|
53
|
+
- **Mark only YOUR subtask done.** Use `stask subtask done <your-id>`, not anyone else's.
|
|
54
|
+
- **If you're blocked**, tell the Lead. Don't try to work around issues.
|
|
55
|
+
|
|
56
|
+
## Common Mistakes
|
|
57
|
+
|
|
58
|
+
1. **Forgetting to push** — `git commit` is not enough. You must `git push`.
|
|
59
|
+
2. **Working in main checkout** — Always verify you're in the worktree directory.
|
|
60
|
+
3. **Marking done too early** — Only mark done when your implementation is complete and pushed.
|
|
61
|
+
4. **Not reading the spec** — The spec has acceptance criteria. Read them before coding.
|