chore: install openagent opencode

Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
2026-04-07 11:31:26 -04:00
parent b4c03ff25e
commit c2263602c4
204 changed files with 38010 additions and 0 deletions

View 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!

View 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

View 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"
`);
}