Add list command with subtasks option and update documentation
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
# API Keys (Required)
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here # Format: sk-ant-api03-...
|
||||
PERPLEXITY_API_KEY=your_perplexity_api_key_here # Format: pplx-...
|
||||
|
||||
# Model Configuration
|
||||
MODEL=claude-3-7-sonnet-20250219 # Recommended models: claude-3-7-sonnet-20250219, claude-3-opus-20240229
|
||||
PERPLEXITY_MODEL=sonar-small-online # Perplexity model for research-backed subtasks
|
||||
MAX_TOKENS=4000 # Maximum tokens for model responses
|
||||
TEMPERATURE=0.7 # Temperature for model responses (0.0-1.0)
|
||||
|
||||
|
||||
66
README.md
66
README.md
@@ -7,6 +7,27 @@ A task management system for AI-driven development with Claude, designed to work
|
||||
- Node.js 14.0.0 or higher
|
||||
- Anthropic API key (Claude API)
|
||||
- Anthropic SDK version 0.39.0 or higher
|
||||
- OpenAI SDK (for Perplexity API integration, optional)
|
||||
|
||||
## Configuration
|
||||
|
||||
The script can be configured through environment variables in a `.env` file at the root of the project:
|
||||
|
||||
### Required Configuration
|
||||
- `ANTHROPIC_API_KEY`: Your Anthropic API key for Claude
|
||||
|
||||
### Optional Configuration
|
||||
- `MODEL`: Specify which Claude model to use (default: "claude-3-7-sonnet-20250219")
|
||||
- `MAX_TOKENS`: Maximum tokens for model responses (default: 4000)
|
||||
- `TEMPERATURE`: Temperature for model responses (default: 0.7)
|
||||
- `PERPLEXITY_API_KEY`: Your Perplexity API key for research-backed subtask generation
|
||||
- `PERPLEXITY_MODEL`: Specify which Perplexity model to use (default: "sonar-medium-online")
|
||||
- `DEBUG`: Enable debug logging (default: false)
|
||||
- `LOG_LEVEL`: Log level - debug, info, warn, error (default: info)
|
||||
- `DEFAULT_SUBTASKS`: Default number of subtasks when expanding (default: 3)
|
||||
- `DEFAULT_PRIORITY`: Default priority for generated tasks (default: medium)
|
||||
- `PROJECT_NAME`: Override default project name in tasks.json
|
||||
- `PROJECT_VERSION`: Override default version in tasks.json
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -105,6 +126,7 @@ What tasks are available to work on next?
|
||||
|
||||
The agent will:
|
||||
- Run `node scripts/dev.js list` to see all tasks
|
||||
- Run `node scripts/dev.js list --with-subtasks` to see tasks with their subtasks
|
||||
- Analyze dependencies to determine which tasks are ready to be worked on
|
||||
- Prioritize tasks based on priority level and ID order
|
||||
- Suggest the next task(s) to implement
|
||||
@@ -194,6 +216,26 @@ The agent will execute:
|
||||
node scripts/dev.js expand --all
|
||||
```
|
||||
|
||||
For research-backed subtask generation using Perplexity AI:
|
||||
```
|
||||
Please break down task 5 using research-backed generation.
|
||||
```
|
||||
|
||||
The agent will execute:
|
||||
```bash
|
||||
node scripts/dev.js expand --id=5 --research
|
||||
```
|
||||
|
||||
You can also apply research-backed generation to all tasks:
|
||||
```
|
||||
Please break down all pending tasks using research-backed generation.
|
||||
```
|
||||
|
||||
The agent will execute:
|
||||
```bash
|
||||
node scripts/dev.js expand --all --research
|
||||
```
|
||||
|
||||
## Manual Command Reference
|
||||
|
||||
While the Cursor agent will handle most commands for you, you can also run them manually:
|
||||
@@ -208,6 +250,21 @@ npm run parse-prd -- --input=<prd-file.txt>
|
||||
npm run list
|
||||
```
|
||||
|
||||
# List tasks with a specific status
|
||||
```bash
|
||||
npm run dev -- list --status=<status>
|
||||
```
|
||||
|
||||
# List tasks with subtasks
|
||||
```bash
|
||||
npm run dev -- list --with-subtasks
|
||||
```
|
||||
|
||||
# List tasks with a specific status and include subtasks
|
||||
```bash
|
||||
npm run dev -- list --status=<status> --with-subtasks
|
||||
```
|
||||
|
||||
### Update Tasks
|
||||
```bash
|
||||
npm run dev -- update --from=<id> --prompt="<prompt>"
|
||||
@@ -232,6 +289,15 @@ or
|
||||
npm run dev -- expand --all
|
||||
```
|
||||
|
||||
For research-backed subtask generation:
|
||||
```bash
|
||||
npm run dev -- expand --id=<id> --research
|
||||
```
|
||||
or
|
||||
```bash
|
||||
npm run dev -- expand --all --research
|
||||
```
|
||||
|
||||
## Task Structure
|
||||
|
||||
Tasks in tasks.json have the following structure:
|
||||
|
||||
184
package-lock.json
generated
184
package-lock.json
generated
@@ -1,32 +1,40 @@
|
||||
{
|
||||
"name": "mcp-saas",
|
||||
"name": "claude-task-master",
|
||||
"version": "1.3.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mcp-saas",
|
||||
"name": "claude-task-master",
|
||||
"version": "1.3.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.16.0",
|
||||
"dotenv": "^16.4.7"
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^11.1.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"openai": "^4.86.1"
|
||||
},
|
||||
"bin": {
|
||||
"claude-task-init": "scripts/init.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.16.1.tgz",
|
||||
"integrity": "sha512-vHgvfWEyFy5ktqam56Nrhv8MVa7EJthsRYNi+1OrFFfyrj9tR2/aji1QbVbQjYU/pPhPFaYrdCEC/MLPFrmKwA==",
|
||||
"version": "0.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz",
|
||||
"integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"agentkeepalive": "^4.2.1",
|
||||
"digest-fetch": "^1.3.0",
|
||||
"form-data-encoder": "1.7.2",
|
||||
"formdata-node": "^4.3.2",
|
||||
"node-fetch": "^2.6.7",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
@@ -72,17 +80,27 @@
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base-64": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -96,15 +114,40 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -117,13 +160,13 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
|
||||
"license": "BSD-3-Clause",
|
||||
"node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
@@ -135,16 +178,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/digest-fetch": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
|
||||
"integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"base-64": "^0.1.0",
|
||||
"md5": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
@@ -326,6 +359,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@@ -374,12 +416,6 @@
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -389,17 +425,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "~1.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -466,6 +491,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "4.86.1",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.86.1.tgz",
|
||||
"integrity": "sha512-x3iCLyaC3yegFVZaxOmrYJjitKxZ9hpVbLi+ZlT5UHuHTMlEQEbKXkGOM78z9qm2T5GF+XRUZCP2/aV4UPFPJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"agentkeepalive": "^4.2.1",
|
||||
"form-data-encoder": "1.7.2",
|
||||
"formdata-node": "^4.3.2",
|
||||
"node-fetch": "^2.6.7"
|
||||
},
|
||||
"bin": {
|
||||
"openai": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
@@ -478,15 +545,6 @@
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-task-master",
|
||||
"version": "1.3.1",
|
||||
"version": "1.3.2",
|
||||
"description": "A task management system for AI-driven development with Claude",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@@ -28,7 +28,8 @@
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^11.1.0",
|
||||
"dotenv": "^16.3.1"
|
||||
"dotenv": "^16.3.1",
|
||||
"openai": "^4.86.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -12,6 +12,7 @@ In an AI-driven development process—particularly with tools like [Cursor](http
|
||||
4. **Generate** individual task files (e.g., `task_001.txt`) for easy reference or to feed into an AI coding workflow.
|
||||
5. **Set task status**—mark tasks as `done`, `pending`, or `deferred` based on progress.
|
||||
6. **Expand** tasks with subtasks—break down complex tasks into smaller, more manageable subtasks.
|
||||
7. **Research-backed subtask generation**—use Perplexity AI to generate more informed and contextually relevant subtasks.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -24,6 +25,8 @@ The script can be configured through environment variables in a `.env` file at t
|
||||
- `MODEL`: Specify which Claude model to use (default: "claude-3-7-sonnet-20250219")
|
||||
- `MAX_TOKENS`: Maximum tokens for model responses (default: 4000)
|
||||
- `TEMPERATURE`: Temperature for model responses (default: 0.7)
|
||||
- `PERPLEXITY_API_KEY`: Your Perplexity API key for research-backed subtask generation
|
||||
- `PERPLEXITY_MODEL`: Specify which Perplexity model to use (default: "sonar-medium-online")
|
||||
- `DEBUG`: Enable debug logging (default: false)
|
||||
- `LOG_LEVEL`: Log level - debug, info, warn, error (default: info)
|
||||
- `DEFAULT_SUBTASKS`: Default number of subtasks when expanding (default: 3)
|
||||
@@ -56,6 +59,24 @@ The script can be configured through environment variables in a `.env` file at t
|
||||
|
||||
Run `node scripts/dev.js` without arguments to see detailed usage information.
|
||||
|
||||
## Listing Tasks
|
||||
|
||||
The `list` command allows you to view all tasks and their status:
|
||||
|
||||
```bash
|
||||
# List all tasks
|
||||
node scripts/dev.js list
|
||||
|
||||
# List tasks with a specific status
|
||||
node scripts/dev.js list --status=pending
|
||||
|
||||
# List tasks and include their subtasks
|
||||
node scripts/dev.js list --with-subtasks
|
||||
|
||||
# List tasks with a specific status and include their subtasks
|
||||
node scripts/dev.js list --status=pending --with-subtasks
|
||||
```
|
||||
|
||||
## Expanding Tasks
|
||||
|
||||
The `expand` command allows you to break down tasks into subtasks for more detailed implementation:
|
||||
@@ -75,12 +96,35 @@ node scripts/dev.js expand --all
|
||||
|
||||
# Force regeneration of subtasks for all pending tasks
|
||||
node scripts/dev.js expand --all --force
|
||||
|
||||
# Use Perplexity AI for research-backed subtask generation
|
||||
node scripts/dev.js expand --id=3 --research
|
||||
|
||||
# Use Perplexity AI for research-backed generation on all pending tasks
|
||||
node scripts/dev.js expand --all --research
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Tasks marked as 'done' or 'completed' are always skipped
|
||||
- By default, tasks that already have subtasks are skipped unless `--force` is used
|
||||
- Subtasks include title, description, dependencies, and acceptance criteria
|
||||
- The `--research` flag uses Perplexity AI to generate more informed and contextually relevant subtasks
|
||||
- If Perplexity API is unavailable, the script will fall back to using Anthropic's Claude
|
||||
|
||||
## AI Integration
|
||||
|
||||
The script integrates with two AI services:
|
||||
|
||||
1. **Anthropic Claude**: Used for parsing PRDs, generating tasks, and creating subtasks.
|
||||
2. **Perplexity AI**: Used for research-backed subtask generation when the `--research` flag is specified.
|
||||
|
||||
The Perplexity integration uses the OpenAI client to connect to Perplexity's API, which provides enhanced research capabilities for generating more informed subtasks. If the Perplexity API is unavailable or encounters an error, the script will automatically fall back to using Anthropic's Claude.
|
||||
|
||||
To use the Perplexity integration:
|
||||
1. Obtain a Perplexity API key
|
||||
2. Add `PERPLEXITY_API_KEY` to your `.env` file
|
||||
3. Optionally specify `PERPLEXITY_MODEL` in your `.env` file (default: "sonar-medium-online")
|
||||
4. Use the `--research` flag with the `expand` command
|
||||
|
||||
## Logging
|
||||
|
||||
|
||||
610
scripts/dev.js
610
scripts/dev.js
@@ -42,24 +42,43 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import readline from 'readline';
|
||||
import { program } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { Anthropic } from '@anthropic-ai/sdk';
|
||||
import OpenAI from 'openai';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Load environment variables from .env file
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
// Configure Anthropic client
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
|
||||
// Configure OpenAI client for Perplexity
|
||||
const perplexity = new OpenAI({
|
||||
apiKey: process.env.PERPLEXITY_API_KEY,
|
||||
baseURL: 'https://api.perplexity.ai',
|
||||
});
|
||||
|
||||
// Model configuration
|
||||
const MODEL = process.env.MODEL || 'claude-3-7-sonnet-20250219';
|
||||
const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || 'sonar-small-online';
|
||||
const MAX_TOKENS = parseInt(process.env.MAX_TOKENS || '4000');
|
||||
const TEMPERATURE = parseFloat(process.env.TEMPERATURE || '0.7');
|
||||
|
||||
// Set up configuration with environment variables or defaults
|
||||
const CONFIG = {
|
||||
model: process.env.MODEL || "claude-3-7-sonnet-20250219",
|
||||
maxTokens: parseInt(process.env.MAX_TOKENS || "4000"),
|
||||
temperature: parseFloat(process.env.TEMPERATURE || "0.7"),
|
||||
model: MODEL,
|
||||
maxTokens: MAX_TOKENS,
|
||||
temperature: TEMPERATURE,
|
||||
debug: process.env.DEBUG === "true",
|
||||
logLevel: process.env.LOG_LEVEL || "info",
|
||||
defaultSubtasks: parseInt(process.env.DEFAULT_SUBTASKS || "3"),
|
||||
@@ -95,10 +114,6 @@ function log(level, ...args) {
|
||||
}
|
||||
}
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
|
||||
function readJSON(filepath) {
|
||||
if (!fs.existsSync(filepath)) return null;
|
||||
const content = fs.readFileSync(filepath, 'utf8');
|
||||
@@ -613,7 +628,7 @@ function setTaskStatus(tasksPath, taskIdInput, newStatus) {
|
||||
//
|
||||
// 5) list tasks
|
||||
//
|
||||
function listTasks(tasksPath) {
|
||||
function listTasks(tasksPath, statusFilter, withSubtasks = false) {
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
@@ -621,114 +636,138 @@ function listTasks(tasksPath) {
|
||||
}
|
||||
|
||||
log('info', `Tasks in ${tasksPath}:`);
|
||||
data.tasks.forEach(t => {
|
||||
|
||||
// Filter tasks by status if a filter is provided
|
||||
const filteredTasks = statusFilter
|
||||
? data.tasks.filter(t => t.status === statusFilter)
|
||||
: data.tasks;
|
||||
|
||||
filteredTasks.forEach(t => {
|
||||
log('info', `- ID=${t.id}, [${t.status}] ${t.title}`);
|
||||
|
||||
// Display subtasks if requested and they exist
|
||||
if (withSubtasks && t.subtasks && t.subtasks.length > 0) {
|
||||
t.subtasks.forEach(st => {
|
||||
log('info', ` └─ ID=${t.id}.${st.id}, [${st.status || 'pending'}] ${st.title}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// If no tasks match the filter, show a message
|
||||
if (filteredTasks.length === 0) {
|
||||
log('info', `No tasks found${statusFilter ? ` with status '${statusFilter}'` : ''}.`);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 6) expand task with subtasks
|
||||
//
|
||||
async function expandTask(tasksPath, taskId, numSubtasks, additionalContext = '') {
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
process.exit(1);
|
||||
/**
|
||||
* Expand a task by generating subtasks
|
||||
* @param {string} taskId - The ID of the task to expand
|
||||
* @param {number} numSubtasks - The number of subtasks to generate
|
||||
* @param {boolean} useResearch - Whether to use Perplexity for research-backed subtask generation
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function expandTask(taskId, numSubtasks = CONFIG.defaultSubtasks, useResearch = false) {
|
||||
try {
|
||||
// Get the tasks
|
||||
const tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json'));
|
||||
const task = tasksData.tasks.find(t => t.id === parseInt(taskId));
|
||||
|
||||
if (!task) {
|
||||
console.error(chalk.red(`Task with ID ${taskId} not found.`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the task is already completed
|
||||
if (task.status === 'completed' || task.status === 'done') {
|
||||
console.log(chalk.yellow(`Task ${taskId} is already completed. Skipping expansion.`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize subtasks array if it doesn't exist
|
||||
if (!task.subtasks) {
|
||||
task.subtasks = [];
|
||||
}
|
||||
|
||||
// Calculate the next subtask ID
|
||||
const nextSubtaskId = task.subtasks.length > 0
|
||||
? Math.max(...task.subtasks.map(st => st.id)) + 1
|
||||
: 1;
|
||||
|
||||
// Generate subtasks
|
||||
let subtasks;
|
||||
if (useResearch) {
|
||||
console.log(chalk.blue(`Using Perplexity AI for research-backed subtask generation...`));
|
||||
subtasks = await generateSubtasksWithPerplexity(task, numSubtasks, nextSubtaskId);
|
||||
} else {
|
||||
subtasks = await generateSubtasks(task, numSubtasks, nextSubtaskId);
|
||||
}
|
||||
|
||||
// Add the subtasks to the task
|
||||
task.subtasks = [...task.subtasks, ...subtasks];
|
||||
|
||||
// Save the updated tasks
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'tasks', 'tasks.json'),
|
||||
JSON.stringify(tasksData, null, 2)
|
||||
);
|
||||
|
||||
console.log(chalk.green(`Added ${subtasks.length} subtasks to task ${taskId}.`));
|
||||
|
||||
// Log the added subtasks
|
||||
subtasks.forEach(st => {
|
||||
console.log(chalk.cyan(` ${st.id}. ${st.title}`));
|
||||
console.log(chalk.gray(` ${st.description.substring(0, 100)}${st.description.length > 100 ? '...' : ''}`));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error expanding task:'), error);
|
||||
}
|
||||
|
||||
// Use default subtasks count from config if not specified
|
||||
numSubtasks = numSubtasks || CONFIG.defaultSubtasks;
|
||||
|
||||
const task = data.tasks.find(t => t.id === taskId);
|
||||
if (!task) {
|
||||
log('error', `Task with ID=${taskId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Skip tasks that are already completed
|
||||
if (task.status === 'done' || task.status === 'completed') {
|
||||
log('info', `Skipping task ID=${taskId} "${task.title}" - task is already marked as ${task.status}.`);
|
||||
log('info', `Use set-status command to change the status if you want to modify this task.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
log('info', `Expanding task: ${task.title}`);
|
||||
|
||||
// Initialize subtasks array if it doesn't exist
|
||||
if (!task.subtasks) {
|
||||
task.subtasks = [];
|
||||
}
|
||||
|
||||
// Calculate next subtask ID
|
||||
const nextSubtaskId = task.subtasks.length > 0
|
||||
? Math.max(...task.subtasks.map(st => st.id)) + 1
|
||||
: 1;
|
||||
|
||||
// Generate subtasks using Claude
|
||||
const subtasks = await generateSubtasks(task, numSubtasks, nextSubtaskId, additionalContext);
|
||||
|
||||
// Add new subtasks to the task
|
||||
task.subtasks = [...task.subtasks, ...subtasks];
|
||||
|
||||
// Update tasks.json
|
||||
writeJSON(tasksPath, data);
|
||||
log('info', `Added ${subtasks.length} subtasks to task ID=${taskId}.`);
|
||||
|
||||
// Print the new subtasks
|
||||
log('info', "New subtasks:");
|
||||
subtasks.forEach(st => {
|
||||
log('info', `- ${st.id}. ${st.title}`);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Expand all tasks with subtasks
|
||||
//
|
||||
async function expandAllTasks(tasksPath, numSubtasks, additionalContext = '', forceRegenerate = false) {
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log('info', `Expanding all ${data.tasks.length} tasks with subtasks...`);
|
||||
|
||||
let tasksExpanded = 0;
|
||||
let tasksSkipped = 0;
|
||||
let tasksCompleted = 0;
|
||||
|
||||
// Process each task sequentially to avoid overwhelming the API
|
||||
for (const task of data.tasks) {
|
||||
// Skip tasks that are already completed
|
||||
if (task.status === 'done' || task.status === 'completed') {
|
||||
log('info', `Skipping task ID=${task.id} "${task.title}" - task is already marked as ${task.status}.`);
|
||||
tasksCompleted++;
|
||||
continue;
|
||||
/**
|
||||
* Expand all tasks that are not completed
|
||||
* @param {number} numSubtasks - The number of subtasks to generate for each task
|
||||
* @param {boolean} useResearch - Whether to use Perplexity for research-backed subtask generation
|
||||
* @returns {Promise<number>} - The number of tasks expanded
|
||||
*/
|
||||
async function expandAllTasks(numSubtasks = CONFIG.defaultSubtasks, useResearch = false) {
|
||||
try {
|
||||
// Get the tasks
|
||||
const tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json'));
|
||||
|
||||
if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) {
|
||||
console.error(chalk.red('No valid tasks found.'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip tasks that already have subtasks unless force regeneration is enabled
|
||||
if (!forceRegenerate && task.subtasks && task.subtasks.length > 0) {
|
||||
log('info', `Skipping task ID=${task.id} "${task.title}" - already has ${task.subtasks.length} subtasks`);
|
||||
tasksSkipped++;
|
||||
continue;
|
||||
// Filter tasks that are not completed
|
||||
const tasksToExpand = tasksData.tasks.filter(task =>
|
||||
task.status !== 'completed' && task.status !== 'done'
|
||||
);
|
||||
|
||||
if (tasksToExpand.length === 0) {
|
||||
console.log(chalk.yellow('No tasks to expand. All tasks are already completed.'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const success = await expandTask(tasksPath, task.id, numSubtasks, additionalContext);
|
||||
if (success) {
|
||||
console.log(chalk.blue(`Expanding ${tasksToExpand.length} tasks with ${numSubtasks} subtasks each...`));
|
||||
|
||||
let tasksExpanded = 0;
|
||||
|
||||
// Expand each task
|
||||
for (const task of tasksToExpand) {
|
||||
console.log(chalk.blue(`\nExpanding task ${task.id}: ${task.title}`));
|
||||
await expandTask(task.id, numSubtasks, useResearch);
|
||||
tasksExpanded++;
|
||||
}
|
||||
}
|
||||
|
||||
log('info', `Expansion complete: ${tasksExpanded} tasks expanded, ${tasksSkipped} tasks skipped (already had subtasks), ${tasksCompleted} tasks skipped (already completed).`);
|
||||
|
||||
if (tasksSkipped > 0) {
|
||||
log('info', `Tip: Use --force flag to regenerate subtasks for all tasks, including those that already have subtasks.`);
|
||||
}
|
||||
|
||||
if (tasksCompleted > 0) {
|
||||
log('info', `Note: Completed tasks are always skipped. Use set-status command to change task status if needed.`);
|
||||
|
||||
console.log(chalk.green(`\nExpanded ${tasksExpanded} tasks with ${numSubtasks} subtasks each.`));
|
||||
return tasksExpanded;
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error expanding all tasks:'), error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,135 +1000,272 @@ function parseSubtasksFromText(text, startId, expectedCount) {
|
||||
return subtasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate subtasks for a task using Perplexity AI with research capabilities
|
||||
* @param {Object} task - The task to generate subtasks for
|
||||
* @param {number} numSubtasks - The number of subtasks to generate
|
||||
* @param {number} nextSubtaskId - The ID to start assigning to subtasks
|
||||
* @returns {Promise<Array>} - The generated subtasks
|
||||
*/
|
||||
async function generateSubtasksWithPerplexity(task, numSubtasks = 3, nextSubtaskId = 1) {
|
||||
const { title, description, details = '', subtasks = [] } = task;
|
||||
|
||||
console.log(chalk.blue(`Generating ${numSubtasks} subtasks for task: ${title}`));
|
||||
if (subtasks.length > 0) {
|
||||
console.log(chalk.yellow(`Task already has ${subtasks.length} subtasks. Adding ${numSubtasks} more.`));
|
||||
}
|
||||
|
||||
// Get the tasks.json content for context
|
||||
let tasksData = {};
|
||||
try {
|
||||
tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json'));
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow('Could not read tasks.json for context. Proceeding without it.'));
|
||||
}
|
||||
|
||||
// Get the PRD content for context if available
|
||||
let prdContent = '';
|
||||
if (tasksData.meta && tasksData.meta.source) {
|
||||
try {
|
||||
prdContent = fs.readFileSync(path.join(process.cwd(), tasksData.meta.source), 'utf8');
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(`Could not read PRD at ${tasksData.meta.source}. Proceeding without it.`));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the prompt for Perplexity/Anthropic
|
||||
const prompt = `I need to break down the following task into ${numSubtasks} detailed subtasks:
|
||||
|
||||
Task Title: ${title}
|
||||
Task Description: ${description}
|
||||
Additional Details: ${details}
|
||||
|
||||
${subtasks.length > 0 ? `Existing Subtasks:
|
||||
${subtasks.map(st => `- ${st.title}: ${st.description}`).join('\n')}` : ''}
|
||||
|
||||
${prdContent ? `Here is the Product Requirements Document for context:
|
||||
${prdContent}` : ''}
|
||||
|
||||
${tasksData.tasks ? `Here are the other tasks in the project for context:
|
||||
${JSON.stringify(tasksData.tasks.filter(t => t.id !== task.id).map(t => ({ id: t.id, title: t.title, description: t.description })), null, 2)}` : ''}
|
||||
|
||||
Please generate ${numSubtasks} subtasks. For each subtask, provide:
|
||||
1. A clear, concise title
|
||||
2. A detailed description explaining what needs to be done
|
||||
3. Dependencies (if any) - list the IDs of tasks this subtask depends on
|
||||
4. Acceptance criteria - specific conditions that must be met for the subtask to be considered complete
|
||||
|
||||
Format each subtask as follows:
|
||||
|
||||
Subtask 1: [Title]
|
||||
Description: [Detailed description]
|
||||
Dependencies: [List of task IDs, or "None" if no dependencies]
|
||||
Acceptance Criteria: [List of criteria]
|
||||
|
||||
Subtask 2: [Title]
|
||||
...
|
||||
|
||||
Research the task thoroughly and ensure the subtasks are comprehensive, specific, and actionable.`;
|
||||
|
||||
// Start loading indicator
|
||||
const loadingInterval = startLoadingIndicator('Researching and generating subtasks with AI');
|
||||
|
||||
try {
|
||||
let responseText;
|
||||
|
||||
try {
|
||||
// Try to use Perplexity first
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation...'));
|
||||
const result = await perplexity.chat.completions.create({
|
||||
model: PERPLEXITY_MODEL,
|
||||
messages: [{
|
||||
role: "user",
|
||||
content: prompt
|
||||
}],
|
||||
temperature: TEMPERATURE,
|
||||
max_tokens: MAX_TOKENS,
|
||||
});
|
||||
|
||||
// Extract the response text
|
||||
responseText = result.choices[0].message.content;
|
||||
console.log(chalk.green('Successfully generated subtasks with Perplexity AI'));
|
||||
} catch (perplexityError) {
|
||||
console.log(chalk.yellow('Falling back to Anthropic for subtask generation...'));
|
||||
console.log(chalk.gray('Perplexity error:'), perplexityError.message);
|
||||
|
||||
// Use Anthropic as fallback
|
||||
const stream = await anthropic.messages.create({
|
||||
model: MODEL,
|
||||
max_tokens: MAX_TOKENS,
|
||||
temperature: TEMPERATURE,
|
||||
system: "You are an expert software developer and project manager. Your task is to break down software development tasks into detailed subtasks.",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: prompt
|
||||
}
|
||||
],
|
||||
stream: true
|
||||
});
|
||||
|
||||
// Process the stream
|
||||
responseText = '';
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === 'content_block_delta' && chunk.delta.text) {
|
||||
responseText += chunk.delta.text;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green('Successfully generated subtasks with Anthropic AI'));
|
||||
}
|
||||
|
||||
// Stop loading indicator
|
||||
stopLoadingIndicator(loadingInterval);
|
||||
|
||||
if (CONFIG.debug) {
|
||||
console.log(chalk.gray('AI Response:'));
|
||||
console.log(chalk.gray(responseText));
|
||||
}
|
||||
|
||||
// Parse the subtasks from the response text
|
||||
const subtasks = parseSubtasksFromText(responseText, nextSubtaskId, numSubtasks);
|
||||
return subtasks;
|
||||
} catch (error) {
|
||||
stopLoadingIndicator(loadingInterval);
|
||||
console.error(chalk.red('Error generating subtasks:'), error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Main CLI
|
||||
// ------------------------------------------
|
||||
(async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
async function main() {
|
||||
program
|
||||
.name('dev')
|
||||
.description('AI-driven development task management')
|
||||
.version('1.3.1');
|
||||
|
||||
const outputDir = path.resolve(process.cwd(), 'tasks');
|
||||
// Update tasksPath to be inside the tasks directory
|
||||
const tasksPath = path.resolve(outputDir, 'tasks.json');
|
||||
program
|
||||
.command('parse-prd')
|
||||
.description('Parse a PRD file and generate tasks')
|
||||
.argument('<file>', 'Path to the PRD file')
|
||||
.option('-o, --output <file>', 'Output file path', 'tasks/tasks.json')
|
||||
.option('-n, --num-tasks <number>', 'Number of tasks to generate', '10')
|
||||
.action(async (file, options) => {
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
console.log(chalk.blue(`Parsing PRD file: ${file}`));
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
|
||||
await parsePRD(file, outputPath, numTasks);
|
||||
});
|
||||
|
||||
const inputArg = (args.find(a => a.startsWith('--input=')) || '').split('=')[1] || 'sample-prd.txt';
|
||||
const fromArg = (args.find(a => a.startsWith('--from=')) || '').split('=')[1];
|
||||
const promptArg = (args.find(a => a.startsWith('--prompt=')) || '').split('=')[1] || '';
|
||||
const idArg = (args.find(a => a.startsWith('--id=')) || '').split('=')[1];
|
||||
const statusArg = (args.find(a => a.startsWith('--status=')) || '').split('=')[1] || '';
|
||||
const tasksCountArg = (args.find(a => a.startsWith('--tasks=')) || '').split('=')[1];
|
||||
const numTasks = tasksCountArg ? parseInt(tasksCountArg, 10) : undefined;
|
||||
const subtasksArg = (args.find(a => a.startsWith('--subtasks=')) || '').split('=')[1];
|
||||
const numSubtasks = subtasksArg ? parseInt(subtasksArg, 10) : 3; // Default to 3 subtasks if not specified
|
||||
const forceFlag = args.includes('--force'); // Check if --force flag is present
|
||||
program
|
||||
.command('update')
|
||||
.description('Update tasks based on the PRD')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
|
||||
console.log(chalk.blue(`Updating tasks from: ${tasksPath}`));
|
||||
|
||||
await updateTasks(tasksPath);
|
||||
});
|
||||
|
||||
log('info', `Executing command: ${command}`);
|
||||
|
||||
// Make sure the tasks directory exists
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
log('info', `Creating tasks directory: ${outputDir}`);
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 'parse-prd':
|
||||
log('info', `Parsing PRD from ${inputArg} to generate tasks.json...`);
|
||||
if (numTasks) {
|
||||
log('info', `Limiting to ${numTasks} tasks as specified`);
|
||||
program
|
||||
.command('generate')
|
||||
.description('Generate task files from tasks.json')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-o, --output <dir>', 'Output directory', 'tasks')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const outputDir = options.output;
|
||||
|
||||
console.log(chalk.blue(`Generating task files from: ${tasksPath}`));
|
||||
console.log(chalk.blue(`Output directory: ${outputDir}`));
|
||||
|
||||
await generateTaskFiles(tasksPath, outputDir);
|
||||
});
|
||||
|
||||
program
|
||||
.command('set-status')
|
||||
.description('Set the status of a task')
|
||||
.argument('<id>', 'Task ID')
|
||||
.argument('<status>', 'New status (todo, in-progress, review, done)')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.action(async (id, status, options) => {
|
||||
const tasksPath = options.file;
|
||||
const taskId = parseInt(id, 10);
|
||||
|
||||
console.log(chalk.blue(`Setting status of task ${taskId} to: ${status}`));
|
||||
|
||||
await setTaskStatus(tasksPath, taskId, status);
|
||||
});
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('List all tasks')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-s, --status <status>', 'Filter by status')
|
||||
.option('--with-subtasks', 'Show subtasks for each task')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const statusFilter = options.status;
|
||||
const withSubtasks = options.withSubtasks || false;
|
||||
|
||||
console.log(chalk.blue(`Listing tasks from: ${tasksPath}`));
|
||||
if (statusFilter) {
|
||||
console.log(chalk.blue(`Filtering by status: ${statusFilter}`));
|
||||
}
|
||||
await parsePRD(inputArg, tasksPath, numTasks);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
if (!fromArg) {
|
||||
log('error', "Please specify --from=<id>. e.g. node dev.js update --from=3 --prompt='Changes...'");
|
||||
process.exit(1);
|
||||
if (withSubtasks) {
|
||||
console.log(chalk.blue('Including subtasks in listing'));
|
||||
}
|
||||
log('info', `Updating tasks from ID ${fromArg} based on prompt...`);
|
||||
await updateTasks(tasksPath, parseInt(fromArg, 10), promptArg);
|
||||
break;
|
||||
|
||||
await listTasks(tasksPath, statusFilter, withSubtasks);
|
||||
});
|
||||
|
||||
case 'generate':
|
||||
log('info', `Generating individual task files from ${tasksPath} to ${outputDir}...`);
|
||||
generateTaskFiles(tasksPath, outputDir);
|
||||
break;
|
||||
|
||||
case 'set-status':
|
||||
if (!idArg) {
|
||||
log('error', "Missing --id=<taskId> argument.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!statusArg) {
|
||||
log('error', "Missing --status=<newStatus> argument (e.g., done, pending, deferred, in-progress).");
|
||||
process.exit(1);
|
||||
}
|
||||
log('info', `Setting task(s) ${idArg} status to "${statusArg}"...`);
|
||||
setTaskStatus(tasksPath, idArg, statusArg);
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
log('info', `Listing tasks from ${tasksPath}...`);
|
||||
listTasks(tasksPath);
|
||||
break;
|
||||
|
||||
case 'expand':
|
||||
if (args.includes('--all')) {
|
||||
// Expand all tasks
|
||||
log('info', `Expanding all tasks with ${numSubtasks} subtasks each...`);
|
||||
await expandAllTasks(tasksPath, numSubtasks, promptArg, forceFlag);
|
||||
program
|
||||
.command('expand')
|
||||
.description('Expand tasks with subtasks')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-i, --id <id>', 'Task ID to expand')
|
||||
.option('-a, --all', 'Expand all tasks')
|
||||
.option('-n, --num <number>', 'Number of subtasks to generate', CONFIG.defaultSubtasks.toString())
|
||||
.option('-r, --research', 'Use Perplexity AI for research-backed subtask generation')
|
||||
.option('--force', 'Force regeneration of subtasks for tasks that already have them')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const idArg = options.id ? parseInt(options.id, 10) : null;
|
||||
const allFlag = options.all;
|
||||
const numSubtasks = parseInt(options.num, 10);
|
||||
const forceFlag = options.force;
|
||||
const useResearch = options.research;
|
||||
|
||||
if (allFlag) {
|
||||
console.log(chalk.blue(`Expanding all tasks with ${numSubtasks} subtasks each...`));
|
||||
if (useResearch) {
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation'));
|
||||
}
|
||||
await expandAllTasks(numSubtasks, useResearch);
|
||||
} else if (idArg) {
|
||||
// Expand a specific task
|
||||
log('info', `Expanding task ${idArg} with ${numSubtasks} subtasks...`);
|
||||
await expandTask(tasksPath, parseInt(idArg, 10), numSubtasks, promptArg);
|
||||
console.log(chalk.blue(`Expanding task ${idArg} with ${numSubtasks} subtasks...`));
|
||||
if (useResearch) {
|
||||
console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation'));
|
||||
}
|
||||
await expandTask(idArg, numSubtasks, useResearch);
|
||||
} else {
|
||||
log('error', "Error: Please specify a task ID with --id=<id> or use --all to expand all tasks.");
|
||||
process.exit(1);
|
||||
console.error(chalk.red('Error: Please specify a task ID with --id=<id> or use --all to expand all tasks.'));
|
||||
}
|
||||
break;
|
||||
});
|
||||
|
||||
default:
|
||||
log('info', `
|
||||
Dev.js - Task Management Script
|
||||
await program.parseAsync(process.argv);
|
||||
}
|
||||
|
||||
Subcommands:
|
||||
1) parse-prd --input=some-prd.txt [--tasks=10]
|
||||
-> Creates/overwrites tasks.json with a set of tasks.
|
||||
-> Optional --tasks parameter limits the number of tasks generated.
|
||||
// ... existing code ...
|
||||
|
||||
2) update --from=5 --prompt="We changed from Slack to Discord."
|
||||
-> Regenerates tasks from ID >= 5 using the provided prompt.
|
||||
|
||||
3) generate
|
||||
-> Generates per-task files (e.g., task_001.txt) from tasks.json
|
||||
|
||||
4) set-status --id=4 --status=done
|
||||
-> Updates a single task's status to done (or pending, deferred, in-progress, etc.).
|
||||
-> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2
|
||||
|
||||
5) list
|
||||
-> Lists tasks in a brief console view (ID, title, status).
|
||||
|
||||
6) expand --id=3 --subtasks=5 [--prompt="Additional context"]
|
||||
-> Expands a task with subtasks for more detailed implementation.
|
||||
-> Use --all instead of --id to expand all tasks.
|
||||
-> Optional --subtasks parameter controls number of subtasks (default: 3).
|
||||
-> Add --force when using --all to regenerate subtasks for tasks that already have them.
|
||||
-> Note: Tasks marked as 'done' or 'completed' are always skipped.
|
||||
|
||||
Usage examples:
|
||||
node dev.js parse-prd --input=scripts/prd.txt
|
||||
node dev.js parse-prd --input=scripts/prd.txt --tasks=10
|
||||
node dev.js update --from=4 --prompt="Refactor tasks from ID 4 onward"
|
||||
node dev.js generate
|
||||
node dev.js set-status --id=3 --status=done
|
||||
node dev.js list
|
||||
node dev.js expand --id=3 --subtasks=5
|
||||
node dev.js expand --all
|
||||
node dev.js expand --all --force
|
||||
`);
|
||||
break;
|
||||
}
|
||||
})().catch(err => {
|
||||
main().catch(err => {
|
||||
log('error', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user