chore: install openagent opencode
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
399
.opencode/skills/task-management/SKILL.md
Normal file
399
.opencode/skills/task-management/SKILL.md
Normal file
@@ -0,0 +1,399 @@
|
||||
---
|
||||
name: task-management
|
||||
description: Task management CLI for tracking and managing feature subtasks with status, dependencies, and validation
|
||||
version: 1.0.0
|
||||
author: opencode
|
||||
type: skill
|
||||
category: development
|
||||
tags:
|
||||
- tasks
|
||||
- management
|
||||
- tracking
|
||||
- dependencies
|
||||
- cli
|
||||
---
|
||||
|
||||
# Task Management Skill
|
||||
|
||||
> **Purpose**: Track, manage, and validate feature implementations with atomic task breakdowns, dependency resolution, and progress monitoring.
|
||||
|
||||
---
|
||||
|
||||
## What I Do
|
||||
|
||||
I provide a command-line interface for managing task breakdowns created by the TaskManager subagent. I help you:
|
||||
|
||||
- **Track progress** - See status of all features and their subtasks
|
||||
- **Find next tasks** - Show eligible tasks (dependencies satisfied)
|
||||
- **Identify blocked tasks** - See what's blocked and why
|
||||
- **Manage completion** - Mark subtasks as complete with summaries
|
||||
- **Validate integrity** - Check JSON files and dependency trees
|
||||
|
||||
---
|
||||
|
||||
## How to Use Me
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Show all task statuses
|
||||
bash .opencode/skills/task-management/router.sh status
|
||||
|
||||
# Show next eligible tasks
|
||||
bash .opencode/skills/task-management/router.sh next
|
||||
|
||||
# Show blocked tasks
|
||||
bash .opencode/skills/task-management/router.sh blocked
|
||||
|
||||
# Mark a task complete
|
||||
bash .opencode/skills/task-management/router.sh complete <feature> <seq> "summary"
|
||||
|
||||
# Validate all tasks
|
||||
bash .opencode/skills/task-management/router.sh validate
|
||||
```
|
||||
|
||||
### Command Reference
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `status [feature]` | Show task status summary for all features or specific one |
|
||||
| `next [feature]` | Show next eligible tasks (dependencies satisfied) |
|
||||
| `parallel [feature]` | Show parallelizable tasks ready to run |
|
||||
| `deps <feature> <seq>` | Show dependency tree for a specific subtask |
|
||||
| `blocked [feature]` | Show blocked tasks and why |
|
||||
| `complete <feature> <seq> "summary"` | Mark subtask complete with summary |
|
||||
| `validate [feature]` | Validate JSON files and dependencies |
|
||||
| `help` | Show help message |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Check Overall Progress
|
||||
|
||||
```bash
|
||||
$ bash .opencode/skills/task-management/router.sh status
|
||||
|
||||
[my-feature] My Feature Implementation
|
||||
Status: active | Progress: 45% (5/11)
|
||||
Pending: 3 | In Progress: 2 | Completed: 5 | Blocked: 1
|
||||
```
|
||||
|
||||
### Find What's Next
|
||||
|
||||
```bash
|
||||
$ bash .opencode/skills/task-management/router.sh next
|
||||
|
||||
=== Ready Tasks (deps satisfied) ===
|
||||
|
||||
[my-feature]
|
||||
06 - Implement API endpoint [sequential]
|
||||
08 - Write unit tests [parallel]
|
||||
```
|
||||
|
||||
### Mark Complete
|
||||
|
||||
```bash
|
||||
$ bash .opencode/skills/task-management/router.sh complete my-feature 05 "Implemented authentication module"
|
||||
|
||||
✓ Marked my-feature/05 as completed
|
||||
Summary: Implemented authentication module
|
||||
Progress: 6/11
|
||||
```
|
||||
|
||||
### Check Dependencies
|
||||
|
||||
```bash
|
||||
$ bash .opencode/skills/task-management/router.sh deps my-feature 07
|
||||
|
||||
=== Dependency Tree: my-feature/07 ===
|
||||
|
||||
07 - Write integration tests [pending]
|
||||
├── ✓ 05 - Implement authentication module [completed]
|
||||
└── ○ 06 - Implement API endpoint [in_progress]
|
||||
```
|
||||
|
||||
### Validate Everything
|
||||
|
||||
```bash
|
||||
$ bash .opencode/skills/task-management/router.sh validate
|
||||
|
||||
=== Validation Results ===
|
||||
|
||||
[my-feature]
|
||||
✓ All checks passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
.opencode/skills/task-management/
|
||||
├── SKILL.md # This file
|
||||
├── router.sh # CLI router (entry point)
|
||||
└── scripts/
|
||||
└── task-cli.ts # Task management CLI implementation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task File Structure
|
||||
|
||||
Tasks are stored in `.tmp/tasks/` at the project root:
|
||||
|
||||
```
|
||||
.tmp/tasks/
|
||||
├── {feature-slug}/
|
||||
│ ├── task.json # Feature-level metadata
|
||||
│ ├── subtask_01.json # Subtask definitions
|
||||
│ ├── subtask_02.json
|
||||
│ └── ...
|
||||
└── completed/
|
||||
└── {feature-slug}/ # Completed tasks
|
||||
```
|
||||
|
||||
### task.json Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-feature",
|
||||
"name": "My Feature",
|
||||
"status": "active",
|
||||
"objective": "Implement X",
|
||||
"context_files": ["docs/spec.md"],
|
||||
"reference_files": ["src/existing.ts"],
|
||||
"exit_criteria": ["Tests pass", "Code reviewed"],
|
||||
"subtask_count": 5,
|
||||
"completed_count": 2,
|
||||
"created_at": "2026-01-11T10:00:00Z",
|
||||
"completed_at": null
|
||||
}
|
||||
```
|
||||
|
||||
### subtask_##.json Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-feature-05",
|
||||
"seq": "05",
|
||||
"title": "Implement authentication",
|
||||
"status": "pending",
|
||||
"depends_on": ["03", "04"],
|
||||
"parallel": false,
|
||||
"suggested_agent": "coder-agent",
|
||||
"context_files": ["docs/auth.md"],
|
||||
"reference_files": ["src/auth-old.ts"],
|
||||
"acceptance_criteria": ["Login works", "JWT tokens valid"],
|
||||
"deliverables": ["auth.ts", "auth.test.ts"],
|
||||
"started_at": null,
|
||||
"completed_at": null,
|
||||
"completion_summary": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with TaskManager
|
||||
|
||||
The TaskManager subagent creates task files using this format. When you delegate to TaskManager:
|
||||
|
||||
```javascript
|
||||
task(
|
||||
subagent_type="TaskManager",
|
||||
description="Implement feature X",
|
||||
prompt="Break down this feature into atomic subtasks..."
|
||||
)
|
||||
```
|
||||
|
||||
TaskManager creates:
|
||||
1. `.tmp/tasks/{feature}/task.json` - Feature metadata
|
||||
2. `.tmp/tasks/{feature}/subtask_XX.json` - Individual subtasks
|
||||
|
||||
You can then use this skill to track and manage progress.
|
||||
|
||||
---
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### 1. Dependency Resolution
|
||||
Subtasks can depend on other subtasks. A task is "ready" only when all its dependencies are complete.
|
||||
|
||||
### 2. Parallel Execution
|
||||
Set `parallel: true` to indicate a subtask can run alongside other parallel tasks with satisfied dependencies.
|
||||
|
||||
### 3. Status Tracking
|
||||
- **pending** - Not started, waiting for dependencies
|
||||
- **in_progress** - Currently being worked on
|
||||
- **completed** - Finished with summary
|
||||
- **blocked** - Explicitly blocked (not waiting for deps)
|
||||
|
||||
### 4. Exit Criteria
|
||||
Each feature has exit_criteria that must be met before marking the feature complete.
|
||||
|
||||
### 5. Validation Rules
|
||||
|
||||
The `validate` command performs comprehensive checks on task files:
|
||||
|
||||
**Task-Level Validation:**
|
||||
- ✅ task.json file exists for the feature
|
||||
- ✅ Task ID matches feature slug
|
||||
- ✅ Subtask count in task.json matches actual subtask files
|
||||
- ✅ All required fields are present
|
||||
|
||||
**Subtask-Level Validation:**
|
||||
- ✅ All subtask IDs start with feature name (e.g., "my-feature-01")
|
||||
- ✅ Sequence numbers are unique and properly formatted (01, 02, etc.)
|
||||
- ✅ All dependencies reference existing subtasks
|
||||
- ✅ No circular dependencies exist
|
||||
- ✅ Each subtask has acceptance criteria defined
|
||||
- ✅ Each subtask has deliverables specified
|
||||
- ✅ Status values are valid (pending, in_progress, completed, blocked)
|
||||
|
||||
**Dependency Validation:**
|
||||
- ✅ All depends_on references point to existing subtasks
|
||||
- ✅ No task depends on itself
|
||||
- ✅ No circular dependency chains
|
||||
- ✅ Dependency graph is acyclic
|
||||
|
||||
Run `validate` regularly to catch issues early:
|
||||
```bash
|
||||
bash .opencode/skills/task-management/router.sh validate my-feature
|
||||
```
|
||||
|
||||
### 6. Context and Reference Files
|
||||
- **context_files** - Standards, conventions, and guidelines to follow
|
||||
- **reference_files** - Existing project files to look at or build upon
|
||||
|
||||
---
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### With TaskManager Subagent
|
||||
|
||||
1. **TaskManager creates tasks** → Generates `.tmp/tasks/{feature}/` structure
|
||||
2. **You use this skill to track** → Monitor progress with `status`, `next`, `blocked`
|
||||
3. **You mark tasks complete** → Use `complete` command with summaries
|
||||
4. **Skill validates integrity** → Use `validate` to check consistency
|
||||
|
||||
### With Other Subagents
|
||||
|
||||
Working agents (CoderAgent, TestEngineer, etc.) execute subtasks and report completion. Use this skill to:
|
||||
- Find next available tasks with `next`
|
||||
- Check what's blocking progress with `blocked`
|
||||
- Validate task definitions with `validate`
|
||||
|
||||
---
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Starting a New Feature
|
||||
|
||||
```bash
|
||||
# 1. TaskManager creates the task structure
|
||||
task(subagent_type="TaskManager", description="Implement feature X", ...)
|
||||
|
||||
# 2. Check what's ready
|
||||
bash .opencode/skills/task-management/router.sh next
|
||||
|
||||
# 3. Delegate first task to working agent
|
||||
task(subagent_type="CoderAgent", description="Implement subtask 01", ...)
|
||||
```
|
||||
|
||||
### Tracking Progress
|
||||
|
||||
```bash
|
||||
# Check overall status
|
||||
bash .opencode/skills/task-management/router.sh status my-feature
|
||||
|
||||
# See what's next
|
||||
bash .opencode/skills/task-management/router.sh next my-feature
|
||||
|
||||
# Check what's blocked
|
||||
bash .opencode/skills/task-management/router.sh blocked my-feature
|
||||
```
|
||||
|
||||
### Completing Tasks
|
||||
|
||||
```bash
|
||||
# After working agent finishes
|
||||
bash .opencode/skills/task-management/router.sh complete my-feature 05 "Implemented auth module with JWT support"
|
||||
|
||||
# Check progress
|
||||
bash .opencode/skills/task-management/router.sh status my-feature
|
||||
|
||||
# Find next task
|
||||
bash .opencode/skills/task-management/router.sh next my-feature
|
||||
```
|
||||
|
||||
### Validating Everything
|
||||
|
||||
```bash
|
||||
# Validate all tasks
|
||||
bash .opencode/skills/task-management/router.sh validate
|
||||
|
||||
# Validate specific feature
|
||||
bash .opencode/skills/task-management/router.sh validate my-feature
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
### 1. Use Meaningful Summaries
|
||||
When marking tasks complete, provide clear summaries:
|
||||
```bash
|
||||
# Good
|
||||
complete my-feature 05 "Implemented JWT authentication with refresh tokens and error handling"
|
||||
|
||||
# Avoid
|
||||
complete my-feature 05 "Done"
|
||||
```
|
||||
|
||||
### 2. Check Dependencies Before Starting
|
||||
```bash
|
||||
# See what a task depends on
|
||||
bash .opencode/skills/task-management/router.sh deps my-feature 07
|
||||
```
|
||||
|
||||
### 3. Identify Parallelizable Work
|
||||
```bash
|
||||
# Find tasks that can run in parallel
|
||||
bash .opencode/skills/task-management/router.sh parallel my-feature
|
||||
```
|
||||
|
||||
### 4. Regular Validation
|
||||
```bash
|
||||
# Validate regularly to catch issues early
|
||||
bash .opencode/skills/task-management/router.sh validate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "task-cli.ts not found"
|
||||
Make sure you're running from the project root or the router.sh can find it.
|
||||
|
||||
### "No tasks found"
|
||||
Run `status` to see if any tasks have been created yet. Use TaskManager to create tasks first.
|
||||
|
||||
### "Dependency not satisfied"
|
||||
Check the dependency tree with `deps` to see what's blocking the task.
|
||||
|
||||
### "Validation failed"
|
||||
Run `validate` to see specific issues, then check the JSON files in `.tmp/tasks/`.
|
||||
|
||||
---
|
||||
|
||||
## File Locations
|
||||
|
||||
- **Skill**: `.opencode/skills/task-management/`
|
||||
- **Router**: `.opencode/skills/task-management/router.sh`
|
||||
- **CLI**: `.opencode/skills/task-management/scripts/task-cli.ts`
|
||||
- **Tasks**: `.tmp/tasks/` (created by TaskManager)
|
||||
- **Documentation**: `.opencode/skills/task-management/SKILL.md` (this file)
|
||||
|
||||
---
|
||||
|
||||
**Task Management Skill** - Track, manage, and validate your feature implementations!
|
||||
108
.opencode/skills/task-management/router.sh
Normal file
108
.opencode/skills/task-management/router.sh
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
#############################################################################
|
||||
# Task Management Skill Router
|
||||
# Routes to task-cli.ts with proper path resolution and command handling
|
||||
#############################################################################
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLI_SCRIPT="$SCRIPT_DIR/scripts/task-cli.ts"
|
||||
MIGRATE_SCRIPT="$SCRIPT_DIR/scripts/migrate-schema.ts"
|
||||
|
||||
# Show help
|
||||
show_help() {
|
||||
cat << 'HELP'
|
||||
📋 Task Management Skill
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Usage: router.sh [COMMAND] [OPTIONS]
|
||||
|
||||
COMMANDS:
|
||||
status [feature] Show task status summary
|
||||
next [feature] Show next eligible tasks
|
||||
parallel [feature] Show parallelizable tasks
|
||||
deps <feature> <seq> Show dependency tree
|
||||
blocked [feature] Show blocked tasks
|
||||
complete <feature> <seq> "msg" Mark subtask complete
|
||||
validate [feature] Validate JSON files
|
||||
context <feature> Show bounded context breakdown
|
||||
contracts <feature> Show contract dependencies
|
||||
migrate <feature> [options] Migrate to enhanced schema
|
||||
help Show this help message
|
||||
|
||||
MIGRATION OPTIONS:
|
||||
--dry-run Preview migration changes
|
||||
--lines-only Add only line-number precision
|
||||
|
||||
EXAMPLES:
|
||||
./router.sh status
|
||||
./router.sh status my-feature
|
||||
./router.sh next
|
||||
./router.sh deps my-feature 05
|
||||
./router.sh complete my-feature 05 "Implemented auth module"
|
||||
./router.sh validate
|
||||
./router.sh context my-feature
|
||||
./router.sh contracts my-feature
|
||||
./router.sh migrate my-feature
|
||||
./router.sh migrate my-feature --dry-run
|
||||
|
||||
FEATURES:
|
||||
✓ Track progress across all features
|
||||
✓ Find next eligible tasks (dependencies satisfied)
|
||||
✓ Identify blocked tasks
|
||||
✓ Mark subtasks complete with summaries
|
||||
✓ Validate task integrity
|
||||
✓ Show bounded context breakdown
|
||||
✓ Show contract dependencies
|
||||
✓ Migrate to enhanced schema
|
||||
|
||||
For more info, see: .opencode/skills/task-management/SKILL.md
|
||||
HELP
|
||||
}
|
||||
|
||||
# Check if CLI script exists
|
||||
if [ ! -f "$CLI_SCRIPT" ]; then
|
||||
echo "❌ Error: task-cli.ts not found at $CLI_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find project root
|
||||
find_project_root() {
|
||||
local dir
|
||||
dir="$(pwd)"
|
||||
while [ "$dir" != "/" ]; do
|
||||
if [ -d "$dir/.git" ] || [ -f "$dir/package.json" ]; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
pwd
|
||||
return 1
|
||||
}
|
||||
|
||||
# Handle help
|
||||
if [ "$1" = "help" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||
show_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If no arguments, show help
|
||||
if [ $# -eq 0 ]; then
|
||||
show_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PROJECT_ROOT="$(find_project_root)"
|
||||
|
||||
# Route commands
|
||||
case "$1" in
|
||||
migrate)
|
||||
cd "$PROJECT_ROOT" && bunx --bun ts-node "$MIGRATE_SCRIPT" "$@"
|
||||
;;
|
||||
*)
|
||||
# Run the task CLI with all arguments
|
||||
cd "$PROJECT_ROOT" && bunx --bun ts-node "$CLI_SCRIPT" "$@"
|
||||
;;
|
||||
esac
|
||||
553
.opencode/skills/task-management/scripts/task-cli.ts
Normal file
553
.opencode/skills/task-management/scripts/task-cli.ts
Normal file
@@ -0,0 +1,553 @@
|
||||
#!/usr/bin/env bunx --bun ts-node
|
||||
/**
|
||||
* Task Management CLI
|
||||
*
|
||||
* Usage: bunx --bun ts-node task-cli.ts <command> [feature] [args...]
|
||||
*
|
||||
* Commands:
|
||||
* status [feature] - Show task status summary
|
||||
* next [feature] - Show next eligible tasks
|
||||
* parallel [feature] - Show parallelizable tasks ready to run
|
||||
* deps <feature> <seq> - Show dependency tree for a task
|
||||
* blocked [feature] - Show blocked tasks and why
|
||||
* complete <feature> <seq> "summary" - Mark task completed
|
||||
* validate [feature] - Validate JSON files and dependencies
|
||||
*
|
||||
* Task files are stored in .tmp/tasks/ at the project root:
|
||||
* .tmp/tasks/{feature-slug}/task.json
|
||||
* .tmp/tasks/{feature-slug}/subtask_01.json
|
||||
* .tmp/tasks/completed/{feature-slug}/
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Find project root (look for .git or package.json)
|
||||
function findProjectRoot(): string {
|
||||
let dir = process.cwd();
|
||||
while (dir !== path.dirname(dir)) {
|
||||
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'package.json'))) {
|
||||
return dir;
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
const PROJECT_ROOT = findProjectRoot();
|
||||
const TASKS_DIR = path.join(PROJECT_ROOT, '.tmp', 'tasks');
|
||||
const COMPLETED_DIR = path.join(TASKS_DIR, 'completed');
|
||||
|
||||
interface Task {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'active' | 'completed' | 'blocked' | 'archived';
|
||||
objective: string;
|
||||
context_files: string[];
|
||||
reference_files?: string[];
|
||||
exit_criteria: string[];
|
||||
subtask_count: number;
|
||||
completed_count: number;
|
||||
created_at: string;
|
||||
completed_at: string | null;
|
||||
}
|
||||
|
||||
interface Subtask {
|
||||
id: string;
|
||||
seq: string;
|
||||
title: string;
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'blocked';
|
||||
depends_on: string[];
|
||||
parallel: boolean;
|
||||
context_files: string[];
|
||||
reference_files?: string[];
|
||||
acceptance_criteria: string[];
|
||||
deliverables: string[];
|
||||
agent_id: string | null;
|
||||
suggested_agent?: string;
|
||||
started_at: string | null;
|
||||
completed_at: string | null;
|
||||
completion_summary: string | null;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function getFeatureDirs(): string[] {
|
||||
if (!fs.existsSync(TASKS_DIR)) return [];
|
||||
return fs.readdirSync(TASKS_DIR).filter((f: string) => {
|
||||
const fullPath = path.join(TASKS_DIR, f);
|
||||
return fs.statSync(fullPath).isDirectory() && f !== 'completed';
|
||||
});
|
||||
}
|
||||
|
||||
function loadTask(feature: string): Task | null {
|
||||
const taskPath = path.join(TASKS_DIR, feature, 'task.json');
|
||||
if (!fs.existsSync(taskPath)) return null;
|
||||
return JSON.parse(fs.readFileSync(taskPath, 'utf-8'));
|
||||
}
|
||||
|
||||
function loadSubtasks(feature: string): Subtask[] {
|
||||
const featureDir = path.join(TASKS_DIR, feature);
|
||||
if (!fs.existsSync(featureDir)) return [];
|
||||
|
||||
const files = fs.readdirSync(featureDir)
|
||||
.filter((f: string) => f.match(/^subtask_\d{2}\.json$/))
|
||||
.sort();
|
||||
|
||||
return files.map((f: string) => JSON.parse(fs.readFileSync(path.join(featureDir, f), 'utf-8')));
|
||||
}
|
||||
|
||||
function saveSubtask(feature: string, subtask: Subtask): void {
|
||||
const subtaskPath = path.join(TASKS_DIR, feature, `subtask_${subtask.seq}.json`);
|
||||
fs.writeFileSync(subtaskPath, JSON.stringify(subtask, null, 2));
|
||||
}
|
||||
|
||||
function saveTask(feature: string, task: Task): void {
|
||||
const taskPath = path.join(TASKS_DIR, feature, 'task.json');
|
||||
fs.writeFileSync(taskPath, JSON.stringify(task, null, 2));
|
||||
}
|
||||
|
||||
// Commands
|
||||
function cmdStatus(feature?: string): void {
|
||||
const features = feature ? [feature] : getFeatureDirs();
|
||||
|
||||
if (features.length === 0) {
|
||||
console.log('No active features found.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const f of features) {
|
||||
const task = loadTask(f);
|
||||
const subtasks = loadSubtasks(f);
|
||||
|
||||
if (!task) {
|
||||
console.log(`\n[${f}] - No task.json found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const counts = {
|
||||
pending: subtasks.filter(s => s.status === 'pending').length,
|
||||
in_progress: subtasks.filter(s => s.status === 'in_progress').length,
|
||||
completed: subtasks.filter(s => s.status === 'completed').length,
|
||||
blocked: subtasks.filter(s => s.status === 'blocked').length,
|
||||
};
|
||||
|
||||
const progress = subtasks.length > 0
|
||||
? Math.round((counts.completed / subtasks.length) * 100)
|
||||
: 0;
|
||||
|
||||
console.log(`\n[${f}] ${task.name}`);
|
||||
console.log(` Status: ${task.status} | Progress: ${progress}% (${counts.completed}/${subtasks.length})`);
|
||||
console.log(` Pending: ${counts.pending} | In Progress: ${counts.in_progress} | Completed: ${counts.completed} | Blocked: ${counts.blocked}`);
|
||||
}
|
||||
}
|
||||
|
||||
function cmdNext(feature?: string): void {
|
||||
const features = feature ? [feature] : getFeatureDirs();
|
||||
|
||||
console.log('\n=== Ready Tasks (deps satisfied) ===\n');
|
||||
|
||||
for (const f of features) {
|
||||
const subtasks = loadSubtasks(f);
|
||||
const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq));
|
||||
|
||||
const ready = subtasks.filter(s => {
|
||||
if (s.status !== 'pending') return false;
|
||||
return s.depends_on.every(dep => completedSeqs.has(dep));
|
||||
});
|
||||
|
||||
if (ready.length > 0) {
|
||||
console.log(`[${f}]`);
|
||||
for (const s of ready) {
|
||||
const parallel = s.parallel ? '[parallel]' : '[sequential]';
|
||||
console.log(` ${s.seq} - ${s.title} ${parallel}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cmdParallel(feature?: string): void {
|
||||
const features = feature ? [feature] : getFeatureDirs();
|
||||
|
||||
console.log('\n=== Parallelizable Tasks Ready Now ===\n');
|
||||
|
||||
for (const f of features) {
|
||||
const subtasks = loadSubtasks(f);
|
||||
const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq));
|
||||
|
||||
const parallel = subtasks.filter(s => {
|
||||
if (s.status !== 'pending') return false;
|
||||
if (!s.parallel) return false;
|
||||
return s.depends_on.every(dep => completedSeqs.has(dep));
|
||||
});
|
||||
|
||||
if (parallel.length > 0) {
|
||||
console.log(`[${f}] - ${parallel.length} parallel tasks:`);
|
||||
for (const s of parallel) {
|
||||
console.log(` ${s.seq} - ${s.title}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cmdDeps(feature: string, seq: string): void {
|
||||
const subtasks = loadSubtasks(feature);
|
||||
const target = subtasks.find(s => s.seq === seq);
|
||||
|
||||
if (!target) {
|
||||
console.log(`Task ${seq} not found in ${feature}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\n=== Dependency Tree: ${feature}/${seq} ===\n`);
|
||||
console.log(`${seq} - ${target.title} [${target.status}]`);
|
||||
|
||||
if (target.depends_on.length === 0) {
|
||||
console.log(' └── (no dependencies)');
|
||||
return;
|
||||
}
|
||||
|
||||
const printDeps = (seqs: string[], indent: string = ' '): void => {
|
||||
for (let i = 0; i < seqs.length; i++) {
|
||||
const depSeq = seqs[i];
|
||||
const dep = subtasks.find(s => s.seq === depSeq);
|
||||
const isLast = i === seqs.length - 1;
|
||||
const branch = isLast ? '└──' : '├──';
|
||||
|
||||
if (dep) {
|
||||
const statusIcon = dep.status === 'completed' ? '✓' : dep.status === 'in_progress' ? '~' : '○';
|
||||
console.log(`${indent}${branch} ${statusIcon} ${depSeq} - ${dep.title} [${dep.status}]`);
|
||||
if (dep.depends_on.length > 0) {
|
||||
const newIndent = indent + (isLast ? ' ' : '│ ');
|
||||
printDeps(dep.depends_on, newIndent);
|
||||
}
|
||||
} else {
|
||||
console.log(`${indent}${branch} ? ${depSeq} - NOT FOUND`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
printDeps(target.depends_on);
|
||||
}
|
||||
|
||||
function cmdBlocked(feature?: string): void {
|
||||
const features = feature ? [feature] : getFeatureDirs();
|
||||
|
||||
console.log('\n=== Blocked Tasks ===\n');
|
||||
|
||||
for (const f of features) {
|
||||
const subtasks = loadSubtasks(f);
|
||||
const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq));
|
||||
|
||||
const blocked = subtasks.filter(s => {
|
||||
if (s.status === 'blocked') return true;
|
||||
if (s.status !== 'pending') return false;
|
||||
return !s.depends_on.every(dep => completedSeqs.has(dep));
|
||||
});
|
||||
|
||||
if (blocked.length > 0) {
|
||||
console.log(`[${f}]`);
|
||||
for (const s of blocked) {
|
||||
const waitingFor = s.depends_on.filter(dep => !completedSeqs.has(dep));
|
||||
const reason = s.status === 'blocked'
|
||||
? 'explicitly blocked'
|
||||
: `waiting: ${waitingFor.join(', ')}`;
|
||||
console.log(` ${s.seq} - ${s.title} (${reason})`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cmdComplete(feature: string, seq: string, summary: string): void {
|
||||
if (summary.length > 200) {
|
||||
console.log('Error: Summary must be max 200 characters');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const subtasks = loadSubtasks(feature);
|
||||
const subtask = subtasks.find(s => s.seq === seq);
|
||||
|
||||
if (!subtask) {
|
||||
console.log(`Task ${seq} not found in ${feature}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
subtask.status = 'completed';
|
||||
subtask.completed_at = new Date().toISOString();
|
||||
subtask.completion_summary = summary;
|
||||
|
||||
saveSubtask(feature, subtask);
|
||||
|
||||
// Update task.json counts
|
||||
const task = loadTask(feature);
|
||||
if (task) {
|
||||
const newSubtasks = loadSubtasks(feature);
|
||||
task.completed_count = newSubtasks.filter(s => s.status === 'completed').length;
|
||||
saveTask(feature, task);
|
||||
}
|
||||
|
||||
console.log(`\n✓ Marked ${feature}/${seq} as completed`);
|
||||
console.log(` Summary: ${summary}`);
|
||||
|
||||
if (task) {
|
||||
console.log(` Progress: ${task.completed_count}/${task.subtask_count}`);
|
||||
}
|
||||
}
|
||||
|
||||
function cmdValidate(feature?: string): void {
|
||||
const features = feature ? [feature] : getFeatureDirs();
|
||||
let hasErrors = false;
|
||||
|
||||
const validTaskStatuses = new Set(['active', 'completed', 'blocked', 'archived']);
|
||||
const validSubtaskStatuses = new Set(['pending', 'in_progress', 'completed', 'blocked']);
|
||||
|
||||
const requiredTaskFields = [
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
'objective',
|
||||
'context_files',
|
||||
'exit_criteria',
|
||||
'subtask_count',
|
||||
'completed_count',
|
||||
'created_at',
|
||||
'completed_at',
|
||||
];
|
||||
|
||||
const requiredSubtaskFields = [
|
||||
'id',
|
||||
'seq',
|
||||
'title',
|
||||
'status',
|
||||
'depends_on',
|
||||
'parallel',
|
||||
'context_files',
|
||||
'acceptance_criteria',
|
||||
'deliverables',
|
||||
'agent_id',
|
||||
'started_at',
|
||||
'completed_at',
|
||||
'completion_summary',
|
||||
];
|
||||
|
||||
const hasField = (obj: any, field: string): boolean => Object.prototype.hasOwnProperty.call(obj, field);
|
||||
const isStringArray = (value: any): boolean => Array.isArray(value) && value.every(v => typeof v === 'string');
|
||||
|
||||
console.log('\n=== Validation Results ===\n');
|
||||
|
||||
for (const f of features) {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Check task.json exists
|
||||
const task = loadTask(f);
|
||||
if (!task) {
|
||||
errors.push('Missing task.json');
|
||||
}
|
||||
|
||||
// Load and validate subtasks
|
||||
const subtasks = loadSubtasks(f);
|
||||
const seqCounts = new Map<string, number>();
|
||||
for (const s of subtasks) {
|
||||
const seq = typeof s.seq === 'string' ? s.seq : '';
|
||||
seqCounts.set(seq, (seqCounts.get(seq) || 0) + 1);
|
||||
}
|
||||
const seqs = new Set(subtasks.map(s => s.seq));
|
||||
|
||||
if (task) {
|
||||
// Required fields in task.json
|
||||
for (const field of requiredTaskFields) {
|
||||
if (!hasField(task, field)) {
|
||||
errors.push(`task.json: missing required field '${field}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Task ID should match feature slug
|
||||
if (task.id !== f) {
|
||||
errors.push(`task.json id ('${task.id}') should match feature slug ('${f}')`);
|
||||
}
|
||||
|
||||
// Task status should be valid
|
||||
if (!validTaskStatuses.has(task.status)) {
|
||||
errors.push(`task.json: invalid status '${task.status}'`);
|
||||
}
|
||||
|
||||
// Basic type checks for key task fields
|
||||
if (!isStringArray(task.context_files)) {
|
||||
errors.push('task.json: context_files must be string[]');
|
||||
}
|
||||
if (hasField(task, 'reference_files') && task.reference_files !== undefined && !isStringArray(task.reference_files)) {
|
||||
errors.push('task.json: reference_files must be string[] when present');
|
||||
}
|
||||
if (!isStringArray(task.exit_criteria)) {
|
||||
errors.push('task.json: exit_criteria must be string[]');
|
||||
}
|
||||
if (typeof task.subtask_count !== 'number') {
|
||||
errors.push('task.json: subtask_count must be number');
|
||||
}
|
||||
if (typeof task.completed_count !== 'number') {
|
||||
errors.push('task.json: completed_count must be number');
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of subtasks) {
|
||||
// Required fields in subtask files
|
||||
for (const field of requiredSubtaskFields) {
|
||||
if (!hasField(s, field)) {
|
||||
errors.push(`${s.seq || '??'}: missing required field '${field}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Sequence format and uniqueness
|
||||
if (!/^\d{2}$/.test(s.seq)) {
|
||||
errors.push(`${s.seq}: sequence must be 2 digits (e.g., 01, 02)`);
|
||||
}
|
||||
if ((seqCounts.get(s.seq) || 0) > 1) {
|
||||
errors.push(`${s.seq}: duplicate sequence number`);
|
||||
}
|
||||
|
||||
// Check ID format
|
||||
if (!s.id.startsWith(f)) {
|
||||
errors.push(`${s.seq}: ID should start with feature name`);
|
||||
}
|
||||
|
||||
// Status should be valid
|
||||
if (!validSubtaskStatuses.has(s.status)) {
|
||||
errors.push(`${s.seq}: invalid status '${s.status}'`);
|
||||
}
|
||||
|
||||
// Type checks
|
||||
if (!isStringArray(s.depends_on)) {
|
||||
errors.push(`${s.seq}: depends_on must be string[]`);
|
||||
}
|
||||
if (typeof s.parallel !== 'boolean') {
|
||||
errors.push(`${s.seq}: parallel must be boolean`);
|
||||
}
|
||||
if (!isStringArray(s.context_files)) {
|
||||
errors.push(`${s.seq}: context_files must be string[]`);
|
||||
}
|
||||
if (hasField(s, 'reference_files') && s.reference_files !== undefined && !isStringArray(s.reference_files)) {
|
||||
errors.push(`${s.seq}: reference_files must be string[] when present`);
|
||||
}
|
||||
if (!isStringArray(s.acceptance_criteria)) {
|
||||
errors.push(`${s.seq}: acceptance_criteria must be string[]`);
|
||||
} else if (s.acceptance_criteria.length === 0) {
|
||||
errors.push(`${s.seq}: No acceptance criteria defined`);
|
||||
}
|
||||
if (!isStringArray(s.deliverables)) {
|
||||
errors.push(`${s.seq}: deliverables must be string[]`);
|
||||
} else if (s.deliverables.length === 0) {
|
||||
errors.push(`${s.seq}: No deliverables defined`);
|
||||
}
|
||||
|
||||
// Self dependency is invalid
|
||||
if (Array.isArray(s.depends_on) && s.depends_on.includes(s.seq)) {
|
||||
errors.push(`${s.seq}: task cannot depend on itself`);
|
||||
}
|
||||
|
||||
// Check for missing dependencies
|
||||
for (const dep of (Array.isArray(s.depends_on) ? s.depends_on : [])) {
|
||||
if (!seqs.has(dep)) {
|
||||
errors.push(`${s.seq}: depends on non-existent task ${dep}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for circular dependencies
|
||||
const visited = new Set<string>();
|
||||
const checkCircular = (seq: string, path: string[]): boolean => {
|
||||
if (path.includes(seq)) {
|
||||
errors.push(`${s.seq}: circular dependency detected: ${[...path, seq].join(' -> ')}`);
|
||||
return true;
|
||||
}
|
||||
if (visited.has(seq)) return false;
|
||||
visited.add(seq);
|
||||
|
||||
const task = subtasks.find(t => t.seq === seq);
|
||||
if (task) {
|
||||
for (const dep of task.depends_on) {
|
||||
if (checkCircular(dep, [...path, seq])) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
checkCircular(s.seq, []);
|
||||
}
|
||||
|
||||
// Check counts match
|
||||
if (task && task.subtask_count !== subtasks.length) {
|
||||
errors.push(`task.json subtask_count (${task.subtask_count}) doesn't match actual count (${subtasks.length})`);
|
||||
}
|
||||
|
||||
// Print results
|
||||
console.log(`[${f}]`);
|
||||
if (errors.length === 0) {
|
||||
console.log(' ✓ All checks passed');
|
||||
} else {
|
||||
for (const e of errors) {
|
||||
console.log(` ✗ ERROR: ${e}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
process.exit(hasErrors ? 1 : 0);
|
||||
}
|
||||
|
||||
// Main
|
||||
const [,, command, ...args] = process.argv;
|
||||
|
||||
switch (command) {
|
||||
case 'status':
|
||||
cmdStatus(args[0]);
|
||||
break;
|
||||
case 'next':
|
||||
cmdNext(args[0]);
|
||||
break;
|
||||
case 'parallel':
|
||||
cmdParallel(args[0]);
|
||||
break;
|
||||
case 'deps':
|
||||
if (args.length < 2) {
|
||||
console.log('Usage: deps <feature> <seq>');
|
||||
process.exit(1);
|
||||
}
|
||||
cmdDeps(args[0], args[1]);
|
||||
break;
|
||||
case 'blocked':
|
||||
cmdBlocked(args[0]);
|
||||
break;
|
||||
case 'complete':
|
||||
if (args.length < 3) {
|
||||
console.log('Usage: complete <feature> <seq> "summary"');
|
||||
process.exit(1);
|
||||
}
|
||||
cmdComplete(args[0], args[1], args.slice(2).join(' '));
|
||||
break;
|
||||
case 'validate':
|
||||
cmdValidate(args[0]);
|
||||
break;
|
||||
default:
|
||||
console.log(`
|
||||
Task Management CLI
|
||||
|
||||
Usage: bunx --bun ts-node task-cli.ts <command> [feature] [args...]
|
||||
|
||||
Task files are stored in: .tmp/tasks/{feature-slug}/
|
||||
|
||||
Commands:
|
||||
status [feature] Show task status summary
|
||||
next [feature] Show next eligible tasks (deps satisfied)
|
||||
parallel [feature] Show parallelizable tasks ready to run
|
||||
deps <feature> <seq> Show dependency tree for a task
|
||||
blocked [feature] Show blocked tasks and why
|
||||
complete <feature> <seq> "summary" Mark task completed with summary
|
||||
validate [feature] Validate JSON files and dependencies
|
||||
|
||||
Examples:
|
||||
bunx --bun ts-node task-cli.ts status
|
||||
bunx --bun ts-node task-cli.ts next my-feature
|
||||
bunx --bun ts-node task-cli.ts complete my-feature 02 "Implemented auth module"
|
||||
`);
|
||||
}
|
||||
Reference in New Issue
Block a user