diff --git a/.claude/skills/memory/SKILL.md b/.claude/skills/memory/SKILL.md
deleted file mode 100644
index 5dc4422..0000000
--- a/.claude/skills/memory/SKILL.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-name: memory
-description: Manage long-term memories using date-based files. Allows creating, listing, and reading memories.
----
-
-# Memory Skill
-
-This skill allows you to manage long-term memories in `docs/memories/`.
-It uses the `scripts/memory.py` utility.
-
-## Commands
-
-### Create Memory
-Create a new memory file.
-Command: `python3 scripts/memory.py create "
" "" [--tags "tag1,tag2"] [--format json]`
-
-### List Memories
-List recent memories, optionally filtered by tag.
-Command: `python3 scripts/memory.py list [--tag ] [--limit ] [--format json]`
-
-### Read Memory
-Read a specific memory file.
-Command: `python3 scripts/memory.py read [--format json]`
-
-### Index Memories (Entity Indexer)
-Scans tasks and memories for wikilinks (e.g. `[[Entity]]`) and tags to build an Entity Index (`docs/memories/entities.json`).
-Command: `python3 scripts/memory.py index [--format json]`
-
-## Usage Instructions
-- Use `create` to store important architectural decisions, lessons learned, or long-term context.
-- Use `list` to recall past memories.
-- Use `read` to get the full content of a memory.
-- Memories are stored as Markdown files with YAML frontmatter.
diff --git a/.claude/skills/micro-planning/SKILL.md b/.claude/skills/micro-planning/SKILL.md
deleted file mode 100644
index 0a8a6ca..0000000
--- a/.claude/skills/micro-planning/SKILL.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-name: micro-planning
-description: Breaks down a feature into bite-sized tasks.
----
-
-# Micro Planning
-
-Breaks down a feature into bite-sized tasks. Use this skill to read a design doc and generate a series of dependent micro-tasks in the task system.
-
-### Run Breakdown
-```bash
-python3 scripts/tasks.py breakdown
-```
diff --git a/.claude/skills/task-manager/SKILL.md b/.claude/skills/task-manager/SKILL.md
deleted file mode 100644
index 6d5e09a..0000000
--- a/.claude/skills/task-manager/SKILL.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-name: task-manager
-description: Manage project tasks. Create, list, update, and track dependencies of tasks.
----
-
-# Task Manager
-
-Use this skill to manage the project's task documentation in `docs/tasks/`.
-
-### Create a Task
-```bash
-python3 scripts/tasks.py create "Task Title" --status pending --priority high --type task --estimate "2h"
-```
-
-### List Tasks
-```bash
-# List all pending tasks
-python3 scripts/tasks.py list --status pending
-
-# List tasks in a specific sprint
-python3 scripts/tasks.py list --sprint "Sprint 1"
-```
-
-### Update a Task
-```bash
-python3 scripts/tasks.py update TASK-ID --status in_progress
-```
-
-### Validate Tasks
-Check for errors or inconsistencies in task files.
-```bash
-python3 scripts/tasks.py validate
-```
-
-### Manage Dependencies
-```bash
-python3 scripts/tasks.py link TASK-A TASK-B # A depends on B
-python3 scripts/tasks.py unlink TASK-A TASK-B
-```
-
-### View Task Details
-```bash
-python3 scripts/tasks.py show TASK-ID
-```
-
-### Get Next Task Recommendation
-```bash
-python3 scripts/tasks.py next
-```
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 507c62f..77fca66 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,7 +22,6 @@ jobs:
run: python3 -m unittest discover tests
- name: Validate Tasks
- run: python3 scripts/tasks.py validate
automerge:
needs: test
diff --git a/AGENTS.md b/AGENTS.md
index bff7e3c..cb1115d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -9,31 +9,22 @@ You are an expert Software Engineer working on this project. Your primary respon
To improve feature completeness, this repository integrates a sequence of rigorous software engineering workflows known as the "Superpowers":
- **Brainstorming:** `python3 scripts/design.py brainstorm --title "Feature" ...`
- **Workspace Setup:** `python3 scripts/workspace.py setup [TASK_ID]`
-- **Micro Planning:** `scripts/tasks.py breakdown [TASK_ID]`
- **TDD Enforcement:** `python3 scripts/tdd.py state`, `python3 scripts/tdd.py run`, `python3 scripts/tdd.py reset`
-- **Orchestration:** `python3 scripts/orchestrator.py run`, `python3 scripts/orchestrator.py assign`, `python3 scripts/orchestrator.py monitor`
- **Local Review:** Pre-PR automated review via `scripts/review.py`
## Workflow
-1. **Pick a Task**: Run `python3 scripts/tasks.py next` to find the best task, `context` to see active tasks, or `list` to see pending ones.
2. **Plan & Document**:
- * **Memory Check**: Run `python3 scripts/memory.py list` (or use the Memory Skill) to recall relevant long-term information.
* **Security Check**: Ask the user about specific security considerations for this task.
- * If starting a new task, use `scripts/tasks.py create` (or `python3 scripts/tasks.py create`) to generate a new task file.
- * Update the task status: `python3 scripts/tasks.py update [TASK_ID] in_progress`.
3. **Implement**: Write code, run tests.
4. **Update Documentation Loop**:
* As you complete sub-tasks, check them off in the task document.
* If you hit a blocker, update status to `wip_blocked` and describe the issue in the file.
* Record key architectural decisions in the task document.
- * **Memory Update**: If you learn something valuable for the long term, use `scripts/memory.py create` to record it.
5. **Review & Verify**:
* **Quality Check**: Run `python3 scripts/quality.py verify` to ensure tests and validation pass. **Do not request review if this fails.**
- * Once implementation is complete, update status to `review_requested`: `python3 scripts/tasks.py update [TASK_ID] review_requested`.
* Ask a human or another agent to review the code.
* Once approved and tested, update status to `verified`.
6. **Finalize**:
- * Update status to `completed`: `python3 scripts/tasks.py update [TASK_ID] completed`.
* Record actual effort in the file.
* Ensure all acceptance criteria are met.
@@ -46,7 +37,6 @@ To improve feature completeness, this repository integrates a sequence of rigoro
* **Context**: `./scripts/tasks context`
* **Update**: `./scripts/tasks update [ID] [status]`
* **Migrate**: `./scripts/tasks migrate` (Migrate legacy tasks to new format)
-* **Memory**: `./scripts/memory.py [create|list|read]`
* **JSON Output**: Add `--format json` to any command for machine parsing.
## Documentation Reference
@@ -64,7 +54,6 @@ To improve feature completeness, this repository integrates a sequence of rigoro
When performing a PR review, follow this "Human-in-the-loop" process to ensure depth and efficiency.
### 1. Preparation
-1. **Create Task**: `python3 scripts/tasks.py create review "Review PR #: "`
2. **Fetch Details**: Use `gh` to get the PR context.
* `gh pr view `
* `gh pr diff `
diff --git a/README.md b/README.md
index 291dc37..e54a272 100644
--- a/README.md
+++ b/README.md
@@ -18,24 +18,20 @@ If you have just cloned this repository to start a new project:
1. **Initialize Directory Structure**:
```bash
- python3 scripts/tasks.py init
```
This will create the `docs/` hierarchy and ensure all necessary configuration files are in place.
2. **Create Your First Task**:
```bash
- python3 scripts/tasks.py create foundation "Initial Project Setup"
```
3. **Install Pre-Commit Hooks** (Optional but Recommended):
```bash
- python3 scripts/tasks.py install-hooks
```
This ensures task validity before every commit.
### 2. Workflow
-The core workflow is centered around `scripts/tasks.py` (or the `./scripts/tasks` wrapper).
* **List Tasks**: `./scripts/tasks list`
* **Create Task**: `./scripts/tasks create features "New Feature"`
@@ -53,7 +49,6 @@ Refer to [AGENTS.md](AGENTS.md) for detailed operational instructions. This file
* `docs/tasks/`: Active and completed tasks.
* `docs/memories/`: Long-term project context.
* `docs/architecture/`: System design documentation.
-* `scripts/`: Automation tools (`tasks.py`, `memory.py`, `bootstrap.py`).
* `templates/`: Templates for tasks and guides.
## Maintenance
diff --git a/ah b/ah
deleted file mode 100755
index 7ac82f7..0000000
--- a/ah
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/bin/bash
-# Agent Harness CLI Wrapper
-
-REPO_ROOT="$(dirname "$0")"
-TASKS_SCRIPT="$REPO_ROOT/scripts/tasks.py"
-MEMORY_SCRIPT="$REPO_ROOT/scripts/memory.py"
-
-function show_help {
- echo "Agent Harness (ah) - Developer CLI"
- echo ""
- echo "Usage: ./ah [command] [args]"
- echo ""
- echo "Task Commands:"
- echo " list List tasks"
- echo " next Show next recommended task"
- echo " ready Show strictly ready tasks (unblocked)"
- echo " show [ID] Show task details"
- echo " create ... Create a task"
- echo " update ... Update a task"
- echo " compact ... Compact a completed task"
- echo " validate Validate task graph"
- echo " visualize Visualize task graph"
- echo ""
- echo "Memory Commands:"
- echo " index Rebuild entity index"
- echo " mem-list List memories"
- echo " mem-create Create memory"
- echo ""
- exit 1
-}
-
-CMD="$1"
-shift
-
-case "$CMD" in
- # Tasks Mapping
- list|next|show|create|update|compact|validate|visualize|archive|delete|context|add-dep|rm-dep|link|unlink)
- python3 "$TASKS_SCRIPT" "$CMD" "$@"
- ;;
-
- # New 'ready' command (mapped to tasks.py ready, which we will implement next)
- ready)
- python3 "$TASKS_SCRIPT" ready "$@"
- ;;
-
- # Memory Mapping
- index)
- python3 "$MEMORY_SCRIPT" index "$@"
- ;;
- mem-list)
- python3 "$MEMORY_SCRIPT" list "$@"
- ;;
- mem-create)
- python3 "$MEMORY_SCRIPT" create "$@"
- ;;
- mem-read)
- python3 "$MEMORY_SCRIPT" read "$@"
- ;;
-
- *)
- show_help
- ;;
-esac
diff --git a/docs/CODE_INDEX.json b/docs/CODE_INDEX.json
index bdcafae..cb1fca1 100644
--- a/docs/CODE_INDEX.json
+++ b/docs/CODE_INDEX.json
@@ -65,97 +65,41 @@
}
]
},
- "scripts/__init__.py": {
- "path": "scripts/__init__.py",
- "symbols": []
- },
- "scripts/upgrade.py": {
- "path": "scripts/upgrade.py",
- "symbols": [
- {
- "name": "upgrade",
- "type": "function",
- "line": 11
- },
- {
- "name": "SCRIPT_DIR",
- "type": "variable",
- "line": 7
- },
- {
- "name": "REPO_ROOT",
- "type": "variable",
- "line": 8
- },
- {
- "name": "TASKS_SCRIPT",
- "type": "variable",
- "line": 9
- }
- ]
- },
- "scripts/tools.sh": {
- "path": "scripts/tools.sh",
- "symbols": []
- },
- "scripts/index": {
- "path": "scripts/index",
- "symbols": []
- },
- "scripts/memory.py": {
- "path": "scripts/memory.py",
+ "scripts/design.py": {
+ "path": "scripts/design.py",
"symbols": [
{
- "name": "init_memory",
+ "name": "sanitize_slug",
"type": "function",
- "line": 18
+ "line": 20
},
{
- "name": "slugify",
+ "name": "run_brainstorm",
"type": "function",
"line": 24
},
- {
- "name": "create_memory",
- "type": "function",
- "line": 29
- },
- {
- "name": "list_memories",
- "type": "function",
- "line": 81
- },
- {
- "name": "read_memory",
- "type": "function",
- "line": 154
- },
{
"name": "main",
"type": "function",
- "line": 199
+ "line": 76
},
{
- "name": "SCRIPT_DIR",
+ "name": "DOCS_DIR",
"type": "variable",
- "line": 10
+ "line": 16
},
{
- "name": "REPO_ROOT",
+ "name": "FEATURES_DIR",
"type": "variable",
- "line": 12
+ "line": 17
},
{
- "name": "MEMORY_DIR",
+ "name": "ARCHITECTURE_DIR",
"type": "variable",
- "line": 16
+ "line": 18
}
]
},
- "scripts/tasks": {
- "path": "scripts/tasks",
- "symbols": []
- },
"scripts/continuity.py": {
"path": "scripts/continuity.py",
"symbols": [
@@ -341,263 +285,86 @@
}
]
},
- "scripts/tasks.py": {
- "path": "scripts/tasks.py",
+ "scripts/__init__.py": {
+ "path": "scripts/__init__.py",
+ "symbols": []
+ },
+ "scripts/index": {
+ "path": "scripts/index",
+ "symbols": []
+ },
+ "scripts/tdd.py": {
+ "path": "scripts/tdd.py",
"symbols": [
{
- "name": "init_docs",
- "type": "function",
- "line": 34
- },
- {
- "name": "generate_task_id",
- "type": "function",
- "line": 86
- },
- {
- "name": "extract_frontmatter",
- "type": "function",
- "line": 92
- },
- {
- "name": "parse_task_content",
- "type": "function",
- "line": 131
- },
- {
- "name": "create_task",
- "type": "function",
- "line": 183
- },
- {
- "name": "find_task_file",
- "type": "function",
- "line": 244
- },
- {
- "name": "show_task",
- "type": "function",
- "line": 269
- },
- {
- "name": "delete_task",
- "type": "function",
- "line": 295
- },
- {
- "name": "archive_task",
- "type": "function",
- "line": 319
- },
- {
- "name": "migrate_to_frontmatter",
- "type": "function",
- "line": 350
- },
- {
- "name": "update_task_status",
- "type": "function",
- "line": 395
- },
- {
- "name": "update_frontmatter_field",
- "type": "function",
- "line": 491
- },
- {
- "name": "add_dependency",
- "type": "function",
- "line": 543
- },
- {
- "name": "remove_dependency",
- "type": "function",
- "line": 572
- },
- {
- "name": "generate_index",
- "type": "function",
- "line": 595
+ "name": "TDDState",
+ "type": "class",
+ "line": 17
},
{
- "name": "list_tasks",
+ "name": "get_state",
"type": "function",
- "line": 642
+ "line": 22
},
{
- "name": "get_context",
+ "name": "set_state",
"type": "function",
- "line": 696
+ "line": 29
},
{
- "name": "migrate_all",
+ "name": "get_git_diff",
"type": "function",
- "line": 702
+ "line": 33
},
{
- "name": "validate_all",
+ "name": "run_tests",
"type": "function",
- "line": 729
+ "line": 38
},
{
- "name": "visualize_tasks",
+ "name": "tdd_enforce_red",
"type": "function",
- "line": 832
+ "line": 50
},
{
- "name": "get_next_task",
+ "name": "tdd_enforce_green",
"type": "function",
- "line": 889
+ "line": 67
},
{
- "name": "install_hooks",
+ "name": "tdd_enforce_refactor",
"type": "function",
- "line": 985
+ "line": 77
},
{
"name": "main",
"type": "function",
- "line": 1011
- },
- {
- "name": "detect_cycle",
- "type": "function",
- "line": 788
- },
- {
- "name": "SCRIPT_DIR",
- "type": "variable",
- "line": 14
- },
- {
- "name": "REPO_ROOT",
- "type": "variable",
- "line": 15
- },
- {
- "name": "config",
- "type": "variable",
- "line": 23
- },
- {
- "name": "DOCS_DIR",
- "type": "variable",
- "line": 25
- },
- {
- "name": "TEMPLATES_DIR",
- "type": "variable",
- "line": 26
- },
- {
- "name": "CATEGORIES",
- "type": "variable",
- "line": 28
- },
- {
- "name": "VALID_STATUSES",
- "type": "variable",
- "line": 29
- },
- {
- "name": "VALID_TYPES",
- "type": "variable",
- "line": 30
- },
- {
- "name": "ARCHIVE_DIR_NAME",
- "type": "variable",
- "line": 32
- }
- ]
- },
- "scripts/bootstrap.py": {
- "path": "scripts/bootstrap.py",
- "symbols": [
- {
- "name": "is_ignored_preamble_line",
- "type": "function",
- "line": 34
- },
- {
- "name": "extract_custom_content",
- "type": "function",
- "line": 46
- },
- {
- "name": "scaffold",
- "type": "function",
- "line": 84
- },
- {
- "name": "check_state",
- "type": "function",
- "line": 146
- },
- {
- "name": "finalize",
- "type": "function",
- "line": 182
+ "line": 87
},
{
"name": "SCRIPT_DIR",
"type": "variable",
- "line": 7
+ "line": 11
},
{
"name": "REPO_ROOT",
"type": "variable",
- "line": 8
- },
- {
- "name": "AGENTS_FILE",
- "type": "variable",
- "line": 13
- },
- {
- "name": "CLAUDE_FILE",
- "type": "variable",
- "line": 14
+ "line": 12
},
{
- "name": "TEMPLATE_MAINTENANCE",
+ "name": "TDD_STATE_FILE_DEFAULT",
"type": "variable",
"line": 15
- },
- {
- "name": "CONFIG_FILE",
- "type": "variable",
- "line": 16
- },
- {
- "name": "STANDARD_HEADERS",
- "type": "variable",
- "line": 18
- },
- {
- "name": "PREAMBLE_IGNORE_PATTERNS",
- "type": "variable",
- "line": 26
}
]
},
- "scripts/generate_tools.py": {
- "path": "scripts/generate_tools.py",
+ "scripts/upgrade.py": {
+ "path": "scripts/upgrade.py",
"symbols": [
{
- "name": "generate_markdown",
- "type": "function",
- "line": 17
- },
- {
- "name": "generate_shell",
- "type": "function",
- "line": 41
- },
- {
- "name": "main",
+ "name": "upgrade",
"type": "function",
- "line": 83
+ "line": 10
},
{
"name": "SCRIPT_DIR",
@@ -608,21 +375,6 @@
"name": "REPO_ROOT",
"type": "variable",
"line": 8
- },
- {
- "name": "TOOLS_JSON",
- "type": "variable",
- "line": 13
- },
- {
- "name": "TOOLS_MD",
- "type": "variable",
- "line": 14
- },
- {
- "name": "TOOLS_SH",
- "type": "variable",
- "line": 15
}
]
},
@@ -690,24 +442,29 @@
"line": 276
},
{
- "name": "cmd_search",
+ "name": "cmd_stats",
"type": "function",
"line": 288
},
+ {
+ "name": "cmd_search",
+ "type": "function",
+ "line": 323
+ },
{
"name": "cmd_lookup",
"type": "function",
- "line": 298
+ "line": 333
},
{
"name": "cmd_references",
"type": "function",
- "line": 323
+ "line": 358
},
{
"name": "main",
"type": "function",
- "line": 333
+ "line": 368
},
{
"name": "SCRIPT_DIR",
@@ -731,33 +488,103 @@
}
]
},
- "scripts/hooks/session_start.py": {
- "path": "scripts/hooks/session_start.py",
+ "scripts/quality.py": {
+ "path": "scripts/quality.py",
"symbols": [
{
- "name": "get_latest_content",
+ "name": "check_linter_availability",
"type": "function",
- "line": 21
+ "line": 24
},
{
- "name": "main",
+ "name": "run_tests",
"type": "function",
- "line": 30
+ "line": 32
},
{
- "name": "SCRIPT_DIR",
- "type": "variable",
- "line": 14
+ "name": "run_validation",
+ "type": "function",
+ "line": 78
},
{
- "name": "REPO_ROOT",
- "type": "variable",
- "line": 16
+ "name": "run_lint",
+ "type": "function",
+ "line": 106
},
{
- "name": "CONTINUITY_DIR",
- "type": "variable",
- "line": 17
+ "name": "scaffold_test",
+ "type": "function",
+ "line": 146
+ },
+ {
+ "name": "cmd_verify",
+ "type": "function",
+ "line": 215
+ },
+ {
+ "name": "main",
+ "type": "function",
+ "line": 238
+ },
+ {
+ "name": "SCRIPT_DIR",
+ "type": "variable",
+ "line": 11
+ },
+ {
+ "name": "REPO_ROOT",
+ "type": "variable",
+ "line": 12
+ },
+ {
+ "name": "LINTERS",
+ "type": "variable",
+ "line": 18
+ }
+ ]
+ },
+ "scripts/workspace.py": {
+ "path": "scripts/workspace.py",
+ "symbols": [
+ {
+ "name": "setup_workspace",
+ "type": "function",
+ "line": 13
+ },
+ {
+ "name": "main",
+ "type": "function",
+ "line": 46
+ }
+ ]
+ },
+ "scripts/hooks/session_start.py": {
+ "path": "scripts/hooks/session_start.py",
+ "symbols": [
+ {
+ "name": "get_latest_content",
+ "type": "function",
+ "line": 21
+ },
+ {
+ "name": "main",
+ "type": "function",
+ "line": 30
+ },
+ {
+ "name": "SCRIPT_DIR",
+ "type": "variable",
+ "line": 14
+ },
+ {
+ "name": "REPO_ROOT",
+ "type": "variable",
+ "line": 16
+ },
+ {
+ "name": "CONTINUITY_DIR",
+ "type": "variable",
+ "line": 17
},
{
"name": "LEDGERS_DIR",
@@ -775,33 +602,33 @@
"path": "scripts/lib/__init__.py",
"symbols": []
},
- "scripts/lib/io.py": {
- "path": "scripts/lib/io.py",
+ "scripts/lib/config.py": {
+ "path": "scripts/lib/config.py",
"symbols": [
{
- "name": "write_atomic",
- "type": "function",
- "line": 7
+ "name": "Config",
+ "type": "class",
+ "line": 49
},
{
- "name": "write_atomic_gzip",
- "type": "function",
- "line": 36
+ "name": "Config.load",
+ "type": "method",
+ "line": 53
},
{
- "name": "read_text",
- "type": "function",
- "line": 60
+ "name": "Config._merge",
+ "type": "method",
+ "line": 77
},
{
- "name": "write_json",
+ "name": "get_config",
"type": "function",
- "line": 83
+ "line": 86
},
{
- "name": "read_json",
- "type": "function",
- "line": 91
+ "name": "DEFAULT_CONFIG",
+ "type": "variable",
+ "line": 6
}
]
},
@@ -845,63 +672,33 @@
}
]
},
- "scripts/lib/config.py": {
- "path": "scripts/lib/config.py",
- "symbols": [
- {
- "name": "Config",
- "type": "class",
- "line": 49
- },
- {
- "name": "Config.load",
- "type": "method",
- "line": 53
- },
- {
- "name": "Config._merge",
- "type": "method",
- "line": 77
- },
- {
- "name": "get_config",
- "type": "function",
- "line": 86
- },
- {
- "name": "DEFAULT_CONFIG",
- "type": "variable",
- "line": 6
- }
- ]
- },
- "scripts/lib/audit.py": {
- "path": "scripts/lib/audit.py",
+ "scripts/lib/io.py": {
+ "path": "scripts/lib/io.py",
"symbols": [
{
- "name": "log_activity",
+ "name": "write_atomic",
"type": "function",
- "line": 6
+ "line": 7
},
{
- "name": "audit_log",
+ "name": "write_atomic_gzip",
"type": "function",
- "line": 47
+ "line": 36
},
{
- "name": "serialize",
+ "name": "read_text",
"type": "function",
- "line": 32
+ "line": 60
},
{
- "name": "decorator",
+ "name": "write_json",
"type": "function",
- "line": 51
+ "line": 83
},
{
- "name": "wrapper",
+ "name": "read_json",
"type": "function",
- "line": 52
+ "line": 91
}
]
},
@@ -934,6 +731,36 @@
"line": 25
}
]
+ },
+ "scripts/lib/audit.py": {
+ "path": "scripts/lib/audit.py",
+ "symbols": [
+ {
+ "name": "log_activity",
+ "type": "function",
+ "line": 6
+ },
+ {
+ "name": "audit_log",
+ "type": "function",
+ "line": 47
+ },
+ {
+ "name": "serialize",
+ "type": "function",
+ "line": 32
+ },
+ {
+ "name": "decorator",
+ "type": "function",
+ "line": 51
+ },
+ {
+ "name": "wrapper",
+ "type": "function",
+ "line": 52
+ }
+ ]
}
},
"symbols": {
@@ -1000,8 +827,8 @@
"type": "function"
},
{
- "file": "scripts/memory.py",
- "line": 199,
+ "file": "scripts/design.py",
+ "line": 76,
"type": "function"
},
{
@@ -1015,18 +842,23 @@
"type": "function"
},
{
- "file": "scripts/tasks.py",
- "line": 1011,
+ "file": "scripts/tdd.py",
+ "line": 87,
"type": "function"
},
{
- "file": "scripts/generate_tools.py",
- "line": 83,
+ "file": "scripts/code_index.py",
+ "line": 368,
"type": "function"
},
{
- "file": "scripts/code_index.py",
- "line": 333,
+ "file": "scripts/quality.py",
+ "line": 238,
+ "type": "function"
+ },
+ {
+ "file": "scripts/workspace.py",
+ "line": 46,
"type": "function"
},
{
@@ -1041,16 +873,6 @@
"line": 8,
"type": "variable"
},
- {
- "file": "scripts/upgrade.py",
- "line": 7,
- "type": "variable"
- },
- {
- "file": "scripts/memory.py",
- "line": 10,
- "type": "variable"
- },
{
"file": "scripts/continuity.py",
"line": 15,
@@ -1062,22 +884,22 @@
"type": "variable"
},
{
- "file": "scripts/tasks.py",
- "line": 14,
+ "file": "scripts/tdd.py",
+ "line": 11,
"type": "variable"
},
{
- "file": "scripts/bootstrap.py",
+ "file": "scripts/upgrade.py",
"line": 7,
"type": "variable"
},
{
- "file": "scripts/generate_tools.py",
- "line": 7,
+ "file": "scripts/code_index.py",
+ "line": 11,
"type": "variable"
},
{
- "file": "scripts/code_index.py",
+ "file": "scripts/quality.py",
"line": 11,
"type": "variable"
},
@@ -1093,16 +915,6 @@
"line": 9,
"type": "variable"
},
- {
- "file": "scripts/upgrade.py",
- "line": 8,
- "type": "variable"
- },
- {
- "file": "scripts/memory.py",
- "line": 12,
- "type": "variable"
- },
{
"file": "scripts/continuity.py",
"line": 16,
@@ -1114,22 +926,22 @@
"type": "variable"
},
{
- "file": "scripts/tasks.py",
- "line": 15,
+ "file": "scripts/tdd.py",
+ "line": 12,
"type": "variable"
},
{
- "file": "scripts/bootstrap.py",
+ "file": "scripts/upgrade.py",
"line": 8,
"type": "variable"
},
{
- "file": "scripts/generate_tools.py",
- "line": 8,
+ "file": "scripts/code_index.py",
+ "line": 12,
"type": "variable"
},
{
- "file": "scripts/code_index.py",
+ "file": "scripts/quality.py",
"line": 12,
"type": "variable"
},
@@ -1151,89 +963,70 @@
"type": "variable"
}
],
- "upgrade": [
+ "sanitize_slug": [
{
- "file": "scripts/upgrade.py",
- "line": 11,
+ "file": "scripts/design.py",
+ "line": 20,
"type": "function"
}
],
- "TASKS_SCRIPT": [
+ "run_brainstorm": [
{
- "file": "scripts/upgrade.py",
- "line": 9,
+ "file": "scripts/design.py",
+ "line": 24,
+ "type": "function"
+ }
+ ],
+ "DOCS_DIR": [
+ {
+ "file": "scripts/design.py",
+ "line": 16,
+ "type": "variable"
+ }
+ ],
+ "FEATURES_DIR": [
+ {
+ "file": "scripts/design.py",
+ "line": 17,
"type": "variable"
}
],
- "init_memory": [
+ "ARCHITECTURE_DIR": [
{
- "file": "scripts/memory.py",
+ "file": "scripts/design.py",
"line": 18,
- "type": "function"
+ "type": "variable"
}
],
- "slugify": [
+ "ensure_dirs": [
{
- "file": "scripts/memory.py",
- "line": 24,
+ "file": "scripts/continuity.py",
+ "line": 21,
"type": "function"
- },
+ }
+ ],
+ "slugify": [
{
"file": "scripts/continuity.py",
"line": 25,
"type": "function"
}
],
- "create_memory": [
+ "get_latest_file": [
{
- "file": "scripts/memory.py",
+ "file": "scripts/continuity.py",
"line": 29,
"type": "function"
}
],
- "list_memories": [
+ "create_entry": [
{
- "file": "scripts/memory.py",
- "line": 81,
+ "file": "scripts/continuity.py",
+ "line": 39,
"type": "function"
}
],
- "read_memory": [
- {
- "file": "scripts/memory.py",
- "line": 154,
- "type": "function"
- }
- ],
- "MEMORY_DIR": [
- {
- "file": "scripts/memory.py",
- "line": 16,
- "type": "variable"
- }
- ],
- "ensure_dirs": [
- {
- "file": "scripts/continuity.py",
- "line": 21,
- "type": "function"
- }
- ],
- "get_latest_file": [
- {
- "file": "scripts/continuity.py",
- "line": 29,
- "type": "function"
- }
- ],
- "create_entry": [
- {
- "file": "scripts/continuity.py",
- "line": 39,
- "type": "function"
- }
- ],
- "read_file": [
+ "read_file": [
{
"file": "scripts/continuity.py",
"line": 60,
@@ -1423,333 +1216,81 @@
"type": "function"
}
],
- "init_docs": [
+ "TDDState": [
{
- "file": "scripts/tasks.py",
- "line": 34,
- "type": "function"
- }
- ],
- "generate_task_id": [
- {
- "file": "scripts/tasks.py",
- "line": 86,
- "type": "function"
- }
- ],
- "extract_frontmatter": [
- {
- "file": "scripts/tasks.py",
- "line": 92,
- "type": "function"
- }
- ],
- "parse_task_content": [
- {
- "file": "scripts/tasks.py",
- "line": 131,
- "type": "function"
- }
- ],
- "create_task": [
- {
- "file": "scripts/tasks.py",
- "line": 183,
- "type": "function"
- }
- ],
- "find_task_file": [
- {
- "file": "scripts/tasks.py",
- "line": 244,
- "type": "function"
- }
- ],
- "show_task": [
- {
- "file": "scripts/tasks.py",
- "line": 269,
- "type": "function"
- }
- ],
- "delete_task": [
- {
- "file": "scripts/tasks.py",
- "line": 295,
- "type": "function"
- }
- ],
- "archive_task": [
- {
- "file": "scripts/tasks.py",
- "line": 319,
- "type": "function"
- }
- ],
- "migrate_to_frontmatter": [
- {
- "file": "scripts/tasks.py",
- "line": 350,
- "type": "function"
- }
- ],
- "update_task_status": [
- {
- "file": "scripts/tasks.py",
- "line": 395,
- "type": "function"
- }
- ],
- "update_frontmatter_field": [
- {
- "file": "scripts/tasks.py",
- "line": 491,
- "type": "function"
- }
- ],
- "add_dependency": [
- {
- "file": "scripts/tasks.py",
- "line": 543,
- "type": "function"
- }
- ],
- "remove_dependency": [
- {
- "file": "scripts/tasks.py",
- "line": 572,
- "type": "function"
- }
- ],
- "generate_index": [
- {
- "file": "scripts/tasks.py",
- "line": 595,
- "type": "function"
- }
- ],
- "list_tasks": [
- {
- "file": "scripts/tasks.py",
- "line": 642,
- "type": "function"
- }
- ],
- "get_context": [
- {
- "file": "scripts/tasks.py",
- "line": 696,
- "type": "function"
- }
- ],
- "migrate_all": [
- {
- "file": "scripts/tasks.py",
- "line": 702,
- "type": "function"
- }
- ],
- "validate_all": [
- {
- "file": "scripts/tasks.py",
- "line": 729,
- "type": "function"
+ "file": "scripts/tdd.py",
+ "line": 17,
+ "type": "class"
}
],
- "visualize_tasks": [
+ "get_state": [
{
- "file": "scripts/tasks.py",
- "line": 832,
+ "file": "scripts/tdd.py",
+ "line": 22,
"type": "function"
}
],
- "get_next_task": [
+ "set_state": [
{
- "file": "scripts/tasks.py",
- "line": 889,
+ "file": "scripts/tdd.py",
+ "line": 29,
"type": "function"
}
],
- "install_hooks": [
+ "get_git_diff": [
{
- "file": "scripts/tasks.py",
- "line": 985,
+ "file": "scripts/tdd.py",
+ "line": 33,
"type": "function"
}
],
- "detect_cycle": [
+ "run_tests": [
{
- "file": "scripts/tasks.py",
- "line": 788,
+ "file": "scripts/tdd.py",
+ "line": 38,
"type": "function"
- }
- ],
- "config": [
- {
- "file": "scripts/tasks.py",
- "line": 23,
- "type": "variable"
- }
- ],
- "DOCS_DIR": [
- {
- "file": "scripts/tasks.py",
- "line": 25,
- "type": "variable"
- }
- ],
- "TEMPLATES_DIR": [
- {
- "file": "scripts/tasks.py",
- "line": 26,
- "type": "variable"
- }
- ],
- "CATEGORIES": [
- {
- "file": "scripts/tasks.py",
- "line": 28,
- "type": "variable"
- }
- ],
- "VALID_STATUSES": [
- {
- "file": "scripts/tasks.py",
- "line": 29,
- "type": "variable"
- }
- ],
- "VALID_TYPES": [
- {
- "file": "scripts/tasks.py",
- "line": 30,
- "type": "variable"
- }
- ],
- "ARCHIVE_DIR_NAME": [
+ },
{
- "file": "scripts/tasks.py",
+ "file": "scripts/quality.py",
"line": 32,
- "type": "variable"
- }
- ],
- "is_ignored_preamble_line": [
- {
- "file": "scripts/bootstrap.py",
- "line": 34,
- "type": "function"
- }
- ],
- "extract_custom_content": [
- {
- "file": "scripts/bootstrap.py",
- "line": 46,
"type": "function"
}
],
- "scaffold": [
+ "tdd_enforce_red": [
{
- "file": "scripts/bootstrap.py",
- "line": 84,
+ "file": "scripts/tdd.py",
+ "line": 50,
"type": "function"
}
],
- "check_state": [
+ "tdd_enforce_green": [
{
- "file": "scripts/bootstrap.py",
- "line": 146,
+ "file": "scripts/tdd.py",
+ "line": 67,
"type": "function"
}
],
- "finalize": [
+ "tdd_enforce_refactor": [
{
- "file": "scripts/bootstrap.py",
- "line": 182,
+ "file": "scripts/tdd.py",
+ "line": 77,
"type": "function"
}
],
- "AGENTS_FILE": [
+ "TDD_STATE_FILE_DEFAULT": [
{
- "file": "scripts/bootstrap.py",
- "line": 13,
- "type": "variable"
- }
- ],
- "CLAUDE_FILE": [
- {
- "file": "scripts/bootstrap.py",
- "line": 14,
- "type": "variable"
- }
- ],
- "TEMPLATE_MAINTENANCE": [
- {
- "file": "scripts/bootstrap.py",
+ "file": "scripts/tdd.py",
"line": 15,
"type": "variable"
}
],
- "CONFIG_FILE": [
- {
- "file": "scripts/bootstrap.py",
- "line": 16,
- "type": "variable"
- },
- {
- "file": "scripts/code_index.py",
- "line": 19,
- "type": "variable"
- }
- ],
- "STANDARD_HEADERS": [
- {
- "file": "scripts/bootstrap.py",
- "line": 18,
- "type": "variable"
- }
- ],
- "PREAMBLE_IGNORE_PATTERNS": [
- {
- "file": "scripts/bootstrap.py",
- "line": 26,
- "type": "variable"
- }
- ],
- "generate_markdown": [
- {
- "file": "scripts/generate_tools.py",
- "line": 17,
- "type": "function"
- }
- ],
- "generate_shell": [
+ "upgrade": [
{
- "file": "scripts/generate_tools.py",
- "line": 41,
+ "file": "scripts/upgrade.py",
+ "line": 10,
"type": "function"
}
],
- "TOOLS_JSON": [
- {
- "file": "scripts/generate_tools.py",
- "line": 13,
- "type": "variable"
- }
- ],
- "TOOLS_MD": [
- {
- "file": "scripts/generate_tools.py",
- "line": 14,
- "type": "variable"
- }
- ],
- "TOOLS_SH": [
- {
- "file": "scripts/generate_tools.py",
- "line": 15,
- "type": "variable"
- }
- ],
"get_source_roots": [
{
"file": "scripts/code_index.py",
@@ -1834,69 +1375,132 @@
"type": "function"
}
],
- "cmd_search": [
+ "cmd_stats": [
{
"file": "scripts/code_index.py",
"line": 288,
"type": "function"
}
],
+ "cmd_search": [
+ {
+ "file": "scripts/code_index.py",
+ "line": 323,
+ "type": "function"
+ }
+ ],
"cmd_lookup": [
{
"file": "scripts/code_index.py",
- "line": 298,
+ "line": 333,
"type": "function"
}
],
"cmd_references": [
{
"file": "scripts/code_index.py",
- "line": 323,
+ "line": 358,
"type": "function"
}
],
- "get_latest_content": [
+ "CONFIG_FILE": [
{
- "file": "scripts/hooks/session_start.py",
- "line": 21,
+ "file": "scripts/code_index.py",
+ "line": 19,
+ "type": "variable"
+ }
+ ],
+ "check_linter_availability": [
+ {
+ "file": "scripts/quality.py",
+ "line": 24,
"type": "function"
}
],
- "write_atomic": [
+ "run_validation": [
{
- "file": "scripts/lib/io.py",
- "line": 7,
+ "file": "scripts/quality.py",
+ "line": 78,
"type": "function"
}
],
- "write_atomic_gzip": [
+ "run_lint": [
{
- "file": "scripts/lib/io.py",
- "line": 36,
+ "file": "scripts/quality.py",
+ "line": 106,
"type": "function"
}
],
- "read_text": [
+ "scaffold_test": [
{
- "file": "scripts/lib/io.py",
- "line": 60,
+ "file": "scripts/quality.py",
+ "line": 146,
"type": "function"
}
],
- "write_json": [
+ "cmd_verify": [
{
- "file": "scripts/lib/io.py",
- "line": 83,
+ "file": "scripts/quality.py",
+ "line": 215,
"type": "function"
}
],
- "read_json": [
+ "LINTERS": [
{
- "file": "scripts/lib/io.py",
- "line": 91,
+ "file": "scripts/quality.py",
+ "line": 18,
+ "type": "variable"
+ }
+ ],
+ "setup_workspace": [
+ {
+ "file": "scripts/workspace.py",
+ "line": 13,
"type": "function"
}
],
+ "get_latest_content": [
+ {
+ "file": "scripts/hooks/session_start.py",
+ "line": 21,
+ "type": "function"
+ }
+ ],
+ "Config": [
+ {
+ "file": "scripts/lib/config.py",
+ "line": 49,
+ "type": "class"
+ }
+ ],
+ "Config.load": [
+ {
+ "file": "scripts/lib/config.py",
+ "line": 53,
+ "type": "method"
+ }
+ ],
+ "Config._merge": [
+ {
+ "file": "scripts/lib/config.py",
+ "line": 77,
+ "type": "method"
+ }
+ ],
+ "get_config": [
+ {
+ "file": "scripts/lib/config.py",
+ "line": 86,
+ "type": "function"
+ }
+ ],
+ "DEFAULT_CONFIG": [
+ {
+ "file": "scripts/lib/config.py",
+ "line": 6,
+ "type": "variable"
+ }
+ ],
"FileLockException": [
{
"file": "scripts/lib/concurrency.py",
@@ -1946,73 +1550,38 @@
"type": "method"
}
],
- "Config": [
- {
- "file": "scripts/lib/config.py",
- "line": 49,
- "type": "class"
- }
- ],
- "Config.load": [
- {
- "file": "scripts/lib/config.py",
- "line": 53,
- "type": "method"
- }
- ],
- "Config._merge": [
- {
- "file": "scripts/lib/config.py",
- "line": 77,
- "type": "method"
- }
- ],
- "get_config": [
- {
- "file": "scripts/lib/config.py",
- "line": 86,
- "type": "function"
- }
- ],
- "DEFAULT_CONFIG": [
- {
- "file": "scripts/lib/config.py",
- "line": 6,
- "type": "variable"
- }
- ],
- "log_activity": [
+ "write_atomic": [
{
- "file": "scripts/lib/audit.py",
- "line": 6,
+ "file": "scripts/lib/io.py",
+ "line": 7,
"type": "function"
}
],
- "audit_log": [
+ "write_atomic_gzip": [
{
- "file": "scripts/lib/audit.py",
- "line": 47,
+ "file": "scripts/lib/io.py",
+ "line": 36,
"type": "function"
}
],
- "serialize": [
+ "read_text": [
{
- "file": "scripts/lib/audit.py",
- "line": 32,
+ "file": "scripts/lib/io.py",
+ "line": 60,
"type": "function"
}
],
- "decorator": [
+ "write_json": [
{
- "file": "scripts/lib/audit.py",
- "line": 51,
+ "file": "scripts/lib/io.py",
+ "line": 83,
"type": "function"
}
],
- "wrapper": [
+ "read_json": [
{
- "file": "scripts/lib/audit.py",
- "line": 52,
+ "file": "scripts/lib/io.py",
+ "line": 91,
"type": "function"
}
],
@@ -2050,6 +1619,41 @@
"line": 25,
"type": "function"
}
+ ],
+ "log_activity": [
+ {
+ "file": "scripts/lib/audit.py",
+ "line": 6,
+ "type": "function"
+ }
+ ],
+ "audit_log": [
+ {
+ "file": "scripts/lib/audit.py",
+ "line": 47,
+ "type": "function"
+ }
+ ],
+ "serialize": [
+ {
+ "file": "scripts/lib/audit.py",
+ "line": 32,
+ "type": "function"
+ }
+ ],
+ "decorator": [
+ {
+ "file": "scripts/lib/audit.py",
+ "line": 51,
+ "type": "function"
+ }
+ ],
+ "wrapper": [
+ {
+ "file": "scripts/lib/audit.py",
+ "line": 52,
+ "type": "function"
+ }
]
}
}
\ No newline at end of file
diff --git a/docs/interop/TOOLS.md b/docs/interop/TOOLS.md
deleted file mode 100644
index 63af898..0000000
--- a/docs/interop/TOOLS.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Agent Tools Reference
-
-This file is auto-generated. Do not edit manually.
-
-| Tool | Risk | Description | Usage |
-| :--- | :--- | :--- | :--- |
-| task_create | **L1** | Create a new development task. | `task_create(category, title, description)` |
-| task_breakdown | **L1** | Break down a feature into bite-sized tasks. | `task_breakdown(task_id, format)` |
-| task_list | **L0** | List existing tasks, optionally filtered by status or category. | `task_list(status, category, archived)` |
-| task_update | **L1** | Update the status of an existing task. | `task_update(task_id, status)` |
-| task_show | **L0** | Show the details of a specific task. | `task_show(task_id)` |
-| task_validate | **L0** | Validate all task files for integrity and correctness. | `task_validate(format)` |
-| code_index_stats | **L0** | Show codebase statistics. | `code_index_stats(format)` |
-| task_context | **L0** | Show tasks that are currently in progress. | `task_context()` |
-| task_archive | **L1** | Archive a completed task. | `task_archive(task_id)` |
-| memory_create | **L1** | Create a new long-term memory. | `memory_create(title, content, tags)` |
-| memory_list | **L0** | List existing memories, optionally filtered by tag. | `memory_list(tag, limit)` |
-| memory_read | **L0** | Read a specific memory. | `memory_read(filename)` |
-| index_impact | **L0** | Check impact of a file change on documentation. | `index_impact(file)` |
-| index_add | **L2** | Add or update a documentation index entry. | `index_add(doc, related, depends)` |
-| index_check | **L0** | Check integrity of the documentation index. | `index_check()` |
-| agent_send | **L1** | Send a message to another agent or "public". | `agent_send(recipient, message)` |
-| quality_verify | **L0** | Run full verification (tests and validation). | `quality_verify(format)` |
-| quality_test | **L0** | Run unit tests. | `quality_test(pattern, verbose, format)` |
-| quality_lint | **L0** | Run linters. | `quality_lint(files)` |
-| quality_map | **L1** | Scaffold a test file for a source file. | `quality_map(src_file)` |
-| agent_read | **L1** | Read messages from your inbox. | `agent_read(agent_id)` |
-| agent_list | **L0** | List active agents. | `agent_list()` |
-| code_index_init | **L2** | Initialize the code index by selecting source roots. | `code_index_init()` |
-| code_index_refresh | **L1** | Re-index the codebase. | `code_index_refresh()` |
-| code_index_list | **L0** | List indexed files. | `code_index_list(format)` |
-| code_index_search | **L0** | Search for symbols in the code index (fuzzy match). | `code_index_search(query, format)` |
-| code_index_lookup | **L0** | Lookup a specific symbol definition or symbols in a file. | `code_index_lookup(target, format)` |
-| code_index_references | **L0** | Find text references to a symbol in the codebase. | `code_index_references(symbol, format)` |
-| tdd_state | **L0** | Get the current TDD Enforcer state. | `tdd_state()` |
-| tdd_run | **L1** | Run the TDD Enforcer for the current state. | `tdd_run()` |
-| tdd_reset | **L0** | Reset the TDD Enforcer state back to RED. | `tdd_reset()` |
-| local_review | **L0** | Perform a local review of the code against a specific task's requirements to flag any critical severity issues. | `local_review(task_id, format)` |
diff --git a/docs/interop/tool_definitions.json b/docs/interop/tool_definitions.json
deleted file mode 100644
index 898fad0..0000000
--- a/docs/interop/tool_definitions.json
+++ /dev/null
@@ -1,615 +0,0 @@
-{
- "tools": [
- {
- "name": "task_create",
- "description": "Create a new development task.",
- "risk_level": "L1",
- "implementation": "python3 scripts/tasks.py create {category} \"{title}\" --desc \"{description}\"",
- "parameters": {
- "type": "object",
- "properties": {
- "category": {
- "type": "string",
- "enum": [
- "foundation",
- "infrastructure",
- "domain",
- "presentation",
- "migration",
- "features",
- "testing",
- "review",
- "security",
- "research"
- ],
- "description": "The category of the task."
- },
- "title": {
- "type": "string",
- "description": "The title of the task."
- },
- "description": {
- "type": "string",
- "description": "Detailed description of the task."
- }
- },
- "required": [
- "category",
- "title"
- ]
- }
- },
- {
- "name": "task_breakdown",
- "description": "Break down a feature into bite-sized tasks.",
- "risk_level": "L1",
- "implementation": "python3 scripts/tasks.py breakdown {task_id} --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "task_id": {
- "type": "string",
- "description": "The ID of the task to break down."
- },
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- },
- "required": [
- "task_id"
- ]
- }
- },
- {
- "name": "task_list",
- "description": "List existing tasks, optionally filtered by status or category.",
- "risk_level": "L0",
- "implementation": "python3 scripts/tasks.py list --status {status} --category {category}",
- "parameters": {
- "type": "object",
- "properties": {
- "status": {
- "type": "string",
- "enum": [
- "pending",
- "in_progress",
- "wip_blocked",
- "review_requested",
- "verified",
- "completed",
- "blocked",
- "cancelled",
- "deferred"
- ],
- "description": "Filter by task status."
- },
- "category": {
- "type": "string",
- "enum": [
- "foundation",
- "infrastructure",
- "domain",
- "presentation",
- "migration",
- "features",
- "testing",
- "review",
- "security",
- "research"
- ],
- "description": "Filter by task category."
- },
- "archived": {
- "type": "boolean",
- "description": "Include archived tasks in the list."
- }
- }
- }
- },
- {
- "name": "task_update",
- "description": "Update the status of an existing task.",
- "risk_level": "L1",
- "implementation": "python3 scripts/tasks.py update {task_id} {status}",
- "parameters": {
- "type": "object",
- "properties": {
- "task_id": {
- "type": "string",
- "description": "The ID of the task (e.g., FOUNDATION-20230521-120000)."
- },
- "status": {
- "type": "string",
- "enum": [
- "pending",
- "in_progress",
- "wip_blocked",
- "review_requested",
- "verified",
- "completed",
- "blocked",
- "cancelled",
- "deferred"
- ],
- "description": "The new status of the task."
- }
- },
- "required": [
- "task_id",
- "status"
- ]
- }
- },
- {
- "name": "task_show",
- "description": "Show the details of a specific task.",
- "risk_level": "L0",
- "implementation": "python3 scripts/tasks.py show {task_id}",
- "parameters": {
- "type": "object",
- "properties": {
- "task_id": {
- "type": "string",
- "description": "The ID of the task."
- }
- },
- "required": [
- "task_id"
- ]
- }
- },
- {
- "name": "task_validate",
- "description": "Validate all task files for integrity and correctness.",
- "risk_level": "L0",
- "implementation": "python3 scripts/tasks.py validate --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- }
- }
- },
- {
- "name": "code_index_stats",
- "description": "Show codebase statistics.",
- "risk_level": "L0",
- "implementation": "python3 scripts/code_index.py stats --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- }
- }
- },
- {
- "name": "task_context",
- "description": "Show tasks that are currently in progress.",
- "risk_level": "L0",
- "implementation": "python3 scripts/tasks.py context",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "task_archive",
- "description": "Archive a completed task.",
- "risk_level": "L1",
- "implementation": "python3 scripts/tasks.py archive {task_id}",
- "parameters": {
- "type": "object",
- "properties": {
- "task_id": {
- "type": "string",
- "description": "The ID of the task to archive."
- }
- },
- "required": [
- "task_id"
- ]
- }
- },
- {
- "name": "memory_create",
- "description": "Create a new long-term memory.",
- "risk_level": "L1",
- "implementation": "python3 scripts/memory.py create \"{title}\" \"{content}\" --tags \"{tags}\"",
- "parameters": {
- "type": "object",
- "properties": {
- "title": {
- "type": "string",
- "description": "The title of the memory."
- },
- "content": {
- "type": "string",
- "description": "The content of the memory."
- },
- "tags": {
- "type": "string",
- "description": "Comma-separated tags for the memory."
- }
- },
- "required": [
- "title",
- "content"
- ]
- }
- },
- {
- "name": "memory_list",
- "description": "List existing memories, optionally filtered by tag.",
- "risk_level": "L0",
- "implementation": "python3 scripts/memory.py list --tag {tag} --limit {limit}",
- "parameters": {
- "type": "object",
- "properties": {
- "tag": {
- "type": "string",
- "description": "Filter by tag."
- },
- "limit": {
- "type": "integer",
- "description": "Limit the number of results."
- }
- }
- }
- },
- {
- "name": "memory_read",
- "description": "Read a specific memory.",
- "risk_level": "L0",
- "implementation": "python3 scripts/memory.py read {filename}",
- "parameters": {
- "type": "object",
- "properties": {
- "filename": {
- "type": "string",
- "description": "The filename or slug of the memory to read."
- }
- },
- "required": [
- "filename"
- ]
- }
- },
- {
- "name": "index_impact",
- "description": "Check impact of a file change on documentation.",
- "risk_level": "L0",
- "implementation": "python3 scripts/index.py impact {file}",
- "parameters": {
- "type": "object",
- "properties": {
- "file": {
- "type": "string",
- "description": "The file path to check impact for."
- }
- },
- "required": [
- "file"
- ]
- }
- },
- {
- "name": "index_add",
- "description": "Add or update a documentation index entry.",
- "risk_level": "L2",
- "implementation": "python3 scripts/index.py add {doc} --related \"{related}\" --depends \"{depends}\"",
- "parameters": {
- "type": "object",
- "properties": {
- "doc": {
- "type": "string",
- "description": "Path to the document."
- },
- "related": {
- "type": "string",
- "description": "Comma-separated list of related files."
- },
- "depends": {
- "type": "string",
- "description": "Comma-separated list of dependency documents."
- }
- },
- "required": [
- "doc"
- ]
- }
- },
- {
- "name": "index_check",
- "description": "Check integrity of the documentation index.",
- "risk_level": "L0",
- "implementation": "python3 scripts/index.py check",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "agent_send",
- "description": "Send a message to another agent or \"public\".",
- "risk_level": "L1",
- "implementation": "python3 scripts/comm.py send {recipient} \"{message}\"",
- "parameters": {
- "type": "object",
- "properties": {
- "recipient": {
- "type": "string",
- "description": "Agent ID or \"public\"."
- },
- "message": {
- "type": "string",
- "description": "The content of the message."
- }
- },
- "required": [
- "recipient",
- "message"
- ]
- }
- },
- {
- "name": "quality_verify",
- "description": "Run full verification (tests and validation).",
- "risk_level": "L0",
- "implementation": "python3 scripts/quality.py verify --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- }
- }
- },
- {
- "name": "quality_test",
- "description": "Run unit tests.",
- "risk_level": "L0",
- "implementation": "python3 scripts/quality.py test {pattern} {verbose} --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "pattern": {
- "type": "string",
- "description": "Specific test pattern or file."
- },
- "verbose": {
- "type": "boolean",
- "description": "Verbose output.",
- "default": false
- },
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- }
- }
- },
- {
- "name": "quality_lint",
- "description": "Run linters.",
- "risk_level": "L0",
- "implementation": "python3 scripts/quality.py lint {files}",
- "parameters": {
- "type": "object",
- "properties": {
- "files": {
- "type": "string",
- "description": "Specific files to lint (space separated)."
- }
- }
- }
- },
- {
- "name": "quality_map",
- "description": "Scaffold a test file for a source file.",
- "risk_level": "L1",
- "implementation": "python3 scripts/quality.py map {src_file}",
- "parameters": {
- "type": "object",
- "properties": {
- "src_file": {
- "type": "string",
- "description": "Source file path."
- }
- },
- "required": ["src_file"]
- }
- },
- {
- "name": "agent_read",
- "description": "Read messages from your inbox.",
- "risk_level": "L1",
- "implementation": "python3 scripts/comm.py read {agent_id}",
- "parameters": {
- "type": "object",
- "properties": {
- "agent_id": {
- "type": "string",
- "description": "Your Agent ID."
- }
- },
- "required": [
- "agent_id"
- ]
- }
- },
- {
- "name": "agent_list",
- "description": "List active agents.",
- "risk_level": "L0",
- "implementation": "python3 scripts/comm.py list",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "code_index_init",
- "description": "Initialize the code index by selecting source roots.",
- "risk_level": "L2",
- "implementation": "python3 scripts/code_index.py init",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "code_index_refresh",
- "description": "Re-index the codebase.",
- "risk_level": "L1",
- "implementation": "python3 scripts/code_index.py index",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "code_index_list",
- "description": "List indexed files.",
- "risk_level": "L0",
- "implementation": "python3 scripts/code_index.py list --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- }
- }
- },
- {
- "name": "code_index_search",
- "description": "Search for symbols in the code index (fuzzy match).",
- "risk_level": "L0",
- "implementation": "python3 scripts/code_index.py search \"{query}\" --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "query": {
- "type": "string",
- "description": "The symbol name to search for."
- },
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- },
- "required": ["query"]
- }
- },
- {
- "name": "code_index_lookup",
- "description": "Lookup a specific symbol definition or symbols in a file.",
- "risk_level": "L0",
- "implementation": "python3 scripts/code_index.py lookup \"{target}\" --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "target": {
- "type": "string",
- "description": "Symbol name or file path."
- },
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- },
- "required": ["target"]
- }
- },
- {
- "name": "code_index_references",
- "description": "Find text references to a symbol in the codebase.",
- "risk_level": "L0",
- "implementation": "python3 scripts/code_index.py references \"{symbol}\" --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "symbol": {
- "type": "string",
- "description": "Symbol name."
- },
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- },
- "required": ["symbol"]
- }
- },
- {
- "name": "tdd_state",
- "description": "Get the current TDD Enforcer state.",
- "risk_level": "L0",
- "implementation": "python3 scripts/tdd.py state",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "tdd_run",
- "description": "Run the TDD Enforcer for the current state.",
- "risk_level": "L1",
- "implementation": "python3 scripts/tdd.py run",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "tdd_reset",
- "description": "Reset the TDD Enforcer state back to RED.",
- "risk_level": "L0",
- "implementation": "python3 scripts/tdd.py reset",
- "parameters": {
- "type": "object",
- "properties": {}
- }
- },
- {
- "name": "local_review",
- "description": "Perform a local review of the code against a specific task's requirements to flag any critical severity issues.",
- "risk_level": "L0",
- "implementation": "python3 scripts/review.py check --task-id {task_id} --format {format}",
- "parameters": {
- "type": "object",
- "properties": {
- "task_id": {
- "type": "string",
- "description": "The ID of the task to review."
- },
- "format": {
- "type": "string",
- "enum": ["text", "json"],
- "description": "Output format."
- }
- },
- "required": ["task_id"]
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/docs/memories/.keep b/docs/memories/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/memories/entities.json b/docs/memories/entities.json
deleted file mode 100644
index ed582b4..0000000
--- a/docs/memories/entities.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "Beta": [
- "TEST-COMPACT-V2"
- ],
- "Release": [
- "TEST-COMPACT-V2"
- ]
-}
diff --git a/docs/tasks/GUIDE.md b/docs/tasks/GUIDE.md
deleted file mode 100644
index 867689f..0000000
--- a/docs/tasks/GUIDE.md
+++ /dev/null
@@ -1,133 +0,0 @@
-# Task Documentation System Guide
-
-This guide explains how to create, maintain, and update task documentation. It provides a reusable system for tracking implementation work, decisions, and progress.
-
-## Core Philosophy
-**"If it's not documented in `docs/tasks/`, it didn't happen."**
-
-## Directory Structure
-Tasks are organized by category in `docs/tasks/`:
-- `foundation/`: Core architecture and setup
-- `infrastructure/`: Services, adapters, platform code
-- `domain/`: Business logic, use cases
-- `presentation/`: UI, state management
-- `features/`: End-to-end feature implementation
-- `migration/`: Refactoring, upgrades
-- `testing/`: Testing infrastructure
-- `review/`: Code reviews and PR analysis
-
-## Task Document Format
-
-We use **YAML Frontmatter** for metadata and **Markdown** for content.
-
-### Frontmatter (Required)
-```yaml
----
-id: FOUNDATION-20250521-103000 # Auto-generated Timestamp ID
-status: pending # Current status
-title: Initial Project Setup # Task Title
-priority: medium # high, medium, low
-created: 2025-05-21 10:30:00 # Creation timestamp
-category: foundation # Category
-type: task # task, story, bug, epic (Optional)
-sprint: Sprint 1 # Iteration identifier (Optional)
-estimate: 3 # Story points / T-shirt size (Optional)
-dependencies: TASK-001, TASK-002 # Comma separated list of IDs (Optional)
----
-```
-
-### Status Workflow
-1. `pending`: Created but not started.
-2. `in_progress`: Active development.
-3. `review_requested`: Implementation done, awaiting code review.
-4. `verified`: Reviewed and approved.
-5. `completed`: Merged and finalized.
-6. `wip_blocked` / `blocked`: Development halted.
-7. `cancelled` / `deferred`: Stopped or postponed.
-
-### Content Template
-```markdown
-# [Task Title]
-
-## Task Information
-- **Dependencies**: [List IDs]
-
-## Task Details
-[Description of what needs to be done]
-
-### Acceptance Criteria
-- [ ] Criterion 1
-- [ ] Criterion 2
-
-## Implementation Status
-### Completed Work
-- ✅ Implemented X (file.py)
-
-### Blockers
-[Describe blockers if any]
-```
-
-## Tools
-
-Use the `scripts/tasks` wrapper to manage tasks.
-
-```bash
-# Create a new task (standard)
-./scripts/tasks create foundation "Task Title"
-
-# Create an Agile Story in a Sprint
-./scripts/tasks create features "User Login" --type story --sprint "Sprint 1" --estimate 5
-
-# List tasks (can filter by sprint)
-./scripts/tasks list
-./scripts/tasks list --sprint "Sprint 1"
-
-# Find the next best task to work on (Smart Agent Mode)
-./scripts/tasks next
-
-# Update status
-./scripts/tasks update [TASK_ID] in_progress
-./scripts/tasks update [TASK_ID] review_requested
-./scripts/tasks update [TASK_ID] verified
-./scripts/tasks update [TASK_ID] completed
-
-# Migrate legacy tasks (if updating from older version)
-./scripts/tasks migrate
-```
-
-## Superpowers Workflows
-
-The system has been enhanced with several integrated workflow commands to guide feature development:
-
-- **Brainstorming:** `python3 scripts/design.py brainstorm --title "Feature" ...`
-- **Workspace Setup:** `python3 scripts/workspace.py setup [TASK_ID]`
-- **Micro Planning:** `scripts/tasks.py breakdown [TASK_ID]`
-- **TDD Enforcement:** `python3 scripts/tdd.py state`, `python3 scripts/tdd.py run`, `python3 scripts/tdd.py reset`
-- **Orchestration:** `python3 scripts/orchestrator.py run`, `python3 scripts/orchestrator.py assign`, `python3 scripts/orchestrator.py monitor`
-- **Local Review:** Pre-PR automated review via `scripts/review.py`
-
-## Agile Methodology
-
-This system supports Agile/Scrum workflows for LLM-Human collaboration.
-
-### Sprints
-- Tag tasks with `sprint: [Name]` to group them into iterations.
-- Use `./scripts/tasks list --sprint [Name]` to view the sprint backlog.
-
-### Estimation
-- Use `estimate: [Value]` (e.g., Fibonacci numbers 1, 2, 3, 5, 8) to size tasks.
-
-### Auto-Pilot
-- The `./scripts/tasks next` command uses an algorithm to determine the optimal next task based on:
- 1. Status (In Progress > Pending)
- 2. Dependencies (Unblocked > Blocked)
- 3. Sprint (Current Sprint > Backlog)
- 4. Priority (High > Low)
- 5. Type (Stories/Bugs > Tasks)
-
-## Agent Integration
-
-Agents (Claude, etc.) use this system to track their work.
-- Always check `./scripts/tasks context` or use `./scripts/tasks next` before starting.
-- Keep the task file updated with your progress.
-- Use `review_requested` when you need human feedback.
diff --git a/docs/tasks/INDEX.yaml b/docs/tasks/INDEX.yaml
deleted file mode 100644
index 6b748ad..0000000
--- a/docs/tasks/INDEX.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-# Task Dependency Index
-# Generated by scripts/tasks.py index
-
-docs/tasks/features/FEATURES-20260305-085909-JDI-integrate-superpowers-workflows.md:
-
-docs/tasks/features/FEATURES-20260305-171241-FGU-implement-phase-1-planning-and-isolation-brainstorming.md:
-
-docs/tasks/features/FEATURES-20260305-171247-LAG-implement-phase-1-planning-and-isolation-workspace-setup.md:
-
-docs/tasks/features/FEATURES-20260305-171335-FJF-implement-phase-2-granular-execution-micro-planning.md:
-
-docs/tasks/features/FEATURES-20260305-171341-JOO-implement-phase-2-granular-execution-tdd-enforcer.md:
-
-docs/tasks/features/FEATURES-20260305-171348-HVS-implement-phase-3-subagent-orchestration-orchestrator.md:
-
-docs/tasks/features/FEATURES-20260305-171432-HBF-implement-phase-3-subagent-orchestration-local-code-review.md:
-
-docs/tasks/features/FEATURES-20260305-171438-HMD-update-agentsmd-and-docstasksguidemd-to-reflect-new-workflows.md:
-
-docs/tasks/foundation/FOUNDATION-20260203-004709-URI-test-create-v2.md:
-
-docs/tasks/foundation/FOUNDATION-20260203-EVAL-BEADS.md:
-
-docs/tasks/foundation/FOUNDATION-20260203-EVAL-MEMORY.md:
- depends_on:
- - docs/tasks/foundation/FOUNDATION-20260203-EVAL-BEADS.md
-
-docs/tasks/foundation/FOUNDATION-20260306-022244-SBP-implement-parent-and-related-fields-in-frontmatter.md:
-
-docs/tasks/foundation/FOUNDATION-20260306-022253-BHT-add-visualization-for-task-network.md:
-
-docs/tasks/foundation/TEST-GRAPH-V2.md:
- depends_on:
- - docs/tasks/foundation/FOUNDATION-20260203-EVAL-MEMORY.md
diff --git a/docs/tasks/README.md b/docs/tasks/README.md
deleted file mode 100644
index 8e1d78b..0000000
--- a/docs/tasks/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Task Documentation System
-
-This project uses a structured task documentation system to track work, decisions, and progress.
-
-## Overview
-
-All implementation tasks are documented in this directory (`docs/tasks/`). This provides a permanent record of:
-- What was done
-- Why it was done
-- How it was done (including architectural decisions)
-- What problems were encountered
-
-## Structure
-
-Tasks are organized by category:
-
-- `foundation/`: Core architecture and setup
-- `infrastructure/`: Services, adapters, platform code
-- `domain/`: Use cases, repositories, business logic
-- `presentation/`: UI, state management
-- `migration/`: Refactoring and cleanup
-- `features/`: End-to-end feature implementation
-- `testing/`: Test infrastructure
-
-## Workflow
-
-1. **Create a Task**: Use `scripts/tasks.py create` to generate a new task file.
-2. **Update Progress**: Keep the task file updated as you work.
-3. **Complete**: Mark as completed when done.
-
-See [GUIDE.md](GUIDE.md) for detailed instructions on the format and process.
diff --git a/docs/tasks/domain/.gitkeep b/docs/tasks/domain/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/domain/.keep b/docs/tasks/domain/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/features/.gitkeep b/docs/tasks/features/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/features/.keep b/docs/tasks/features/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/features/FEATURES-20260305-085909-JDI-integrate-superpowers-workflows.md b/docs/tasks/features/FEATURES-20260305-085909-JDI-integrate-superpowers-workflows.md
deleted file mode 100644
index 4a4acee..0000000
--- a/docs/tasks/features/FEATURES-20260305-085909-JDI-integrate-superpowers-workflows.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-id: FEATURES-20260305-085909-JDI
-status: completed
-title: Integrate Superpowers Workflows
-priority: medium
-created: 2026-03-05 08:59:09
-dependencies:
-type: epic
-estimate: L
----
-
-# Integrate Superpowers Workflows
-
-Integrate concepts from the `obra/superpowers` repository into our Agent Harness to improve its agentic engineering capabilities. The core philosophy of `superpowers` involves rigorous workflows like TDD, Socratic brainstorming, and subagent-driven development.
-
-See the detailed architectural document in `docs/architecture/SUPERPOWERS_INTEGRATION.md`.
-
-## Subtasks
-
-- [x] Implement Phase 1: Planning & Isolation (`scripts/design.py`, `scripts/workspace.py`)
-- [x] Implement Phase 2: Granular Execution & TDD (`scripts/tasks.py breakdown`, `scripts/tdd.py`)
-- [x] Implement Phase 3: Subagent Orchestration & Review (`scripts/orchestrator.py`, `scripts/review.py`)
-- [x] Update `AGENTS.md` and `docs/tasks/GUIDE.md` to reflect new workflows.
diff --git a/docs/tasks/features/FEATURES-20260305-171241-FGU-implement-phase-1-planning-and-isolation-brainstorming.md b/docs/tasks/features/FEATURES-20260305-171241-FGU-implement-phase-1-planning-and-isolation-brainstorming.md
deleted file mode 100644
index 75b1eac..0000000
--- a/docs/tasks/features/FEATURES-20260305-171241-FGU-implement-phase-1-planning-and-isolation-brainstorming.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-id: FEATURES-20260305-171241-FGU
-status: completed
-title: Implement Phase 1 Planning and Isolation Brainstorming
-priority: medium
-created: 2026-03-05 17:12:41
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Implement Phase 1 Planning and Isolation Brainstorming
-
-Implement the `brainstorming` Skill (.claude/skills/brainstorming/) and its backing script `scripts/design.py`.
-
-The purpose of this skill is to enforce Socratic design refinement.
-
-Functionality:
-- Prompts the user with questions to refine the design before coding.
-- Generates a design document in `docs/architecture/` or `docs/features/`.
-- Creates an Epic task via `scripts/tasks.py`.
diff --git a/docs/tasks/features/FEATURES-20260305-171247-LAG-implement-phase-1-planning-and-isolation-workspace-setup.md b/docs/tasks/features/FEATURES-20260305-171247-LAG-implement-phase-1-planning-and-isolation-workspace-setup.md
deleted file mode 100644
index 86874e2..0000000
--- a/docs/tasks/features/FEATURES-20260305-171247-LAG-implement-phase-1-planning-and-isolation-workspace-setup.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-id: FEATURES-20260305-171247-LAG
-status: completed
-title: Implement Phase 1 Planning and Isolation Workspace Setup
-priority: medium
-created: 2026-03-05 17:12:47
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Implement Phase 1 Planning and Isolation Workspace Setup
-
-Implement the `workspace` Skill (.claude/skills/workspace/) and its backing script `scripts/workspace.py`.
-
-The purpose of this skill is to ensure clean isolation before coding.
-
-Functionality:
-- Automates branching.
-- Runs `scripts/quality.py verify` to establish a test baseline.
-- Ensures the environment is ready for the new task.
diff --git a/docs/tasks/features/FEATURES-20260305-171335-FJF-implement-phase-2-granular-execution-micro-planning.md b/docs/tasks/features/FEATURES-20260305-171335-FJF-implement-phase-2-granular-execution-micro-planning.md
deleted file mode 100644
index 1e57dbf..0000000
--- a/docs/tasks/features/FEATURES-20260305-171335-FJF-implement-phase-2-granular-execution-micro-planning.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-id: FEATURES-20260305-171335-FJF
-status: completed
-title: Implement Phase 2 Granular Execution Micro Planning
-priority: medium
-created: 2026-03-05 17:13:35
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Implement Phase 2 Granular Execution Micro Planning
-
-Implement the `micro-planning` Skill (.claude/skills/micro-planning/) and its backing script enhancements in `scripts/tasks.py`.
-
-The purpose of this skill is to break down a feature into bite-sized tasks.
-
-Functionality:
-- Introduces `scripts/tasks.py breakdown [TASK_ID]`.
-- Reads a design doc and generates a series of dependent micro-tasks in the task system.
diff --git a/docs/tasks/features/FEATURES-20260305-171341-JOO-implement-phase-2-granular-execution-tdd-enforcer.md b/docs/tasks/features/FEATURES-20260305-171341-JOO-implement-phase-2-granular-execution-tdd-enforcer.md
deleted file mode 100644
index 0f2d662..0000000
--- a/docs/tasks/features/FEATURES-20260305-171341-JOO-implement-phase-2-granular-execution-tdd-enforcer.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-id: FEATURES-20260305-171341-JOO
-status: completed
-title: Implement Phase 2 Granular Execution TDD Enforcer
-priority: medium
-created: 2026-03-05 17:13:41
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Implement Phase 2 Granular Execution TDD Enforcer
-
-Implement the `tdd-enforcer` Skill (.claude/skills/tdd/) and its backing script `scripts/tdd.py`.
-
-The purpose of this skill is to enforce the RED-GREEN-REFACTOR cycle.
-
-Functionality:
-- Acts as a state-machine tool that the agent must call.
-- State 1: Run test (must fail).
-- State 2: Implement code.
-- State 3: Run test (must pass).
-- State 4: Refactor.
-- Deletes code written before tests to enforce TDD.
diff --git a/docs/tasks/features/FEATURES-20260305-171348-HVS-implement-phase-3-subagent-orchestration-orchestrator.md b/docs/tasks/features/FEATURES-20260305-171348-HVS-implement-phase-3-subagent-orchestration-orchestrator.md
deleted file mode 100644
index 85a1700..0000000
--- a/docs/tasks/features/FEATURES-20260305-171348-HVS-implement-phase-3-subagent-orchestration-orchestrator.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-id: FEATURES-20260305-171348-HVS
-status: completed
-title: Implement Phase 3 Subagent Orchestration Orchestrator
-priority: medium
-created: 2026-03-05 17:13:48
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Implement Phase 3 Subagent Orchestration Orchestrator
-
-Implement the Orchestrator Feature (`scripts/orchestrator.py`).
-
-The purpose of this feature is to manage parallel agent execution.
-
-Functionality:
-- Reads pending micro-tasks.
-- Uses the existing "Hive" (`scripts/comm.py`) to assign tasks to subagents.
-- Monitors the completion of these subagents.
diff --git a/docs/tasks/features/FEATURES-20260305-171432-HBF-implement-phase-3-subagent-orchestration-local-code-review.md b/docs/tasks/features/FEATURES-20260305-171432-HBF-implement-phase-3-subagent-orchestration-local-code-review.md
deleted file mode 100644
index 48a0341..0000000
--- a/docs/tasks/features/FEATURES-20260305-171432-HBF-implement-phase-3-subagent-orchestration-local-code-review.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-id: FEATURES-20260305-171432-HBF
-status: completed
-title: Implement Phase 3 Subagent Orchestration Local Code Review
-priority: medium
-created: 2026-03-05 17:14:32
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Implement Phase 3 Subagent Orchestration Local Code Review
-
-Implement the `local-review` Skill (.claude/skills/local-review/) and its backing script `scripts/review.py`.
-
-The purpose of this skill is to perform a pre-PR automated review against the spec.
-
-Functionality:
-- Compares the implemented code against the original task document/design spec.
-- Flags severity issues.
-- Blocks the transition to `review_requested` if critical issues are found.
diff --git a/docs/tasks/features/FEATURES-20260305-171438-HMD-update-agentsmd-and-docstasksguidemd-to-reflect-new-workflows.md b/docs/tasks/features/FEATURES-20260305-171438-HMD-update-agentsmd-and-docstasksguidemd-to-reflect-new-workflows.md
deleted file mode 100644
index 6a56874..0000000
--- a/docs/tasks/features/FEATURES-20260305-171438-HMD-update-agentsmd-and-docstasksguidemd-to-reflect-new-workflows.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-id: FEATURES-20260305-171438-HMD
-status: completed
-title: Update AGENTS.md and docs/tasks/GUIDE.md to reflect new workflows
-priority: medium
-created: 2026-03-05 17:14:38
-dependencies:
-type: task
-part_of: [FEATURES-20260305-085909-JDI]
----
-
-# Update AGENTS.md and docs/tasks/GUIDE.md to reflect new workflows
-
-Update `AGENTS.md` and `docs/tasks/GUIDE.md` to reflect new superpowers workflows.
-
-The purpose of this task is to ensure documentation covers the newly added skills and features from the superpowers integration.
-
-Functionality:
-- Document the new Brainstorming, Workspace Setup, Micro Planning, TDD Enforcer, Orchestrator, and Local Code Review workflows in `AGENTS.md`.
-- Update `docs/tasks/GUIDE.md` to describe the new commands and processes related to these workflows.
diff --git a/docs/tasks/foundation/.gitkeep b/docs/tasks/foundation/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/foundation/.keep b/docs/tasks/foundation/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/foundation/FOUNDATION-20260203-004709-URI-test-create-v2.md b/docs/tasks/foundation/FOUNDATION-20260203-004709-URI-test-create-v2.md
deleted file mode 100644
index 74a70c2..0000000
--- a/docs/tasks/foundation/FOUNDATION-20260203-004709-URI-test-create-v2.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-id: FOUNDATION-20260203-004709-URI
-status: pending
-title: Test Create V2
-priority: medium
-created: 2026-02-03 00:47:09
-category: foundation
-created: 2026-02-03 00:47:09
-category: foundation
-dependencies:
-type: task
----
-
-# Test Create V2
-
-To be determined
diff --git a/docs/tasks/foundation/FOUNDATION-20260203-EVAL-BEADS.md b/docs/tasks/foundation/FOUNDATION-20260203-EVAL-BEADS.md
deleted file mode 100644
index f49862a..0000000
--- a/docs/tasks/foundation/FOUNDATION-20260203-EVAL-BEADS.md
+++ /dev/null
@@ -1,71 +0,0 @@
----
-id: FOUNDATION-20260203-EVAL-BEADS
-status: completed
-title: Evaluate Beads Features for Agent Harness Adoption
-priority: high
-created: 2026-02-03 00:30:00
-category: foundation
-type: task
-estimate: 5
-dependencies: []
----
-
-# Evaluate Beads Features for Agent Harness Adoption
-
-## Task Information
-- **Dependencies**: None
-
-## Context
-The [Beads](https://github.com/steveyegge/beads) project represents a significant evolution in "Git-backed Graph Issue Tracking" for AI agents. `agent-harness` currently uses a file-based task system (`docs/tasks/*.md`) managed by `scripts/tasks.py`. This evaluation focuses on specific high-value features from Beads that should be adopted to evolve `agent-harness` into a robust Agentic Framework.
-
-## Feature Analysis & Adoption Plan
-
-### 1. Graph Data Structure & Dependencies
-**Beads Approach**: Treating tasks as nodes in a standard graph (DAG). Explicit `parent <-> child` and `blocking <-> blocked_by` relationships.
-**Current Harness**: Loose dependency lists (`dependencies: [ID]`) in Frontmatter.
-**Improvement Proposal**:
-- Move from "List of Dependencies" to Typed Relationships:
- - `depends_on`: (Blocking)
- - `part_of`: (Parent/Child)
- - `related_to`: (Context)
-- **Action**: Update `scripts/tasks.py` to enforce and validate these relationships.
-- **Benefit**: Better context retrieval. When an agent works on a task, it can pull in the *parent* context automatically.
-
-### 2. Context Compaction (Memory Decay)
-**Beads Approach**: "Semantic memory decay" summarizes old closed tasks to save context window.
-**Current Harness**: Tasks just sit there. `archive/` folder exists but files are full size.
-**Improvement Proposal**:
-- Implement `scripts/tasks.py compact [TASK_ID]`.
-- Uses an LLM to read a completed task (and its subtasks) and replace the content with a high-level summary, moving the full details to a simplified archive or git history.
-- **Benefit**: Keeps the active "Context Window" of the project clean and small, enabling long-horizon agents to work without token overload.
-
-### 3. Distributed ID System
-**Beads Approach**: Hash-based IDs (`bd-a1b2`) derived from content/author to prevent collisions in distributed/branch-based workflows.
-**Current Harness**: Timestamp-based IDs (`CATEGORY-YYYYMMDD-HHMMSS`).
-**Analysis**: Timestamps are "okay" for single-user, but bad for widespread distributed teams. Hash IDs are superior for merging.
-**Recommendation**:
-- Stick with Timestamp IDs for now for human readability (easier to sort chronologically by eye).
-- **OR**: Adopt a hybrid `YYYYMMDD-HASH` to ensure uniqueness without losing sortability.
-
-### 4. "Auto-Ready" Task Detection
-**Beads Approach**: `bd ready` shows tasks that are unblocked (dependencies met).
-**Current Harness**: `scripts/tasks.py next` has a basic algorithm.
-**Improvement Proposal**:
-- Refine `next` logic to strictly hide tasks where `depends_on` tasks are not `completed` or `verified`.
-- Add a "Ready" state or view that only shows actionable items.
-
-## Implementation Roadmap
-
-### Phase 1: Core Graph Enhancements
-- [ ] **Data Model Update**: Update `scripts/tasks.py` to support `parent` and `related` fields in Frontmatter.
-- [ ] **Visualization**: Add `scripts/tasks.py graph` to output Mermaid/DOT graph of the task network.
-
-### Phase 2: Context Management
-- [ ] **Compaction Script**: Create `scripts/compact.py` (or add to `tasks.py`) to summarize verified tasks.
-
-### Phase 3: Developer Experience
-- [ ] **CLI Polish**: Alias `./scripts/tasks.py` to `tasks` or `ah` (AgentHarness) for ergonomics.
-
-## Acceptance Criteria
-- [x] Roadmap approved by user.
-- [x] Implementation tasks created for Phase 1.
diff --git a/docs/tasks/foundation/FOUNDATION-20260203-EVAL-MEMORY.md b/docs/tasks/foundation/FOUNDATION-20260203-EVAL-MEMORY.md
deleted file mode 100644
index c90ff20..0000000
--- a/docs/tasks/foundation/FOUNDATION-20260203-EVAL-MEMORY.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-id: FOUNDATION-20260203-EVAL-MEMORY
-status: completed
-title: Evaluate Advanced Memory and Entity Linking
-priority: medium
-created: 2026-02-03 00:40:00
-category: foundation
-type: story
-estimate: 5
-dependencies: [FOUNDATION-20260203-EVAL-BEADS]
----
-
-# Evaluate Advanced Memory and Entity Linking
-
-## Task Information
-- **Dependencies**: [FOUNDATION-20260203-EVAL-BEADS] (Compaction is a prerequisite for advanced summarization)
-
-## Context
-Evaluated ideas from [Aparna Dhinakaran's post](https://x.com/aparnadhinak/status/2016915570503938452) regarding large context windows and memory retrieval.
-
-## Key Ideas for Evaluation
-
-### 1. Multi-Lens Summaries
-**Concept**: Instead of a single summary, generate multiple summaries from different perspectives (e.g., "Technical Implementation", "Product Decisions", "Security Implications").
-**Application**: When running the `compact` script (from Beads evaluation), generate these facets in the archived file.
-**Benefit**: Allows agents to retrieve only the *facet* of information they need (e.g., "What security changes were made?" vs "How was this coded?").
-
-### 2. Entity Extraction & Linking
-**Concept**: Extract Named Entities (NER) from all texts and link them back to the original layer.
-**Application**:
-- Create a `docs/memories/entities/` index.
-- When an agent works on "Auth", it can look up "Auth" in the Entity Index and find links to every Task ID that touched "Auth".
-**Benefit**: "Pattern recognition and reasoning". Prevents "lost knowledge" where a decision is buried in a closed task.
-
-### 3. File System as Interface
-**Concept**: "Everything has gone file system. Get your data into file systems and give agent unix tools."
-**Validation**: This validates our approach of using Markdown files + `grep`/`find` as the primary interface for agents, rather than complex vector DB APIs. We should double down on `grep`-friendly formats.
-
-## Implementation Tasks (Proposed)
-
-1. [x] **Prototype Multi-Lens Compaction**:
- - Update `scripts/tasks.py compact` to support arguments for structured sections: `## Technical Implementation`, `## Product Decisions`, `## Unresolved / Security Implications`.
-2. [x] **Entity Indexer**:
- - Created `scripts/memory.py index` to scan `docs/tasks/` and build a `docs/memories/entities.json` map (Entity -> [Task IDs]).
-3. [x] **Hyperlink Enforcer**:
- - Handled implicitly: Entity Indexer encourages explicit `[[Entity]]` tags allowing humans and agents to follow references.
-
-## Acceptance Criteria
-- [x] Feasibility prototype of "Entity Indexer" (is it too expensive to run on every task?).
-- [x] Decision on "Multi-Lens" schema for Archived Tasks.
-
-## Evaluation Conclusion
-
-- **Entity Indexer Expense**: Running a regex scan over the entire tasks directory is currently O(N) where N is the number of task files. Because these files are typically small markdown files, python's filesystem and regex operations are extremely fast. For repositories up to several thousand tasks, the `scripts/memory.py index` tool runs almost instantaneously and poses no significant performance bottlenecks, so it is highly feasible.
-- **Multi-Lens Schema**: The "Multi-Lens" schema is successfully mapped to new CLI arguments (`--summary-tech`, `--summary-decisions`, `--summary-unresolved`) in the `scripts/tasks.py compact` tool. This structured approach provides clean segments inside the stub.
diff --git a/docs/tasks/foundation/FOUNDATION-20260306-022244-SBP-implement-parent-and-related-fields-in-frontmatter.md b/docs/tasks/foundation/FOUNDATION-20260306-022244-SBP-implement-parent-and-related-fields-in-frontmatter.md
deleted file mode 100644
index 8d6a069..0000000
--- a/docs/tasks/foundation/FOUNDATION-20260306-022244-SBP-implement-parent-and-related-fields-in-frontmatter.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-id: FOUNDATION-20260306-022244-SBP
-status: completed
-title: Implement parent and related fields in Frontmatter
-priority: medium
-created: 2026-03-06 02:22:44
-dependencies:
-type: task
----
-
-# Implement parent and related fields in Frontmatter
-
-Update scripts/tasks.py to support parent and related fields in Frontmatter.
diff --git a/docs/tasks/foundation/FOUNDATION-20260306-022253-BHT-add-visualization-for-task-network.md b/docs/tasks/foundation/FOUNDATION-20260306-022253-BHT-add-visualization-for-task-network.md
deleted file mode 100644
index b14def5..0000000
--- a/docs/tasks/foundation/FOUNDATION-20260306-022253-BHT-add-visualization-for-task-network.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-id: FOUNDATION-20260306-022253-BHT
-status: pending
-title: Add visualization for task network
-priority: medium
-created: 2026-03-06 02:22:53
-dependencies:
-type: task
----
-
-# Add visualization for task network
-
-Add scripts/tasks.py graph to output Mermaid/DOT graph of the task network.
diff --git a/docs/tasks/foundation/FOUNDATION-20260309-111149-LVP-evaluate-testing-task.md b/docs/tasks/foundation/FOUNDATION-20260309-111149-LVP-evaluate-testing-task.md
deleted file mode 100644
index 35bc831..0000000
--- a/docs/tasks/foundation/FOUNDATION-20260309-111149-LVP-evaluate-testing-task.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-id: FOUNDATION-20260309-111149-LVP
-status: pending
-title: Evaluate Testing Task
-priority: medium
-created: 2026-03-09 11:11:49
-dependencies:
-type: epic
----
-
-# Evaluate Testing Task
-
-To be determined
diff --git a/docs/tasks/foundation/TEST-GRAPH-V2.md b/docs/tasks/foundation/TEST-GRAPH-V2.md
deleted file mode 100644
index d946c55..0000000
--- a/docs/tasks/foundation/TEST-GRAPH-V2.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-id: TEST-GRAPH-V2
-status: pending
-title: Test V2 Graph Features
-priority: low
-created: 2026-02-03 01:00:00
-category: foundation
-part_of: [FOUNDATION-20260309-111149-LVP]
-dependencies: [FOUNDATION-20260203-EVAL-MEMORY]
-type: task
-estimate: 1
----
-
-# Test V2 Graph Features
-
-## Task Information
-- **Part Of**: FOUNDATION-20260203-EVAL
-- **Depends On**: FOUNDATION-20260203-EVAL-MEMORY
-
-## Task Details
-This is a dummy task to verify that `scripts/tasks.py visualize` draws the correct graph.
-- It should have a solid arrow FROM `FOUNDATION-20260203-EVAL-MEMORY` TO `TEST-GRAPH-V2`.
-- It should have a dotted arrow FROM `TEST-GRAPH-V2` TO `FOUNDATION-20260203-EVAL`.
-
-## Acceptance Criteria
-- [ ] Visualization confirms relationships.
diff --git a/docs/tasks/infrastructure/.gitkeep b/docs/tasks/infrastructure/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/infrastructure/.keep b/docs/tasks/infrastructure/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/migration/.gitkeep b/docs/tasks/migration/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/migration/.keep b/docs/tasks/migration/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/presentation/.gitkeep b/docs/tasks/presentation/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/presentation/.keep b/docs/tasks/presentation/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/research/.keep b/docs/tasks/research/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/review/.keep b/docs/tasks/review/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/security/.keep b/docs/tasks/security/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/testing/.gitkeep b/docs/tasks/testing/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/tasks/testing/.keep b/docs/tasks/testing/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/prompts/system/maintenance_mode.md b/prompts/system/maintenance_mode.md
index 7ceed0c..4761eda 100644
--- a/prompts/system/maintenance_mode.md
+++ b/prompts/system/maintenance_mode.md
@@ -6,25 +6,18 @@ You are an expert Software Engineer working on this project. Your primary respon
**"If it's not documented in `docs/tasks/`, it didn't happen."**
## Workflow
-1. **Pick a Task**: Run `python3 scripts/tasks.py context` to see active tasks, or `list` to see pending ones.
2. **Plan & Document**:
- * **Memory Check**: Run `python3 scripts/memory.py list` (or use the Memory Skill) to recall relevant long-term information.
* **Security Check**: Ask the user about specific security considerations for this task.
- * If starting a new task, use `scripts/tasks.py create` (or `python3 scripts/tasks.py create`) to generate a new task file.
- * Update the task status: `python3 scripts/tasks.py update [TASK_ID] in_progress`.
3. **Implement**: Write code, run tests.
4. **Update Documentation Loop**:
* As you complete sub-tasks, check them off in the task document.
* If you hit a blocker, update status to `wip_blocked` and describe the issue in the file.
* Record key architectural decisions in the task document.
- * **Memory Update**: If you learn something valuable for the long term, use `scripts/memory.py create` to record it.
5. **Review & Verify**:
* **Quality Check**: Run `python3 scripts/quality.py verify` to ensure tests and validation pass. **Do not request review if this fails.**
- * Once implementation is complete, update status to `review_requested`: `python3 scripts/tasks.py update [TASK_ID] review_requested`.
* Ask a human or another agent to review the code.
* Once approved and tested, update status to `verified`.
6. **Finalize**:
- * Update status to `completed`: `python3 scripts/tasks.py update [TASK_ID] completed`.
* Record actual effort in the file.
* Ensure all acceptance criteria are met.
@@ -35,7 +28,6 @@ You are an expert Software Engineer working on this project. Your primary respon
* **Context**: `./scripts/tasks context`
* **Update**: `./scripts/tasks update [ID] [status]`
* **Migrate**: `./scripts/tasks migrate` (Migrate legacy tasks to new format)
-* **Memory**: `./scripts/memory.py [create|list|read]`
* **JSON Output**: Add `--format json` to any command for machine parsing.
## Documentation Reference
@@ -53,7 +45,6 @@ You are an expert Software Engineer working on this project. Your primary respon
When performing a PR review, follow this "Human-in-the-loop" process to ensure depth and efficiency.
### 1. Preparation
-1. **Create Task**: `python3 scripts/tasks.py create review "Review PR #: "`
2. **Fetch Details**: Use `gh` to get the PR context.
* `gh pr view `
* `gh pr diff `
diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py
deleted file mode 100644
index eca343d..0000000
--- a/scripts/bootstrap.py
+++ /dev/null
@@ -1,302 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import shutil
-import subprocess
-
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-REPO_ROOT = os.path.dirname(SCRIPT_DIR)
-sys.path.append(REPO_ROOT)
-
-from scripts.lib import io, yaml, config
-
-AGENTS_FILE = os.path.join(REPO_ROOT, "AGENTS.md")
-CLAUDE_FILE = os.path.join(REPO_ROOT, "CLAUDE.md")
-TEMPLATE_MAINTENANCE = os.path.join(REPO_ROOT, "templates", "maintenance_mode.md")
-CONFIG_FILE = os.path.join(REPO_ROOT, "harness.config.yaml")
-
-STANDARD_HEADERS = [
- "Helper Scripts",
- "Agent Interoperability",
- "Step 1: Detect Repository State",
- "Step 2: Execution Strategy",
- "Step 3: Finalize & Switch to Maintenance Mode"
-]
-
-PREAMBLE_IGNORE_PATTERNS = [
- "# AI Agent Bootstrap Instructions",
- "# AI Agent Instructions",
- "**CURRENT STATUS: BOOTSTRAPPING MODE**",
- "You are an expert Software Architect",
- "Your current goal is to bootstrap",
-]
-
-def is_ignored_preamble_line(line):
- l = line.strip()
- # Keep empty lines to preserve spacing in custom content,
- # but we will strip the final result to remove excess whitespace.
- if not l:
- return False
-
- for p in PREAMBLE_IGNORE_PATTERNS:
- if p in l:
- return True
- return False
-
-def extract_custom_content(content):
- lines = content.splitlines()
- custom_sections = []
- preamble_lines = []
- current_header = None
- current_lines = []
-
- for line in lines:
- if line.startswith("## "):
- header = line[3:].strip()
-
- # Flush previous section
- if current_header:
- if current_header not in STANDARD_HEADERS:
- custom_sections.append((current_header, "\n".join(current_lines)))
- else:
- # Capture preamble (lines before first header)
- for l in current_lines:
- if not is_ignored_preamble_line(l):
- preamble_lines.append(l)
-
- current_header = header
- current_lines = []
- else:
- current_lines.append(line)
-
- # Flush last section
- if current_header:
- if current_header not in STANDARD_HEADERS:
- custom_sections.append((current_header, "\n".join(current_lines)))
- else:
- # If no headers found, everything is preamble
- for l in current_lines:
- if not is_ignored_preamble_line(l):
- preamble_lines.append(l)
-
- return "\n".join(preamble_lines).strip(), custom_sections
-
-def scaffold():
- # Create default config if missing
- if not os.path.exists(CONFIG_FILE):
- print(f"\nCreating default configuration: {CONFIG_FILE}")
- yaml.SimpleYaml.save(CONFIG_FILE, config.DEFAULT_CONFIG)
-
- # Scaffolding for Agents
- conf = config.get_config(REPO_ROOT)
-
- # Ensure security dir exists for permissions
- security_dir = os.path.join(REPO_ROOT, "docs", "security")
- os.makedirs(security_dir, exist_ok=True)
- permissions_file = os.path.join(security_dir, "PERMISSIONS.md")
- if not os.path.exists(permissions_file):
- # Create a simple default
- content = """# Agent Permissions
-
-| Level | Name | Description |
-| :--- | :--- | :--- |
-| **L0** | **Viewer** | Read-only |
-| **L1** | **Contributor** | Docs/Tasks |
-| **L2** | **Developer** | Source Code |
-| **L3** | **Admin** | Irreversible |
-
-*Default Level: L2*
-"""
- io.write_atomic(permissions_file, content)
- print(f"Created default permissions policy: {permissions_file}")
-
- agents_dir = os.path.join(REPO_ROOT, conf["agents"]["bus_dir"])
- registry_dir = os.path.join(agents_dir, "registry")
- messages_dir = os.path.join(agents_dir, "messages")
- public_dir = os.path.join(messages_dir, "public")
-
- for d in [registry_dir, messages_dir, public_dir]:
- if not os.path.exists(d):
- os.makedirs(d, exist_ok=True)
- # Add .keep file
- io.write_atomic(os.path.join(d, ".keep"), "")
- print(f"Created directory: {d}")
-
- # Update .gitignore if needed
- gitignore = os.path.join(REPO_ROOT, ".gitignore")
- if os.path.exists(gitignore):
- content = io.read_text(gitignore)
- # Ensure we ignore agent artifacts but keep the structure
- ignore_rules = [
- f"{conf['agents']['bus_dir']}/registry/*.json",
- f"{conf['agents']['bus_dir']}/messages/**/*.json",
- f"!{conf['agents']['bus_dir']}/**/.keep"
- ]
-
- new_rules = []
- for rule in ignore_rules:
- if rule not in content:
- new_rules.append(rule)
-
- if new_rules:
- with open(gitignore, "a") as f:
- f.write("\n" + "\n".join(new_rules) + "\n")
- print(f"Updated .gitignore with agent bus rules.")
-
-def check_state():
- print("Repository Analysis:")
-
- # Check if already in maintenance mode
- if os.path.exists(AGENTS_FILE):
- with open(AGENTS_FILE, "r") as f:
- content = f.read()
- if "BOOTSTRAPPING MODE" not in content:
- print("Status: MAINTENANCE MODE (AGENTS.md is already updated)")
- # Run scaffolding check silently or explicitly?
- # For now, let's run it to ensure upgrades get the new folders
- scaffold()
- print("To list tasks: python3 scripts/tasks.py list")
- return
-
- files = [f for f in os.listdir(REPO_ROOT) if not f.startswith(".")]
- print(f"Files in root: {len(files)}")
-
- if os.path.exists(os.path.join(REPO_ROOT, "src")) or os.path.exists(os.path.join(REPO_ROOT, "lib")) or os.path.exists(os.path.join(REPO_ROOT, ".git")):
- print("Status: EXISTING REPOSITORY (Found src/, lib/, or .git/)")
- else:
- print("Status: NEW REPOSITORY (Likely)")
-
- # Check for hooks
- hook_path = os.path.join(REPO_ROOT, ".git", "hooks", "pre-commit")
- if not os.path.exists(hook_path):
- print("\nTip: Run 'python3 scripts/tasks.py install-hooks' to enable safety checks.")
-
- scaffold()
-
- print("\nNext Steps:")
- print("1. Run 'python3 scripts/tasks.py init' to scaffold directories.")
- print("2. Run 'python3 scripts/tasks.py create foundation \"Initial Setup\"' to track your work.")
- print("3. Explore docs/architecture/ and docs/features/.")
- print("4. When ready to switch to maintenance mode, run: python3 scripts/bootstrap.py finalize --interactive")
-
-def finalize():
- interactive = "--interactive" in sys.argv
- print("Finalizing setup...")
- if not os.path.exists(TEMPLATE_MAINTENANCE):
- print(f"Error: Template {TEMPLATE_MAINTENANCE} not found.")
- sys.exit(1)
-
- # Safety check
- if os.path.exists(AGENTS_FILE):
- with open(AGENTS_FILE, "r") as f:
- content = f.read()
- if "BOOTSTRAPPING MODE" not in content and "--force" not in sys.argv:
- print("Error: AGENTS.md does not appear to be in bootstrapping mode.")
- print("Use --force to overwrite anyway.")
- sys.exit(1)
-
- # Ensure init is run
- print("Ensuring directory structure...")
- tasks_script = os.path.join(SCRIPT_DIR, "tasks.py")
- try:
- subprocess.check_call([sys.executable, tasks_script, "init"])
- except subprocess.CalledProcessError:
- print("Error: Failed to initialize directories.")
- sys.exit(1)
-
- # Analyze AGENTS.md for custom sections
- custom_sections = []
- custom_preamble = ""
- if os.path.exists(AGENTS_FILE):
- try:
- with open(AGENTS_FILE, "r") as f:
- current_content = f.read()
- custom_preamble, custom_sections = extract_custom_content(current_content)
- except Exception as e:
- print(f"Warning: Failed to parse AGENTS.md for custom sections: {e}")
-
- if interactive:
- print("\n--- Merge Analysis ---")
- if custom_preamble:
- print("[PRESERVED] Custom Preamble (lines before first header)")
- print(f" Snippet: {custom_preamble.splitlines()[0][:60]}...")
- else:
- print("[INFO] No custom preamble found.")
-
- if custom_sections:
- print(f"[PRESERVED] {len(custom_sections)} Custom Sections:")
- for header, _ in custom_sections:
- print(f" - {header}")
- else:
- print("[INFO] No custom sections found.")
-
- print("\n[REPLACED] The following standard bootstrapping sections will be replaced by Maintenance Mode instructions:")
- for header in STANDARD_HEADERS:
- print(f" - {header}")
-
- print(f"\n[ACTION] AGENTS.md will be backed up to AGENTS.md.bak")
-
- try:
- # Use input if available, but handle non-interactive environments
- response = input("\nProceed with finalization? [y/N] ")
- except EOFError:
- response = "n"
-
- if response.lower() not in ["y", "yes"]:
- print("Aborting.")
- sys.exit(0)
-
- # Backup AGENTS.md
- if os.path.exists(AGENTS_FILE):
- backup_file = AGENTS_FILE + ".bak"
- try:
- shutil.copy2(AGENTS_FILE, backup_file)
- print(f"Backed up AGENTS.md to {backup_file}")
- if not custom_sections and not custom_preamble and not interactive:
- print("IMPORTANT: If you added custom instructions to AGENTS.md, they are now in .bak")
- print("Please review AGENTS.md.bak and merge any custom context into the new AGENTS.md manually.")
- elif not interactive:
- print(f"NOTE: Custom sections/preamble were preserved in the new AGENTS.md.")
- print("Please review AGENTS.md.bak to ensure no other context was lost.")
- except Exception as e:
- print(f"Warning: Failed to backup AGENTS.md: {e}")
-
- # Read template
- with open(TEMPLATE_MAINTENANCE, "r") as f:
- content = f.read()
-
- # Prepend custom preamble
- if custom_preamble:
- content = custom_preamble + "\n\n" + content
-
- # Append custom sections
- if custom_sections:
- content += "\n"
- for header, body in custom_sections:
- content += f"\n## {header}\n{body}"
- if not interactive:
- print(f"Appended {len(custom_sections)} custom sections to new AGENTS.md")
-
- # Overwrite AGENTS.md
- with open(AGENTS_FILE, "w") as f:
- f.write(content)
-
- print(f"Updated {AGENTS_FILE} with maintenance instructions.")
-
- # Check CLAUDE.md symlink
- if os.path.islink(CLAUDE_FILE):
- print(f"{CLAUDE_FILE} is a symlink. Verified.")
- else:
- print(f"{CLAUDE_FILE} is NOT a symlink. Recreating it...")
- if os.path.exists(CLAUDE_FILE):
- os.remove(CLAUDE_FILE)
- os.symlink("AGENTS.md", CLAUDE_FILE)
- print("Symlink created.")
-
- print("\nBootstrapping Complete! The agent is now in Maintenance Mode.")
-
-if __name__ == "__main__":
- if len(sys.argv) > 1 and sys.argv[1] == "finalize":
- finalize()
- else:
- check_state()
diff --git a/scripts/design.py b/scripts/design.py
index a7cd009..2914feb 100755
--- a/scripts/design.py
+++ b/scripts/design.py
@@ -3,7 +3,6 @@
Brainstorming skill backing script.
Prompts the user with questions to refine the design before coding.
Generates a design document in docs/architecture/ or docs/features/.
-Creates an Epic task via scripts/tasks.py.
"""
import os
diff --git a/scripts/generate_tools.py b/scripts/generate_tools.py
deleted file mode 100644
index 59e0646..0000000
--- a/scripts/generate_tools.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import json
-
-# Setup paths
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-REPO_ROOT = os.getenv("TASKS_REPO_ROOT", os.path.dirname(SCRIPT_DIR))
-sys.path.append(REPO_ROOT)
-
-from scripts.lib import io
-
-TOOLS_JSON = os.path.join(REPO_ROOT, "docs", "interop", "tool_definitions.json")
-TOOLS_MD = os.path.join(REPO_ROOT, "docs", "interop", "TOOLS.md")
-TOOLS_SH = os.path.join(REPO_ROOT, "scripts", "tools.sh")
-
-def generate_markdown(tools):
- lines = [
- "# Agent Tools Reference",
- "",
- "This file is auto-generated. Do not edit manually.",
- "",
- "| Tool | Risk | Description | Usage |",
- "| :--- | :--- | :--- | :--- |"
- ]
-
- for tool in tools:
- name = tool["name"]
- risk = tool.get("risk_level", "N/A")
- desc = tool["description"]
-
- # Simple usage example
- params = tool["parameters"].get("properties", {})
- args = ", ".join([f"{k}" for k in params.keys()])
- usage = f"`{name}({args})`"
-
- lines.append(f"| {name} | **{risk}** | {desc} | {usage} |")
-
- return "\n".join(lines) + "\n"
-
-def generate_shell(tools):
- lines = [
- "#!/bin/bash",
- "# Auto-generated by scripts/generate_tools.py",
- "",
- "# Source this file to enable tool aliases in your shell",
- ""
- ]
-
- for tool in tools:
- name = tool["name"]
- impl = tool.get("implementation", "")
- if not impl:
- continue
-
- lines.append(f"function {name}() {{")
- lines.append(f" # Risk Level: {tool.get('risk_level', 'L1')}")
-
- # Extract the base command
- # We want to remove any part of the string from the first '{' onwards,
- # AND if the word before '{' looks like a flag (starts with -- or -), remove that too.
- import re
-
- # 1. Split by space
- parts = impl.split(' ')
- base_parts = []
- for p in parts:
- if '{' in p:
- # Found a placeholder. If the previous part was a flag, remove it.
- if base_parts and base_parts[-1].startswith('-'):
- base_parts.pop()
- break
- base_parts.append(p)
-
- base_cmd = ' '.join(base_parts).strip()
-
- lines.append(f" {base_cmd} \"$@\"")
- lines.append(f"}}")
- lines.append("")
-
- return "\n".join(lines)
-
-def main():
- if not os.path.exists(TOOLS_JSON):
- print(f"Error: {TOOLS_JSON} not found.")
- sys.exit(1)
-
- data = io.read_json(TOOLS_JSON)
- tools = data.get("tools", [])
-
- # Generate MD
- print(f"Generating {TOOLS_MD}...")
- md_content = generate_markdown(tools)
- io.write_atomic(TOOLS_MD, md_content)
-
- # Generate SH
- print(f"Generating {TOOLS_SH}...")
- sh_content = generate_shell(tools)
- io.write_atomic(TOOLS_SH, sh_content)
-
- print("Generation complete.")
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/memory.py b/scripts/memory.py
deleted file mode 100755
index f043487..0000000
--- a/scripts/memory.py
+++ /dev/null
@@ -1,326 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import argparse
-import json
-import datetime
-import re
-
-# Determine the root directory of the repo
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-# Allow overriding root for testing, similar to tasks.py
-REPO_ROOT = os.getenv("TASKS_REPO_ROOT", os.path.dirname(SCRIPT_DIR))
-sys.path.append(REPO_ROOT)
-from scripts.lib import io
-from collections import defaultdict
-
-
-MEMORY_DIR = os.path.join(REPO_ROOT, "docs", "memories")
-
-def init_memory():
- """Ensures the memory directory exists."""
- os.makedirs(MEMORY_DIR, exist_ok=True)
- if not os.path.exists(os.path.join(MEMORY_DIR, ".keep")):
- io.write_atomic(os.path.join(MEMORY_DIR, ".keep"), "")
-
- # Ensure entities index exists
- entities_path = os.path.join(MEMORY_DIR, "entities.json")
- if not os.path.exists(entities_path):
- io.write_json(entities_path, {})
-
-
-def slugify(text):
- """Creates a URL-safe slug from text."""
- text = text.lower().strip()
- return re.sub(r'[^a-z0-9-]', '-', text).strip('-')
-
-def create_memory(title, content, tags=None, output_format="text"):
- init_memory()
- tags = tags or []
- if isinstance(tags, str):
- tags = [t.strip() for t in tags.split(",") if t.strip()]
-
- date_str = datetime.date.today().isoformat()
- slug = slugify(title)
- if not slug:
- slug = "untitled"
-
- filename = f"{date_str}-{slug}.md"
- filepath = os.path.join(MEMORY_DIR, filename)
-
- # Handle duplicates by appending counter
- counter = 1
- while os.path.exists(filepath):
- filename = f"{date_str}-{slug}-{counter}.md"
- filepath = os.path.join(MEMORY_DIR, filename)
- counter += 1
-
- # Create Frontmatter
- fm = f"""---
-date: {date_str}
-title: "{title}"
-tags: {json.dumps(tags)}
-created: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
----
-"""
-
- full_content = fm + "\n" + content + "\n"
-
- try:
- io.write_atomic(filepath, full_content)
-
- if output_format == "json":
- print(json.dumps({
- "success": True,
- "filepath": filepath,
- "title": title,
- "date": date_str
- }))
- else:
- print(f"Created memory: {filepath}")
- except Exception as e:
- msg = f"Error creating memory: {e}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
-def list_memories(tag=None, limit=20, output_format="text"):
- if not os.path.exists(MEMORY_DIR):
- if output_format == "json":
- print(json.dumps([]))
- else:
- print("No memories found.")
- return
-
- memories = []
- try:
- files = [f for f in os.listdir(MEMORY_DIR) if f.endswith(".md") and f != ".keep"]
- except FileNotFoundError:
- files = []
-
- for f in files:
- path = os.path.join(MEMORY_DIR, f)
- try:
- content = io.read_text(path)
-
- # Extract basic info from frontmatter
- title = "Unknown"
- date = "Unknown"
- tags = []
-
- # Simple regex parsing to avoid YAML dependency
- m_title = re.search(r'^title:\s*"(.*)"', content, re.MULTILINE)
- if m_title:
- title = m_title.group(1)
- else:
- # Fallback: unquoted title
- m_title_uq = re.search(r'^title:\s*(.*)', content, re.MULTILINE)
- if m_title_uq: title = m_title_uq.group(1).strip()
-
- m_date = re.search(r'^date:\s*(.*)', content, re.MULTILINE)
- if m_date: date = m_date.group(1).strip()
-
- m_tags = re.search(r'^tags:\s*(\[.*\])', content, re.MULTILINE)
- if m_tags:
- try:
- tags = json.loads(m_tags.group(1))
- except:
- pass
-
- if tag and tag not in tags:
- continue
-
- memories.append({
- "filename": f,
- "title": title,
- "date": date,
- "tags": tags,
- "path": path
- })
- except Exception:
- # Skip unreadable files
- pass
-
- # Sort by date desc (filename usually works for YYYY-MM-DD prefix)
- memories.sort(key=lambda x: x["filename"], reverse=True)
- memories = memories[:limit]
-
- if output_format == "json":
- print(json.dumps(memories))
- else:
- if not memories:
- print("No memories found.")
- return
-
- print(f"{'Date':<12} {'Title'}")
- print("-" * 50)
- for m in memories:
- print(f"{m['date']:<12} {m['title']}")
-
-def index_memories(output_format="text"):
- """Scans tasks and memories to build an Entity Index."""
- if output_format != "json":
- print("Indexing entities...")
- entities = defaultdict(list)
-
- # helper to add ref
- def add_ref(entity, ref_id):
- if ref_id not in entities[entity]:
- entities[entity].append(ref_id)
-
- # 1. Scan Tasks
- docs_dir = os.path.join(REPO_ROOT, "docs", "tasks")
- for root, dirs, files in os.walk(docs_dir):
- for file in files:
- if not file.endswith(".md"): continue
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
- # Extract ID
- m_id = re.search(r'^id:\s*(.*)', content, re.MULTILINE)
- if not m_id: continue
- task_id = m_id.group(1).strip()
-
- # Extract Tags (if any - tasks usually don't have tags but might in future)
- m_tags = re.search(r'^tags:\s*(\[.*\])', content, re.MULTILINE)
- if m_tags:
- try:
- tags = json.loads(m_tags.group(1))
- for t in tags: add_ref(t, task_id)
- except: pass
-
- # Extract WikiLinks [[Entity]]
- wikilinks = re.findall(r'\[\[(.*?)\]\]', content)
- for link in wikilinks:
- add_ref(link, task_id)
-
- except: pass
-
- # 2. Scan Memories
- if os.path.exists(MEMORY_DIR):
- for file in os.listdir(MEMORY_DIR):
- if not file.endswith(".md"): continue
- path = os.path.join(MEMORY_DIR, file)
- try:
- content = io.read_text(path)
- # Use filename as ID for memories
- mem_id = file
-
- # Tags
- m_tags = re.search(r'^tags:\s*(\[.*\])', content, re.MULTILINE)
- if m_tags:
- try:
- tags = json.loads(m_tags.group(1))
- for t in tags: add_ref(t, mem_id)
- except: pass
-
- # WikiLinks
- wikilinks = re.findall(r'\[\[(.*?)\]\]', content)
- for link in wikilinks:
- add_ref(link, mem_id)
- except: pass
-
- # Save Index
- index_path = os.path.join(MEMORY_DIR, "entities.json")
- io.write_json(index_path, entities)
-
- if output_format == "json":
- print(json.dumps({"success": True, "count": len(entities), "path": index_path}))
- else:
- print(f"Indexed {len(entities)} entities to {index_path}")
-
-
-def read_memory(filename, output_format="text"):
- path = os.path.join(MEMORY_DIR, filename)
- if not os.path.exists(path):
- # Try finding by partial match if not exact
- if os.path.exists(MEMORY_DIR):
- matches = [f for f in os.listdir(MEMORY_DIR) if filename in f and f.endswith(".md")]
- if len(matches) == 1:
- path = os.path.join(MEMORY_DIR, matches[0])
- elif len(matches) > 1:
- msg = f"Error: Ambiguous memory identifier '{filename}'. Matches: {', '.join(matches)}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
- else:
- msg = f"Error: Memory file '{filename}' not found."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
- else:
- msg = f"Error: Memory directory does not exist."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- try:
- content = io.read_text(path)
-
- if output_format == "json":
- print(json.dumps({"filename": os.path.basename(path), "content": content}))
- else:
- print(content)
- except Exception as e:
- msg = f"Error reading file: {e}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
-def main():
- # Common argument for format
- parent_parser = argparse.ArgumentParser(add_help=False)
- parent_parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
-
- parser = argparse.ArgumentParser(description="Manage long-term memories")
-
- subparsers = parser.add_subparsers(dest="command")
-
- # Create
- create_parser = subparsers.add_parser("create", parents=[parent_parser], help="Create a new memory")
- create_parser.add_argument("title", help="Title of the memory")
- create_parser.add_argument("content", help="Content of the memory")
- create_parser.add_argument("--tags", help="Comma-separated tags")
-
- # List
- list_parser = subparsers.add_parser("list", parents=[parent_parser], help="List memories")
- list_parser.add_argument("--tag", help="Filter by tag")
- list_parser.add_argument("--limit", type=int, default=20, help="Max results")
-
- # Read
- read_parser = subparsers.add_parser("read", parents=[parent_parser], help="Read a memory")
- read_parser.add_argument("filename", help="Filename or slug part")
-
- # Index
- subparsers.add_parser("index", parents=[parent_parser], help="Rebuild entity index")
-
-
- args = parser.parse_args()
-
- # Default format to text if not present (though parents default handles it)
- fmt = getattr(args, "format", "text")
-
- if args.command == "create":
- create_memory(args.title, args.content, args.tags, fmt)
- elif args.command == "list":
- list_memories(args.tag, args.limit, fmt)
- elif args.command == "read":
- read_memory(args.filename, fmt)
- elif args.command == "index":
- index_memories(fmt)
-
- else:
- parser.print_help()
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/orchestrator.py b/scripts/orchestrator.py
deleted file mode 100755
index ce5c07f..0000000
--- a/scripts/orchestrator.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import json
-import time
-import argparse
-
-# Setup paths
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-REPO_ROOT = os.getenv("TASKS_REPO_ROOT", os.path.dirname(SCRIPT_DIR))
-sys.path.append(REPO_ROOT)
-
-from scripts import tasks
-from scripts import comm
-
-class Orchestrator:
- def __init__(self, agent_id="orchestrator"):
- self.agent_id = agent_id
- self.comm_bus = comm.Comm(agent_id=self.agent_id, role="orchestrator")
- self.comm_bus.register()
-
- def get_pending_tasks(self):
- """Retrieve tasks with 'pending' status."""
- # Using tasks module directly. list_tasks can return list of dicts.
- pending = tasks.list_tasks(status="pending", output_format="json")
- if isinstance(pending, str):
- try:
- pending = json.loads(pending)
- except json.JSONDecodeError:
- pending = []
- return pending
-
- def assign_tasks(self):
- """Assign pending tasks to available agents."""
- pending_tasks = self.get_pending_tasks()
- if not pending_tasks:
- return 0
-
- agents = self.comm_bus.list_agents()
- available_agents = [a for a in agents if a.get("id") != self.agent_id]
- if not available_agents:
- return 0
-
- assigned_count = 0
- for task in pending_tasks:
- # Simple round-robin or greedy assignment
- agent = available_agents[assigned_count % len(available_agents)]
- agent_id = agent.get("id")
-
- # Send message to agent
- msg_content = json.dumps({"task_id": task["id"], "action": "execute"})
- self.comm_bus.send(recipient_id=agent_id, content=msg_content, type="task_assignment", request_receipt=True)
-
- # Update task status to in_progress
- tasks.update_task_status(task["id"], "in_progress", output_format="json")
- assigned_count += 1
-
- return assigned_count
-
- def monitor(self):
- """Monitor messages for task completions and update statuses."""
- messages = self.comm_bus.read()
- processed_count = 0
- for msg in messages:
- msg_type = msg.get("type", "")
- content = msg.get("content", "")
-
- if msg_type == "task_completion":
- try:
- data = json.loads(content)
- task_id = data.get("task_id")
- if task_id:
- tasks.update_task_status(task_id, "completed", output_format="json")
- processed_count += 1
- except json.JSONDecodeError:
- pass
- return processed_count
-
- def run(self, interval=5):
- """Unified loop to repeatedly assign tasks and monitor."""
- print(f"Orchestrator {self.agent_id} starting run loop. Press Ctrl+C to stop.")
- try:
- while True:
- assigned = self.assign_tasks()
- if assigned > 0:
- print(f"Assigned {assigned} tasks.")
-
- processed = self.monitor()
- if processed > 0:
- print(f"Processed {processed} completion messages.")
-
- time.sleep(interval)
- except KeyboardInterrupt:
- print("Orchestrator stopped.")
-
-def main():
- parser = argparse.ArgumentParser(description="Subagent Orchestrator")
- subparsers = parser.add_subparsers(dest="command")
-
- # Assign
- subparsers.add_parser("assign", help="Assign pending tasks to available agents once")
-
- # Monitor
- subparsers.add_parser("monitor", help="Check for completion messages once")
-
- # Run
- run_parser = subparsers.add_parser("run", help="Run the orchestrator loop")
- run_parser.add_argument("--interval", type=int, default=5, help="Polling interval in seconds")
-
- args = parser.parse_args()
-
- orchestrator = Orchestrator()
-
- if args.command == "assign":
- count = orchestrator.assign_tasks()
- print(f"Assigned {count} tasks.")
- elif args.command == "monitor":
- count = orchestrator.monitor()
- print(f"Processed {count} messages.")
- elif args.command == "run":
- orchestrator.run(interval=args.interval)
- else:
- parser.print_help()
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/quality.py b/scripts/quality.py
index 4e91c15..3ee0e7a 100644
--- a/scripts/quality.py
+++ b/scripts/quality.py
@@ -77,7 +77,6 @@ def run_tests(pattern=None, verbose=False, output_format="text"):
def run_validation(output_format="text"):
"""Runs task validation script."""
- cmd = [sys.executable, os.path.join(SCRIPT_DIR, "tasks.py"), "validate", "--format", "json"]
try:
result = subprocess.run(cmd, capture_output=True, text=True)
diff --git a/scripts/review.py b/scripts/review.py
deleted file mode 100755
index 87e4f4b..0000000
--- a/scripts/review.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import json
-import argparse
-import subprocess
-
-# Setup paths
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-REPO_ROOT = os.getenv("TASKS_REPO_ROOT", os.path.dirname(SCRIPT_DIR))
-sys.path.append(REPO_ROOT)
-
-from scripts import tasks
-from scripts.lib import io
-
-def check_task(task_id, mock_failure=False, output_format="text"):
- """
- Performs a pre-PR automated review against the spec.
- Returns True if passed, False if critical issues found.
- """
- filepath = tasks.find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- return False
-
- content = io.read_text(filepath)
-
- # Mock finding critical issues if mock_failure is true or CRITICAL_ISSUE is in the file
- if mock_failure or "CRITICAL_ISSUE" in content:
- msg = f"Critical Issue: The implementation of {task_id} does not match the spec."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- return False
-
- msg = f"Success: No critical issues found for {task_id}."
- if output_format == "json":
- print(json.dumps({"success": msg}))
- else:
- print(msg)
- return True
-
-def main():
- parser = argparse.ArgumentParser(description="Local Code Review")
- subparsers = parser.add_subparsers(dest="command")
-
- # Check
- check_parser = subparsers.add_parser("check", help="Perform automated review against the spec")
- check_parser.add_argument("--task-id", required=True, help="The ID of the task to review")
- check_parser.add_argument("--mock-failure", action="store_true", help="Mock a critical issue")
- check_parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
-
- args = parser.parse_args()
-
- if args.command == "check":
- if check_task(args.task_id, mock_failure=args.mock_failure, output_format=args.format):
- sys.exit(0)
- else:
- sys.exit(1)
- else:
- parser.print_help()
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/tasks b/scripts/tasks
deleted file mode 100755
index 9c4d703..0000000
--- a/scripts/tasks
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-# Wrapper for tasks.py to ensure Python 3 is available
-
-if ! command -v python3 &> /dev/null; then
- echo "Error: Python 3 is not installed or not in PATH."
- echo "Please install Python 3 to use the task manager."
- exit 1
-fi
-
-# Get the directory of this script
-SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-
-# Execute tasks.py
-exec python3 "$SCRIPT_DIR/tasks.py" "$@"
diff --git a/scripts/tasks.py b/scripts/tasks.py
deleted file mode 100755
index 74ba906..0000000
--- a/scripts/tasks.py
+++ /dev/null
@@ -1,1414 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import shutil
-import subprocess
-import argparse
-import re
-import json
-import random
-import string
-from datetime import datetime
-
-# Determine the root directory of the repo
-# Assumes this script is in scripts/
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-REPO_ROOT = os.getenv("TASKS_REPO_ROOT", os.path.dirname(SCRIPT_DIR))
-sys.path.append(REPO_ROOT) # Enable imports from repo root
-
-from scripts.lib import io
-from scripts.lib.config import get_config
-from scripts.lib import audit
-
-# Load Configuration
-config = get_config(REPO_ROOT)
-
-DOCS_DIR = os.path.join(REPO_ROOT, config["tasks"]["dir"])
-TEMPLATES_DIR = os.path.join(REPO_ROOT, "templates")
-
-CATEGORIES = config["tasks"]["categories"]
-VALID_STATUSES = config["tasks"]["statuses"]
-VALID_TYPES = config["tasks"]["types"]
-
-ARCHIVE_DIR_NAME = "archive"
-
-def init_docs():
- """Scaffolds the documentation directory structure."""
- print("Initializing documentation structure...")
-
- # Create docs/tasks/ directories
- for category in CATEGORIES:
- path = os.path.join(DOCS_DIR, category)
- os.makedirs(path, exist_ok=True)
- # Create .keep file to ensure git tracks the directory
- io.write_atomic(os.path.join(path, ".keep"), "")
-
- # Copy GUIDE.md if missing
- guide_path = os.path.join(DOCS_DIR, "GUIDE.md")
- guide_template = os.path.join(TEMPLATES_DIR, "GUIDE.md")
- if not os.path.exists(guide_path) and os.path.exists(guide_template):
- shutil.copy(guide_template, guide_path)
- print(f"Created {guide_path}")
-
- # Create other doc directories
- for doc_type in ["architecture", "features", "security"]:
- path = os.path.join(REPO_ROOT, "docs", doc_type)
- os.makedirs(path, exist_ok=True)
- readme_path = os.path.join(path, "README.md")
- if not os.path.exists(readme_path):
- if doc_type == "security":
- content = """# Security Documentation
-
-Use this section to document security considerations, risks, and mitigations.
-
-## Risk Assessment
-* [ ] Threat Model
-* [ ] Data Privacy
-
-## Compliance
-* [ ] Requirements
-
-## Secrets Management
-* [ ] Policy
-"""
- else:
- content = f"# {doc_type.capitalize()} Documentation\n\nAdd {doc_type} documentation here.\n"
-
- io.write_atomic(readme_path, content)
-
- # Create memories directory
- memories_path = os.path.join(REPO_ROOT, "docs", "memories")
- os.makedirs(memories_path, exist_ok=True)
- if not os.path.exists(os.path.join(memories_path, ".keep")):
- io.write_atomic(os.path.join(memories_path, ".keep"), "")
-
- print(f"Created directories in {os.path.join(REPO_ROOT, 'docs')}")
-
-def generate_task_id(category):
- """Generates a timestamp-based ID to avoid collisions."""
- timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
- suffix = ''.join(random.choices(string.ascii_uppercase, k=3))
- return f"{category.upper()}-{timestamp}-{suffix}"
-
-def extract_frontmatter(content):
- """Extracts YAML frontmatter if present."""
- # Check if it starts with ---
- if not re.match(r"^\s*---\s*(\n|$)", content):
- return None, content
-
- # Find the second ---
- lines = content.splitlines(keepends=True)
- if not lines:
- return None, content
-
- yaml_lines = []
- body_start_idx = -1
-
- # Skip the first line (delimiter)
- for i, line in enumerate(lines[1:], 1):
- if re.match(r"^\s*---\s*(\n|$)", line):
- body_start_idx = i + 1
- break
- yaml_lines.append(line)
-
- if body_start_idx == -1:
- # No closing delimiter found
- return None, content
-
- yaml_block = "".join(yaml_lines)
- body = "".join(lines[body_start_idx:])
-
- data = {}
- for line in yaml_block.splitlines():
- line = line.strip()
- if not line or line.startswith("#"):
- continue
- if ":" in line:
- key, val = line.split(":", 1)
- data[key.strip()] = val.strip()
-
- return data, body
-
-def parse_task_content(content, filepath=None):
- """Parses task markdown content into a dictionary."""
-
- # Try Frontmatter first
- frontmatter, body = extract_frontmatter(content)
- if frontmatter:
- def parse_list(val):
- if not val: return []
- val = val.strip(" []")
- if not val: return []
- return [d.strip() for d in val.split(",") if d.strip()]
-
- deps = parse_list(frontmatter.get("dependencies") or frontmatter.get("depends_on"))
- part_of = parse_list(frontmatter.get("part_of"))
- related_to = parse_list(frontmatter.get("related_to"))
-
-
- return {
- "id": frontmatter.get("id", "unknown"),
- "status": frontmatter.get("status", "unknown"),
- "title": frontmatter.get("title", "No Title"),
- "priority": frontmatter.get("priority", "medium"),
- "type": frontmatter.get("type", "task"),
- "sprint": frontmatter.get("sprint", ""),
- "estimate": frontmatter.get("estimate", ""),
- "dependencies": deps,
- "depends_on": deps,
- "part_of": part_of,
- "related_to": related_to,
- "filepath": filepath,
- "content": content
- }
-
- # Fallback to Legacy Regex Parsing
- id_match = re.search(r"\*\*Task ID\*\*: ([\w-]+)", content)
- status_match = re.search(r"\*\*Status\*\*: ([\w_]+)", content)
- title_match = re.search(r"# Task: (.+)", content)
- priority_match = re.search(r"\*\*Priority\*\*: ([\w]+)", content)
-
- task_id = id_match.group(1) if id_match else "unknown"
- status = status_match.group(1) if status_match else "unknown"
- title = title_match.group(1).strip() if title_match else "No Title"
- priority = priority_match.group(1) if priority_match else "unknown"
-
- return {
- "id": task_id,
- "status": status,
- "title": title,
- "priority": priority,
- "type": "task",
- "sprint": "",
- "estimate": "",
- "dependencies": [],
- "depends_on": [],
- "part_of": [],
- "related_to": [],
- "filepath": filepath,
- "content": content
- }
-
-@audit.audit_log("task_create")
-def create_task(category, title, description, priority="medium", status="pending", dependencies=None, part_of=None, related_to=None, task_type="task", sprint="", estimate="", output_format="text"):
- if category not in CATEGORIES:
- msg = f"Error: Category '{category}' not found. Available: {', '.join(CATEGORIES)}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- task_id = generate_task_id(category)
-
- slug = title.lower().replace(" ", "-")
- # Sanitize slug
- slug = re.sub(r'[^a-z0-9-]', '', slug)
- filename = f"{task_id}-{slug}.md"
- filepath = os.path.join(DOCS_DIR, category, filename)
-
- # New YAML Frontmatter Format
- deps_str = ""
- if dependencies:
- # Use Flow style list
- deps_str = "[" + ", ".join(dependencies) + "]"
-
- extra_fm = ""
- if task_type:
- extra_fm += f"type: {task_type}\n"
- if sprint:
- extra_fm += f"sprint: {sprint}\n"
- if estimate:
- extra_fm += f"estimate: {estimate}\n"
- if part_of:
- # Flow style list
- val = "[" + ", ".join(part_of) + "]"
- extra_fm += f"part_of: {val}\n"
- if related_to:
- val = "[" + ", ".join(related_to) + "]"
- extra_fm += f"related_to: {val}\n"
-
-
- content = f"""---
-id: {task_id}
-status: {status}
-title: {title}
-priority: {priority}
-created: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
-dependencies: {deps_str}
-{extra_fm}---
-
-# {title}
-
-{description}
-"""
-
- os.makedirs(os.path.dirname(filepath), exist_ok=True)
- io.write_atomic(filepath, content)
-
- if output_format == "json":
- print(json.dumps({
- "id": task_id,
- "title": title,
- "filepath": filepath,
- "status": status,
- "priority": priority,
- "type": task_type
- }))
- else:
- print(f"Created task: {filepath}")
-
-def find_task_file(task_id):
- """Finds the file path for a given task ID."""
- task_id = task_id.upper()
-
- # Optimization: Check if ID starts with a known category
- parts = task_id.split('-')
- if len(parts) > 1:
- category = parts[0].lower()
- if category in CATEGORIES:
- category_dir = os.path.join(DOCS_DIR, category)
- if os.path.exists(category_dir):
- for file in os.listdir(category_dir):
- if file.startswith(task_id) and file.endswith(".md"):
- return os.path.join(category_dir, file)
- # Fallback to full search if not found in expected category (e.g. moved to archive)
-
- for root, _, files in os.walk(DOCS_DIR):
- for file in files:
- # Match strictly on ID at start of filename or substring
- # New ID: FOUNDATION-2023...
- # Old ID: FOUNDATION-001
- if file.startswith(task_id) and file.endswith(".md"):
- return os.path.join(root, file)
- return None
-
-def show_task(task_id, output_format="text"):
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- try:
- content = io.read_text(filepath)
-
- if output_format == "json":
- task_data = parse_task_content(content, filepath)
- print(json.dumps(task_data))
- else:
- print(content)
- except Exception as e:
- msg = f"Error reading file: {e}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
-def delete_task(task_id, output_format="text"):
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- try:
- os.remove(filepath)
- if output_format == "json":
- print(json.dumps({"success": True, "id": task_id, "message": "Deleted task"}))
- else:
- print(f"Deleted task: {task_id}")
- except Exception as e:
- msg = f"Error deleting file: {e}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
-def archive_task(task_id, output_format="text"):
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- try:
- archive_dir = os.path.join(DOCS_DIR, ARCHIVE_DIR_NAME)
- os.makedirs(archive_dir, exist_ok=True)
- filename = os.path.basename(filepath)
- new_filepath = os.path.join(archive_dir, filename)
-
- os.rename(filepath, new_filepath)
-
- if output_format == "json":
- print(json.dumps({"success": True, "id": task_id, "message": "Archived task", "new_path": new_filepath}))
- else:
- print(f"Archived task: {task_id} -> {new_filepath}")
-
- except Exception as e:
- msg = f"Error archiving task: {e}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
-def compact_task(task_id, summary=None, summary_tech=None, summary_decisions=None, summary_unresolved=None, output_format="text"):
- """
- Archives a task content and replaces it with a summary stub.
- """
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- print(json.dumps({"error": msg}) if output_format == "json" else msg)
- sys.exit(1)
-
- try:
- content = io.read_text(filepath)
- task_data = parse_task_content(content, filepath)
-
- # 1. Archive
- archive_dir = os.path.join(DOCS_DIR, ARCHIVE_DIR_NAME)
- os.makedirs(archive_dir, exist_ok=True)
- filename = os.path.basename(filepath)
- archive_path = os.path.join(archive_dir, filename)
-
- # If archive exists, append timestamp to filename
- if os.path.exists(archive_path):
- ts = datetime.now().strftime("%Y%m%d%H%M%S")
- archive_path = os.path.join(archive_dir, f"{ts}_{filename}")
-
- io.write_atomic(archive_path, content)
-
- # 2. Create Stub
- # Extract Frontmatter
- fm_data, _ = extract_frontmatter(content)
-
- # Reconstruct FM (simpler than migration func)
- lines = ["---"]
- for k, v in fm_data.items():
- lines.append(f"{k}: {v}")
- lines.append("compacted: true")
- lines.append("---")
- lines.append("")
- lines.append(f"# {task_data['title']}")
- lines.append("")
- lines.append(f"> **Compacted Task**")
- lines.append(f"> Original archived at: `{os.path.relpath(archive_path, REPO_ROOT)}`")
- lines.append("")
- if summary_tech:
- lines.append("## Technical Implementation")
- lines.append(summary_tech)
- lines.append("")
- if summary_decisions:
- lines.append("## Product Decisions")
- lines.append(summary_decisions)
- lines.append("")
- if summary_unresolved:
- lines.append("## Unresolved / Security Implications")
- lines.append(summary_unresolved)
- lines.append("")
- if summary:
- lines.append("## Summary")
- lines.append(summary)
- lines.append("")
-
- stub_content = "\n".join(lines)
- io.write_atomic(filepath, stub_content)
-
- msg = f"Compacted {task_id}. Archive: {os.path.relpath(archive_path, REPO_ROOT)}"
- if output_format == "json":
- print(json.dumps({"success": True, "id": task_id, "archive_path": archive_path}))
- else:
- print(msg)
-
- except Exception as e:
- msg = f"Error compacting task: {e}"
- print(json.dumps({"error": msg}) if output_format == "json" else msg)
- sys.exit(1)
-
-
-def breakdown_task(task_id, output_format="text"):
- """Provides instructions to break down a task into micro-tasks."""
- filepath = find_task_file(task_id)
- if not filepath:
- if output_format == "json":
- print(json.dumps({"error": f"Task {task_id} not found."}))
- else:
- print(f"Error: Task {task_id} not found.")
- sys.exit(1)
-
- with open(filepath, "r") as f:
- content = f.read()
-
- data = parse_task_content(content, filepath)
-
- if output_format == "json":
- print(json.dumps({
- "task_id": task_id,
- "action": "breakdown",
- "message": "Ready for micro-planning breakdown."
- }))
- else:
- print(f"=== Micro-Planning Breakdown for {task_id} ===")
- print(f"Title: {data.get('title', 'Unknown')}")
- print(f"Description:\n{data.get('content', 'No description')}")
- print("\n--- INSTRUCTIONS FOR AI AGENT ---")
- print("You are the Task Agent. Your goal is to translate the requirements above into atomic, trackable tasks.")
- print("1. Read the feature description or design document above.")
- print("2. Break down the work into bite-sized micro-tasks.")
- print(f"3. For each micro-task, use the `scripts/tasks.py create` command to create it in the system.")
- print(f"4. Ensure every created task is linked to this parent task by specifying `--part-of {task_id}`.")
- print("5. If a micro-task blocks another, specify the dependency using `--dependencies ` when creating the dependent task.")
- print("6. Only create tasks that are strictly necessary to complete this parent task.")
-
-def migrate_to_frontmatter(content, task_data):
- """Converts legacy content to Frontmatter format."""
- # Strip the header section from legacy content
-
- body = content
- if "## Task Details" in content:
- parts = content.split("## Task Details")
- if len(parts) > 1:
- body = parts[1].strip()
-
- description = body
- # Remove footer
- if "*Created:" in description:
- description = description.split("---")[0].strip()
-
- # Check for extra keys in task_data that might need preservation
- extra_fm = ""
- if task_data.get("type"): extra_fm += f"type: {task_data['type']}\n"
- if task_data.get("sprint"): extra_fm += f"sprint: {task_data['sprint']}\n"
- if task_data.get("estimate"): extra_fm += f"estimate: {task_data['estimate']}\n"
-
- deps = task_data.get("dependencies", [])
- if deps:
- if isinstance(deps, list):
- deps_str = "[" + ", ".join(deps) + "]"
- else:
- deps_str = str(deps)
- extra_fm += f"dependencies: {deps_str}\n"
-
- new_content = f"""---
-id: {task_data['id']}
-status: {task_data['status']}
-title: {task_data['title']}
-priority: {task_data['priority']}
-created: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
-category: unknown
-{extra_fm}---
-
-# {task_data['title']}
-
-{description}
-"""
- return new_content
-
-@audit.audit_log("task_update")
-def update_task_status(task_id, new_status, output_format="text"):
- if new_status not in VALID_STATUSES:
- msg = f"Error: Invalid status '{new_status}'. Valid statuses: {', '.join(VALID_STATUSES)}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- content = io.read_text(filepath)
-
- # Pre-PR automated local review
- if new_status == "review_requested":
- review_cmd = [sys.executable, os.path.join(SCRIPT_DIR, "review.py"), "check", "--task-id", task_id]
- if output_format == "json":
- review_cmd.extend(["--format", "json"])
-
- try:
- result = subprocess.run(review_cmd, capture_output=True, text=True)
- if result.returncode != 0:
- if output_format == "json":
- try:
- print(result.stdout)
- except Exception:
- print(json.dumps({"error": f"Critical issues found during local review:\n{result.stderr}"}))
- else:
- print(result.stdout)
- print(result.stderr)
- sys.exit(1)
- except Exception as e:
- msg = f"Error running local review: {e}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- # Check dependencies if moving to active status
- if new_status in ["in_progress", "review_requested", "verified", "completed"]:
- task_data = parse_task_content(content, filepath)
- deps = task_data.get("dependencies", [])
- if deps:
- blocked_by = []
- for dep_id in deps:
- # Resolve dependency file
- dep_path = find_task_file(dep_id)
- if not dep_path:
- blocked_by.append(f"{dep_id} (missing)")
- continue
-
- try:
- dep_content = io.read_text(dep_path)
- dep_data = parse_task_content(dep_content, dep_path)
-
- if dep_data["status"] not in ["completed", "verified"]:
- blocked_by.append(f"{dep_id} ({dep_data['status']})")
- except Exception:
- blocked_by.append(f"{dep_id} (error reading)")
-
- if blocked_by:
- msg = f"Error: Cannot move to '{new_status}' because task is blocked by dependencies: {', '.join(blocked_by)}"
- if output_format == "json":
- print(json.dumps({"error": msg}))
- else:
- print(msg)
- sys.exit(1)
-
- frontmatter, body = extract_frontmatter(content)
-
- if frontmatter:
- # Update Frontmatter
- lines = content.splitlines()
- new_lines = []
- in_fm = False
- updated = False
-
- # Simple finite state machine for update
- for line in lines:
- if re.match(r"^\s*---\s*$", line):
- if not in_fm:
- in_fm = True
- new_lines.append(line)
- continue
- else:
- in_fm = False
- new_lines.append(line)
- continue
-
- match = re.match(r"^(\s*)status:", line)
- if in_fm and match:
- indent = match.group(1)
- new_lines.append(f"{indent}status: {new_status}")
- updated = True
- else:
- new_lines.append(line)
-
- new_content = "\n".join(new_lines) + "\n"
-
- else:
- # Legacy Format: Migrate on Update
- task_data = parse_task_content(content, filepath)
- task_data['status'] = new_status # Set new status
- new_content = migrate_to_frontmatter(content, task_data)
- if output_format == "text":
- print(f"Migrated task {task_id} to new format.")
-
- io.write_atomic(filepath, new_content)
-
- if output_format == "json":
- print(json.dumps({"success": True, "id": task_id, "status": new_status}))
- else:
- print(f"Updated {task_id} status to {new_status}")
-
-def update_frontmatter_field(filepath, field, value):
- """Updates a specific field in the frontmatter."""
- content = io.read_text(filepath)
-
- frontmatter, body = extract_frontmatter(content)
- if not frontmatter:
- # Fallback for legacy: migrate first
- task_data = parse_task_content(content, filepath)
- task_data[field] = value
- new_content = migrate_to_frontmatter(content, task_data)
- io.write_atomic(filepath, new_content)
- return True
-
- # Update Frontmatter line-by-line to preserve comments/order
- lines = content.splitlines()
- new_lines = []
- in_fm = False
- updated = False
-
- # Handle list values (like dependencies)
- if isinstance(value, list):
- # Serialize as Flow-style list [a, b] for valid YAML and easier regex
- val_str = "[" + ", ".join(value) + "]"
- else:
- val_str = str(value)
-
- for line in lines:
- if re.match(r"^\s*---\s*$", line):
- if not in_fm:
- in_fm = True
- new_lines.append(line)
- continue
- else:
- if in_fm and not updated:
- # Field not found, add it before close
- new_lines.append(f"{field}: {val_str}")
- in_fm = False
- new_lines.append(line)
- continue
-
- match = re.match(rf"^(\s*){field}:", line)
- if in_fm and match:
- indent = match.group(1)
- new_lines.append(f"{indent}{field}: {val_str}")
- updated = True
- else:
- new_lines.append(line)
-
- new_content = "\n".join(new_lines) + "\n"
- io.write_atomic(filepath, new_content)
- return True
-
-def add_dependency(task_id, dep_id, output_format="text", link_type="dependencies"):
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- print(json.dumps({"error": msg}) if output_format == "json" else msg)
- sys.exit(1)
-
- # Verify dep exists
- if not find_task_file(dep_id):
- msg = f"Error: Target Task ID {dep_id} not found."
- print(json.dumps({"error": msg}) if output_format == "json" else msg)
- sys.exit(1)
-
- content = io.read_text(filepath)
-
- task_data = parse_task_content(content, filepath)
- links = task_data.get(link_type, [])
-
- if dep_id in links:
- msg = f"Task {task_id} already has {link_type} {dep_id}."
- print(json.dumps({"message": msg}) if output_format == "json" else msg)
- return
-
- links.append(dep_id)
- update_frontmatter_field(filepath, link_type, links)
-
- msg = f"Added {link_type}: {task_id} -> {dep_id}"
- print(json.dumps({"success": True, "message": msg}) if output_format == "json" else msg)
-
-def remove_dependency(task_id, dep_id, output_format="text", link_type="dependencies"):
- filepath = find_task_file(task_id)
- if not filepath:
- msg = f"Error: Task ID {task_id} not found."
- print(json.dumps({"error": msg}) if output_format == "json" else msg)
- sys.exit(1)
-
- content = io.read_text(filepath)
-
- task_data = parse_task_content(content, filepath)
- links = task_data.get(link_type, [])
-
- if dep_id not in links:
- msg = f"Task {task_id} does not have {link_type} {dep_id}."
- print(json.dumps({"message": msg}) if output_format == "json" else msg)
- return
-
- links.remove(dep_id)
- update_frontmatter_field(filepath, link_type, links)
-
- msg = f"Removed {link_type}: {task_id} -x-> {dep_id}"
- print(json.dumps({"success": True, "message": msg}) if output_format == "json" else msg)
-
-def generate_index(output_format="text"):
- """Generates docs/tasks/INDEX.yaml reflecting task dependencies."""
- index_path = os.path.join(DOCS_DIR, "INDEX.yaml")
-
- all_tasks = {} # id -> filepath
- task_deps = {} # id -> [deps]
-
- for root, _, files in os.walk(DOCS_DIR):
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
- task = parse_task_content(content, path)
- if task["id"] != "unknown":
- all_tasks[task["id"]] = path
- task_deps[task["id"]] = task.get("dependencies", [])
- except:
- pass
-
- # Build YAML content
- yaml_lines = ["# Task Dependency Index", "# Generated by scripts/tasks.py index", ""]
-
- for tid, path in sorted(all_tasks.items()):
- rel_path = os.path.relpath(path, REPO_ROOT)
- yaml_lines.append(f"{rel_path}:")
-
- deps = task_deps.get(tid, [])
- if deps:
- yaml_lines.append(" depends_on:")
- for dep_id in sorted(deps):
- dep_path = all_tasks.get(dep_id)
- if dep_path:
- dep_rel_path = os.path.relpath(dep_path, REPO_ROOT)
- yaml_lines.append(f" - {dep_rel_path}")
- else:
- # Dependency not found (maybe archived or missing)
- yaml_lines.append(f" - {dep_id} # Missing")
-
- yaml_lines.append("")
-
- io.write_atomic(index_path, "\n".join(yaml_lines))
-
- msg = f"Generated index at {index_path}"
- print(json.dumps({"success": True, "path": index_path}) if output_format == "json" else msg)
-
-
-
-
-def list_tasks(status=None, category=None, sprint=None, include_archived=False, output_format="text"):
- tasks = []
-
- for root, dirs, files in os.walk(DOCS_DIR):
- rel_path = os.path.relpath(root, DOCS_DIR)
-
- # Exclude archive unless requested
- if not include_archived:
- if rel_path == ARCHIVE_DIR_NAME or rel_path.startswith(ARCHIVE_DIR_NAME + os.sep):
- continue
-
- # Filter by category if provided
- if category:
- if rel_path != category and not rel_path.startswith(category + os.sep):
- continue
-
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
-
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
- except Exception as e:
- if output_format == "text":
- print(f"Error reading {path}: {e}")
- continue
-
- # Parse content
- task = parse_task_content(content, path)
-
- # Skip files that don't look like tasks (no ID)
- if task["id"] == "unknown":
- continue
-
- if status and status.lower() != task["status"].lower():
- continue
-
- if sprint and sprint != task.get("sprint"):
- continue
-
- tasks.append(task)
-
- if output_format == "json":
- summary = [{k: v for k, v in t.items() if k != 'content'} for t in tasks]
- print(json.dumps(summary))
- else:
- # Adjust width for ID to handle longer IDs
- print(f"{'ID':<25} {'Status':<20} {'Type':<8} {'Title'}")
- print("-" * 85)
- for t in tasks:
- t_type = t.get("type", "task")[:8]
- print(f"{t['id']:<25} {t['status']:<20} {t_type:<8} {t['title']}")
-
-def get_context(output_format="text"):
- """Lists tasks that are currently in progress."""
- if output_format == "text":
- print("Current Context (in_progress):")
- list_tasks(status="in_progress", output_format=output_format)
-
-def migrate_all():
- """Migrates all legacy tasks to Frontmatter format."""
- print("Migrating tasks to Frontmatter format...")
- count = 0
- for root, dirs, files in os.walk(DOCS_DIR):
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
-
- path = os.path.join(root, file)
- content = io.read_text(path)
-
- if content.startswith("---\n") or content.startswith("--- "):
- continue # Already migrated (simple check)
-
- task_data = parse_task_content(content, path)
- if task_data['id'] == "unknown":
- continue
-
- new_content = migrate_to_frontmatter(content, task_data)
- io.write_atomic(path, new_content)
-
- print(f"Migrated {task_data['id']}")
- count += 1
-
- print(f"Migration complete. {count} tasks updated.")
-
-def validate_all(output_format="text"):
- """Validates all task files."""
- errors = []
- all_tasks = {} # id -> {path, deps}
-
- # Pass 1: Parse and Basic Validation
- for root, dirs, files in os.walk(DOCS_DIR):
- # Skip archive
- if ARCHIVE_DIR_NAME in root:
- continue
-
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
-
- # Check 1: Frontmatter exists
- frontmatter, body = extract_frontmatter(content)
- if not frontmatter:
- errors.append(f"{file}: Missing valid frontmatter")
- continue
-
- # Check 2: Required fields
- required_fields = ["id", "status", "title", "created"]
- missing = [field for field in required_fields if field not in frontmatter]
- if missing:
- errors.append(f"{file}: Missing required fields: {', '.join(missing)}")
- continue
-
- task_id = frontmatter["id"]
-
- # Check 3: Valid Status
- if "status" in frontmatter and frontmatter["status"] not in VALID_STATUSES:
- errors.append(f"{file}: Invalid status '{frontmatter['status']}'")
-
- # Check 4: Valid Type
- if "type" in frontmatter and frontmatter["type"] not in VALID_TYPES:
- errors.append(f"{file}: Invalid type '{frontmatter['type']}'")
-
- # Parse dependencies, part_of, related_to
- def parse_link_list(val):
- if not val: return []
- val = str(val).strip(" []")
- if not val: return []
- return [d.strip() for d in val.split(",") if d.strip()]
-
- deps = parse_link_list(frontmatter.get("dependencies"))
- part_of = parse_link_list(frontmatter.get("part_of"))
- related_to = parse_link_list(frontmatter.get("related_to"))
-
- # Check for Duplicate IDs
- if task_id in all_tasks:
- errors.append(f"{file}: Duplicate Task ID '{task_id}' (also in {all_tasks[task_id]['path']})")
-
- all_tasks[task_id] = {"path": path, "deps": deps, "part_of": part_of, "related_to": related_to}
-
- except Exception as e:
- errors.append(f"{file}: Error reading/parsing: {str(e)}")
-
- # Pass 2: Link Validation & Cycle Detection
- visited = set()
- recursion_stack = set()
-
- def detect_cycle(curr_id, path):
- visited.add(curr_id)
- recursion_stack.add(curr_id)
-
- if curr_id in all_tasks:
- for dep_id in all_tasks[curr_id]["deps"]:
- # Dependency Existence Check
- if dep_id not in all_tasks:
- # This will be caught in the loop below, but we need to handle it here to avoid error
- continue
-
- if dep_id not in visited:
- if detect_cycle(dep_id, path + [dep_id]):
- return True
- elif dep_id in recursion_stack:
- path.append(dep_id)
- return True
-
- recursion_stack.remove(curr_id)
- return False
-
- for task_id, info in all_tasks.items():
- # Check links exist
- for link_type in ["deps", "part_of", "related_to"]:
- for linked_id in info[link_type]:
- if linked_id not in all_tasks:
- field_name = "dependency" if link_type == "deps" else link_type
- errors.append(f"{os.path.basename(info['path'])}: Invalid {field_name} '{linked_id}' (task not found)")
-
- # Check cycles (only on dependencies)
- if task_id not in visited:
- cycle_path = [task_id]
- if detect_cycle(task_id, cycle_path):
- errors.append(f"Circular dependency detected: {' -> '.join(cycle_path)}")
-
- if output_format == "json":
- print(json.dumps({"valid": len(errors) == 0, "errors": errors}))
- else:
- if not errors:
- print("All tasks validated successfully.")
- else:
- print(f"Found {len(errors)} errors:")
- for err in errors:
- print(f" - {err}")
- sys.exit(1)
-
-def visualize_tasks(output_format="text"):
- """Generates a Mermaid diagram of task dependencies."""
- tasks = []
- # Collect all tasks
- for root, dirs, files in os.walk(DOCS_DIR):
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
- task = parse_task_content(content, path)
- if task["id"] != "unknown":
- tasks.append(task)
- except:
- pass
-
- if output_format == "json":
- nodes = [{"id": t["id"], "title": t["title"], "status": t["status"]} for t in tasks]
- edges = []
- for t in tasks:
- for dep in t.get("dependencies", []):
- edges.append({"from": dep, "to": t["id"]})
- print(json.dumps({"nodes": nodes, "edges": edges}))
- return
-
- # Mermaid Output
- print("graph TD")
- print(" %% Styles")
- print(" classDef completed fill:#d4f1f4,stroke:#005f6b,color:#000;")
- print(" classDef inprogress fill:#fff8c5,stroke:#b29400,color:#000;")
- print(" classDef blocked fill:#ffdce0,stroke:#bc0004,color:#000;")
- print(" classDef default fill:#fff,stroke:#333,color:#000;")
- print("")
-
- # Nodes
- for t in tasks:
- # Sanitize title for label
- safe_title = t["title"].replace('"', '').replace('[', '').replace(']', '')
-
- # Style based on status
- style_class = ""
- if t['status'] in ['completed', 'verified']:
- style_class = ":::completed"
- elif t['status'] == 'in_progress':
- style_class = ":::inprogress"
- elif t['status'] in ['blocked', 'wip_blocked']:
- style_class = ":::blocked"
-
- print(f' {t["id"]}["{t["id"]}: {safe_title}"]{style_class}')
-
- # Edges
- for t in tasks:
- # Dependencies (Solid)
- deps = t.get("depends_on") or t.get("dependencies") or []
- for dep in deps:
- print(f" {dep} --> {t['id']}")
-
- # Parent (Dotted)
- parents = t.get("part_of", [])
- if isinstance(parents, str): parents = [parents]
- for p in parents:
- print(f" {t['id']} -.-> {p}")
-
-
-def get_next_task(output_format="text"):
- """Identifies the next best task to work on."""
- # 1. Collect all tasks
- all_tasks = {}
- for root, _, files in os.walk(DOCS_DIR):
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
- task = parse_task_content(content, path)
- if task["id"] != "unknown":
- all_tasks[task["id"]] = task
- except:
- pass
-
- candidates = []
-
- # Priority mapping
- prio_score = {"high": 3, "medium": 2, "low": 1, "unknown": 1}
-
- for tid, task in all_tasks.items():
- # Filter completed
- if task["status"] in ["completed", "verified", "cancelled", "deferred", "blocked"]:
- continue
-
- # Check dependencies
- deps = task.get("dependencies", [])
- blocked = False
- for dep_id in deps:
- if dep_id not in all_tasks:
- blocked = True # Missing dependency
- break
-
- dep_status = all_tasks[dep_id]["status"]
- if dep_status not in ["completed", "verified"]:
- blocked = True
- break
-
- if blocked:
- continue
-
- # Calculate Score
- score = 0
-
- # Status Bonus
- if task["status"] == "in_progress":
- score += 1000
- elif task["status"] == "pending":
- score += 100
- elif task["status"] == "wip_blocked":
- # Unblocked now
- score += 500
-
- # Priority
- score += prio_score.get(task.get("priority", "medium"), 1) * 10
-
- # Sprint Bonus
- if task.get("sprint"):
- score += 50
-
- # Type Bonus (Stories/Bugs > Tasks > Epics)
- t_type = task.get("type", "task")
- if t_type in ["story", "bug"]:
- score += 20
- elif t_type == "task":
- score += 10
-
- candidates.append((score, task))
-
- candidates.sort(key=lambda x: x[0], reverse=True)
-
- if not candidates:
- msg = "No suitable tasks found (all completed or blocked)."
- if output_format == "json":
- print(json.dumps({"message": msg}))
- else:
- print(msg)
- return
-
- best = candidates[0][1]
-
- if output_format == "json":
- print(json.dumps(best))
- else:
- print(f"Recommended Next Task (Score: {candidates[0][0]}):")
- print(f"ID: {best['id']}")
- print(f"Title: {best['title']}")
- print(f"Status: {best['status']}")
- print(f"Priority: {best['priority']}")
- print(f"Type: {best.get('type', 'task')}")
- if best.get("sprint"):
- print(f"Sprint: {best.get('sprint')}")
- print(f"\nRun: scripts/tasks show {best['id']}")
-
-def list_ready_tasks(output_format="text"):
- """Lists only tasks that are strictly unblocked and ready."""
- # 1. Collect all tasks
- all_tasks = {}
- for root, _, files in os.walk(DOCS_DIR):
- for file in files:
- if not file.endswith(".md") or file in ["GUIDE.md", "README.md", "INDEX.yaml"]:
- continue
- path = os.path.join(root, file)
- try:
- content = io.read_text(path)
- task = parse_task_content(content, path)
- if task["id"] != "unknown":
- all_tasks[task["id"]] = task
- except:
- pass
-
- ready_tasks = []
-
- for tid, task in all_tasks.items():
- # Filter non-actionable statuses
- if task["status"] in ["completed", "verified", "cancelled", "deferred", "blocked"]:
- continue
-
- # Check dependencies
- deps = task.get("dependencies", [])
- blocked = False
- missing_dep = False
-
- for dep_id in deps:
- if dep_id not in all_tasks:
- missing_dep = True
- blocked = True
- break
-
- dep_status = all_tasks[dep_id]["status"]
- if dep_status not in ["completed", "verified"]:
- blocked = True
- break
-
- if blocked:
- continue
-
- ready_tasks.append(task)
-
- # Sort by Priority
- prio_score = {"high": 3, "medium": 2, "low": 1, "unknown": 1}
- ready_tasks.sort(key=lambda x: prio_score.get(x.get("priority", "medium"), 1), reverse=True)
-
- if output_format == "json":
- print(json.dumps([{k:v for k,v in t.items() if k!='content'} for t in ready_tasks]))
- else:
- if not ready_tasks:
- print("No ready tasks found.")
- return
-
- print(f"{'ID':<25} {'Pri':<6} {'Title'}")
- print("-" * 60)
- for t in ready_tasks:
- p = t.get("priority", "med")[:3]
- print(f"{t['id']:<25} {p:<6} {t['title']}")
-
-
-def install_hooks():
- """Installs the git pre-commit hook."""
- hook_path = os.path.join(REPO_ROOT, ".git", "hooks", "pre-commit")
- if not os.path.exists(os.path.join(REPO_ROOT, ".git")):
- print("Error: Not a git repository.")
- sys.exit(1)
-
- script_path = os.path.relpath(os.path.abspath(__file__), REPO_ROOT)
-
- hook_content = f"""#!/bin/sh
-# Auto-generated by scripts/tasks.py
-echo "Running task validation..."
-python3 {script_path} validate --format text
-
-echo "Updating code index..."
-python3 scripts/code_index.py index
-"""
-
- try:
- io.write_atomic(hook_path, hook_content)
- os.chmod(hook_path, 0o755)
- print(f"Installed pre-commit hook at {hook_path}")
- except Exception as e:
- print(f"Error installing hook: {e}")
- sys.exit(1)
-
-def main():
- parser = argparse.ArgumentParser(description="Manage development tasks")
-
- # Common argument for format
- parent_parser = argparse.ArgumentParser(add_help=False)
- parent_parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
-
- subparsers = parser.add_subparsers(dest="command", help="Command to run")
-
- # Init
- subparsers.add_parser("init", help="Initialize documentation structure")
-
- # Create
- create_parser = subparsers.add_parser("create", parents=[parent_parser], help="Create a new task")
- create_parser.add_argument("category", choices=CATEGORIES, help="Task category")
- create_parser.add_argument("title", help="Task title")
- create_parser.add_argument("--desc", default="To be determined", help="Task description")
- create_parser.add_argument("--priority", default="medium", help="Task priority")
- create_parser.add_argument("--status", choices=VALID_STATUSES, default="pending", help="Task status")
- create_parser.add_argument("--dependencies", "--depends-on", dest="dependencies", help="Comma-separated list of dependencies")
- create_parser.add_argument("--part-of", help="Parent task ID(s)")
- create_parser.add_argument("--related-to", help="Related task ID(s)")
- create_parser.add_argument("--type", choices=VALID_TYPES, default="task", help="Task type")
- create_parser.add_argument("--sprint", default="", help="Sprint name/ID")
- create_parser.add_argument("--estimate", default="", help="Estimate (points/size)")
-
-
- # List
- list_parser = subparsers.add_parser("list", parents=[parent_parser], help="List tasks")
- list_parser.add_argument("--status", help="Filter by status")
- list_parser.add_argument("--category", choices=CATEGORIES, help="Filter by category")
- list_parser.add_argument("--sprint", help="Filter by sprint")
- list_parser.add_argument("--archived", action="store_true", help="Include archived tasks")
-
- # Show
- show_parser = subparsers.add_parser("show", parents=[parent_parser], help="Show task details")
- show_parser.add_argument("task_id", help="Task ID (e.g., FOUNDATION-001)")
-
- # Update
- update_parser = subparsers.add_parser("update", parents=[parent_parser], help="Update task status")
- update_parser.add_argument("task_id", help="Task ID (e.g., FOUNDATION-001)")
- update_parser.add_argument("status", help=f"New status: {', '.join(VALID_STATUSES)}")
-
- # Delete
- delete_parser = subparsers.add_parser("delete", parents=[parent_parser], help="Delete a task")
- delete_parser.add_argument("task_id", help="Task ID (e.g., FOUNDATION-001)")
-
- # Archive
- archive_parser = subparsers.add_parser("archive", parents=[parent_parser], help="Archive a task")
- archive_parser.add_argument("task_id", help="Task ID")
-
- # Compact
- compact_parser = subparsers.add_parser("compact", parents=[parent_parser], help="Compact a completed task")
- compact_parser.add_argument("task_id", help="Task ID")
- compact_parser.add_argument("--summary", help="General summary text (legacy/fallback)")
- compact_parser.add_argument("--summary-tech", help="Technical Implementation summary")
- compact_parser.add_argument("--summary-decisions", help="Product Decisions summary")
- compact_parser.add_argument("--summary-unresolved", help="Unresolved / Security Implications summary")
-
- # Breakdown
- breakdown_parser = subparsers.add_parser("breakdown", parents=[parent_parser], help="Break down a feature into bite-sized tasks")
- breakdown_parser.add_argument("task_id", help="ID of the task to break down")
-
-
- # Context
- subparsers.add_parser("context", parents=[parent_parser], help="Show current context (in_progress tasks)")
-
- # Ready
- subparsers.add_parser("ready", parents=[parent_parser], help="Show ready (unblocked) tasks")
-
-
- # Next
- subparsers.add_parser("next", parents=[parent_parser], help="Suggest the next task to work on")
-
- # Migrate
- subparsers.add_parser("migrate", parents=[parent_parser], help="Migrate legacy tasks to new format")
-
- # Complete
- complete_parser = subparsers.add_parser("complete", parents=[parent_parser], help="Mark a task as completed")
- complete_parser.add_argument("task_id", help="Task ID (e.g., FOUNDATION-001)")
-
- # Validate
- subparsers.add_parser("validate", parents=[parent_parser], help="Validate task files")
-
- # Visualize
- subparsers.add_parser("visualize", parents=[parent_parser], help="Visualize task dependencies (Mermaid)")
-
- # Graph (Alias to Visualize)
- subparsers.add_parser("graph", parents=[parent_parser], help="Graph task dependencies (Alias for visualize)")
-
- # Install Hooks
- subparsers.add_parser("install-hooks", parents=[parent_parser], help="Install git hooks")
-
- # Index
- subparsers.add_parser("index", parents=[parent_parser], help="Generate task dependency index")
-
- # Link (Add Dependency)
- link_parser = subparsers.add_parser("link", parents=[parent_parser], help="Add a dependency")
- link_parser.add_argument("task_id", help="Task ID")
- link_parser.add_argument("dep_id", help="Dependency Task ID")
- link_parser.add_argument("--type", choices=["dependencies", "part_of", "related_to"], default="dependencies", help="Type of link to add")
-
- # Unlink (Remove Dependency)
- unlink_parser = subparsers.add_parser("unlink", parents=[parent_parser], help="Remove a dependency")
- unlink_parser.add_argument("task_id", help="Task ID")
- unlink_parser.add_argument("dep_id", help="Dependency Task ID")
- unlink_parser.add_argument("--type", choices=["dependencies", "part_of", "related_to"], default="dependencies", help="Type of link to remove")
-
-
- args = parser.parse_args()
-
- # Default format to text if not present (e.g. init doesn't have it)
- fmt = getattr(args, "format", "text")
-
- if args.command == "create":
- deps = [d.strip() for d in args.dependencies.split(",")] if args.dependencies else []
- part_of = [d.strip() for d in args.part_of.split(",")] if args.part_of else []
- related = [d.strip() for d in args.related_to.split(",")] if args.related_to else []
-
- create_task(args.category, args.title, args.desc, priority=args.priority, status=args.status,
- dependencies=deps, part_of=part_of, related_to=related,
- task_type=args.type, sprint=args.sprint, estimate=args.estimate, output_format=fmt)
-
- elif args.command == "list":
- list_tasks(args.status, args.category, sprint=args.sprint, include_archived=args.archived, output_format=fmt)
- elif args.command == "init":
- init_docs()
- elif args.command == "show":
- show_task(args.task_id, output_format=fmt)
- elif args.command == "delete":
- delete_task(args.task_id, output_format=fmt)
- elif args.command == "archive":
- archive_task(args.task_id, output_format=fmt)
- elif args.command == "compact":
- if not any([args.summary, args.summary_tech, args.summary_decisions, args.summary_unresolved]):
- print("Error: At least one summary type required for compaction")
- sys.exit(1)
- compact_task(args.task_id, summary=args.summary, summary_tech=args.summary_tech, summary_decisions=args.summary_decisions, summary_unresolved=args.summary_unresolved, output_format=fmt)
- elif args.command == "breakdown":
- breakdown_task(args.task_id, output_format=fmt)
- elif args.command == "update":
- update_task_status(args.task_id, args.status, output_format=fmt)
- elif args.command == "context":
- get_context(output_format=fmt)
- elif args.command == "ready":
- list_ready_tasks(output_format=fmt)
-
- elif args.command == "next":
- get_next_task(output_format=fmt)
- elif args.command == "migrate":
- migrate_all()
- elif args.command == "complete":
- update_task_status(args.task_id, "completed", output_format=fmt)
- elif args.command == "validate":
- validate_all(output_format=fmt)
- elif args.command == "visualize" or args.command == "graph":
- visualize_tasks(output_format=fmt)
- elif args.command == "install-hooks":
- install_hooks()
- elif args.command == "index":
- generate_index(output_format=fmt)
- elif args.command == "link":
- add_dependency(args.task_id, args.dep_id, output_format=fmt, link_type=args.type)
- elif args.command == "unlink":
- remove_dependency(args.task_id, args.dep_id, output_format=fmt, link_type=args.type)
- else:
- parser.print_help()
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/tools.sh b/scripts/tools.sh
deleted file mode 100644
index c373335..0000000
--- a/scripts/tools.sh
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/bin/bash
-# Auto-generated by scripts/generate_tools.py
-
-# Source this file to enable tool aliases in your shell
-
-function task_create() {
- # Risk Level: L1
- python3 scripts/tasks.py create "$@"
-}
-
-function task_breakdown() {
- # Risk Level: L1
- python3 scripts/tasks.py breakdown "$@"
-}
-
-function task_list() {
- # Risk Level: L0
- python3 scripts/tasks.py list "$@"
-}
-
-function task_update() {
- # Risk Level: L1
- python3 scripts/tasks.py update "$@"
-}
-
-function task_show() {
- # Risk Level: L0
- python3 scripts/tasks.py show "$@"
-}
-
-function task_validate() {
- # Risk Level: L0
- python3 scripts/tasks.py validate "$@"
-}
-
-function code_index_stats() {
- # Risk Level: L0
- python3 scripts/code_index.py stats "$@"
-}
-
-function task_context() {
- # Risk Level: L0
- python3 scripts/tasks.py context "$@"
-}
-
-function task_archive() {
- # Risk Level: L1
- python3 scripts/tasks.py archive "$@"
-}
-
-function memory_create() {
- # Risk Level: L1
- python3 scripts/memory.py create "$@"
-}
-
-function memory_list() {
- # Risk Level: L0
- python3 scripts/memory.py list "$@"
-}
-
-function memory_read() {
- # Risk Level: L0
- python3 scripts/memory.py read "$@"
-}
-
-function index_impact() {
- # Risk Level: L0
- python3 scripts/index.py impact "$@"
-}
-
-function index_add() {
- # Risk Level: L2
- python3 scripts/index.py add "$@"
-}
-
-function index_check() {
- # Risk Level: L0
- python3 scripts/index.py check "$@"
-}
-
-function agent_send() {
- # Risk Level: L1
- python3 scripts/comm.py send "$@"
-}
-
-function quality_verify() {
- # Risk Level: L0
- python3 scripts/quality.py verify "$@"
-}
-
-function quality_test() {
- # Risk Level: L0
- python3 scripts/quality.py test "$@"
-}
-
-function quality_lint() {
- # Risk Level: L0
- python3 scripts/quality.py lint "$@"
-}
-
-function quality_map() {
- # Risk Level: L1
- python3 scripts/quality.py map "$@"
-}
-
-function agent_read() {
- # Risk Level: L1
- python3 scripts/comm.py read "$@"
-}
-
-function agent_list() {
- # Risk Level: L0
- python3 scripts/comm.py list "$@"
-}
-
-function code_index_init() {
- # Risk Level: L2
- python3 scripts/code_index.py init "$@"
-}
-
-function code_index_refresh() {
- # Risk Level: L1
- python3 scripts/code_index.py index "$@"
-}
-
-function code_index_list() {
- # Risk Level: L0
- python3 scripts/code_index.py list "$@"
-}
-
-function code_index_search() {
- # Risk Level: L0
- python3 scripts/code_index.py search "$@"
-}
-
-function code_index_lookup() {
- # Risk Level: L0
- python3 scripts/code_index.py lookup "$@"
-}
-
-function code_index_references() {
- # Risk Level: L0
- python3 scripts/code_index.py references "$@"
-}
-
-function tdd_state() {
- # Risk Level: L0
- python3 scripts/tdd.py state "$@"
-}
-
-function tdd_run() {
- # Risk Level: L1
- python3 scripts/tdd.py run "$@"
-}
-
-function tdd_reset() {
- # Risk Level: L0
- python3 scripts/tdd.py reset "$@"
-}
-
-function local_review() {
- # Risk Level: L0
- python3 scripts/review.py check "$@"
-}
diff --git a/scripts/upgrade.py b/scripts/upgrade.py
index 12e9f74..bec90be 100755
--- a/scripts/upgrade.py
+++ b/scripts/upgrade.py
@@ -6,7 +6,6 @@
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(SCRIPT_DIR)
-TASKS_SCRIPT = os.path.join(SCRIPT_DIR, "tasks.py")
def upgrade():
print("Starting repository upgrade...")
diff --git a/templates/GUIDE.md b/templates/GUIDE.md
index 867689f..623aa41 100644
--- a/templates/GUIDE.md
+++ b/templates/GUIDE.md
@@ -101,9 +101,7 @@ The system has been enhanced with several integrated workflow commands to guide
- **Brainstorming:** `python3 scripts/design.py brainstorm --title "Feature" ...`
- **Workspace Setup:** `python3 scripts/workspace.py setup [TASK_ID]`
-- **Micro Planning:** `scripts/tasks.py breakdown [TASK_ID]`
- **TDD Enforcement:** `python3 scripts/tdd.py state`, `python3 scripts/tdd.py run`, `python3 scripts/tdd.py reset`
-- **Orchestration:** `python3 scripts/orchestrator.py run`, `python3 scripts/orchestrator.py assign`, `python3 scripts/orchestrator.py monitor`
- **Local Review:** Pre-PR automated review via `scripts/review.py`
## Agile Methodology
diff --git a/templates/maintenance_mode.md b/templates/maintenance_mode.md
index 7ceed0c..4761eda 100644
--- a/templates/maintenance_mode.md
+++ b/templates/maintenance_mode.md
@@ -6,25 +6,18 @@ You are an expert Software Engineer working on this project. Your primary respon
**"If it's not documented in `docs/tasks/`, it didn't happen."**
## Workflow
-1. **Pick a Task**: Run `python3 scripts/tasks.py context` to see active tasks, or `list` to see pending ones.
2. **Plan & Document**:
- * **Memory Check**: Run `python3 scripts/memory.py list` (or use the Memory Skill) to recall relevant long-term information.
* **Security Check**: Ask the user about specific security considerations for this task.
- * If starting a new task, use `scripts/tasks.py create` (or `python3 scripts/tasks.py create`) to generate a new task file.
- * Update the task status: `python3 scripts/tasks.py update [TASK_ID] in_progress`.
3. **Implement**: Write code, run tests.
4. **Update Documentation Loop**:
* As you complete sub-tasks, check them off in the task document.
* If you hit a blocker, update status to `wip_blocked` and describe the issue in the file.
* Record key architectural decisions in the task document.
- * **Memory Update**: If you learn something valuable for the long term, use `scripts/memory.py create` to record it.
5. **Review & Verify**:
* **Quality Check**: Run `python3 scripts/quality.py verify` to ensure tests and validation pass. **Do not request review if this fails.**
- * Once implementation is complete, update status to `review_requested`: `python3 scripts/tasks.py update [TASK_ID] review_requested`.
* Ask a human or another agent to review the code.
* Once approved and tested, update status to `verified`.
6. **Finalize**:
- * Update status to `completed`: `python3 scripts/tasks.py update [TASK_ID] completed`.
* Record actual effort in the file.
* Ensure all acceptance criteria are met.
@@ -35,7 +28,6 @@ You are an expert Software Engineer working on this project. Your primary respon
* **Context**: `./scripts/tasks context`
* **Update**: `./scripts/tasks update [ID] [status]`
* **Migrate**: `./scripts/tasks migrate` (Migrate legacy tasks to new format)
-* **Memory**: `./scripts/memory.py [create|list|read]`
* **JSON Output**: Add `--format json` to any command for machine parsing.
## Documentation Reference
@@ -53,7 +45,6 @@ You are an expert Software Engineer working on this project. Your primary respon
When performing a PR review, follow this "Human-in-the-loop" process to ensure depth and efficiency.
### 1. Preparation
-1. **Create Task**: `python3 scripts/tasks.py create review "Review PR #: "`
2. **Fetch Details**: Use `gh` to get the PR context.
* `gh pr view `
* `gh pr diff `
diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py
deleted file mode 100644
index e77f790..0000000
--- a/tests/test_bootstrap.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import unittest
-import os
-import sys
-import tempfile
-
-# Add scripts directory to path
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../scripts")))
-import tasks
-
-class TestBootstrap(unittest.TestCase):
- def setUp(self):
- self.test_dir = tempfile.TemporaryDirectory()
- self.repo_root = self.test_dir.name
- self.docs_dir = os.path.join(self.repo_root, "docs", "tasks")
-
- # Monkey patch global variables
- self.original_repo_root = tasks.REPO_ROOT
- self.original_docs_dir = tasks.DOCS_DIR
-
- tasks.REPO_ROOT = self.repo_root
- tasks.DOCS_DIR = self.docs_dir
-
- def tearDown(self):
- tasks.REPO_ROOT = self.original_repo_root
- tasks.DOCS_DIR = self.original_docs_dir
- self.test_dir.cleanup()
-
- def test_init_creates_new_dirs(self):
- # Suppress output
- from io import StringIO
- held, sys.stdout = sys.stdout, StringIO()
-
- try:
- tasks.init_docs()
- finally:
- sys.stdout = held
-
- # Check memories
- self.assertTrue(os.path.exists(os.path.join(self.repo_root, "docs", "memories")))
- self.assertTrue(os.path.exists(os.path.join(self.repo_root, "docs", "memories", ".keep")))
-
- # Check security
- security_readme = os.path.join(self.repo_root, "docs", "security", "README.md")
- self.assertTrue(os.path.exists(security_readme))
- with open(security_readme, "r") as f:
- content = f.read()
- self.assertIn("Risk Assessment", content)
-
- # Check security tasks
- self.assertTrue(os.path.exists(os.path.join(self.docs_dir, "security")))
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/test_memory.py b/tests/test_memory.py
deleted file mode 100644
index 629fe28..0000000
--- a/tests/test_memory.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import unittest
-import os
-import sys
-import tempfile
-import json
-from io import StringIO
-from unittest.mock import patch
-import datetime
-
-# Add scripts directory to path
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../scripts")))
-import memory
-
-class TestMemory(unittest.TestCase):
- def setUp(self):
- self.test_dir = tempfile.TemporaryDirectory()
- self.repo_root = self.test_dir.name
- self.docs_dir = os.path.join(self.repo_root, "docs", "memories")
-
- # Monkey patch global variables
- self.original_repo_root = memory.REPO_ROOT
- self.original_memory_dir = memory.MEMORY_DIR
-
- memory.REPO_ROOT = self.repo_root
- memory.MEMORY_DIR = self.docs_dir
-
- # Capture stdout
- self.held, sys.stdout = sys.stdout, StringIO()
-
- def tearDown(self):
- memory.REPO_ROOT = self.original_repo_root
- memory.MEMORY_DIR = self.original_memory_dir
- self.test_dir.cleanup()
- sys.stdout = self.held
-
- def test_create_and_list(self):
- memory.create_memory("Test Title", "Content", ["tag1"], output_format="json")
- output = sys.stdout.getvalue()
- self.assertIn('"success": true', output)
-
- sys.stdout = StringIO()
- memory.list_memories(output_format="json")
- output = sys.stdout.getvalue()
- data = json.loads(output)
- self.assertEqual(len(data), 1)
- self.assertEqual(data[0]['title'], "Test Title")
- self.assertEqual(data[0]['tags'], ["tag1"])
-
- def test_read(self):
- memory.create_memory("Read Me", "Content", output_format="json")
- # Get filename
- sys.stdout = StringIO()
- memory.list_memories(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- filename = data[0]['filename']
-
- sys.stdout = StringIO()
- memory.read_memory(filename, output_format="json")
- output = json.loads(sys.stdout.getvalue())
- self.assertIn("Content", output['content'])
-
- def test_index_memories(self):
- # Create a mock task with wikilinks
- task_dir = os.path.join(self.repo_root, "docs", "tasks", "foundation")
- os.makedirs(task_dir, exist_ok=True)
- task_file = os.path.join(task_dir, "FOUNDATION-001.md")
- with open(task_file, "w") as f:
- f.write("---\nid: FOUNDATION-001\n---\nHere is a wikilink to [[EntityA]] and another to [[EntityB]].")
-
- # Run index
- sys.stdout = StringIO()
- memory.index_memories(output_format="json")
- output = json.loads(sys.stdout.getvalue())
- self.assertTrue(output.get("success"))
-
- # Check entities.json
- entities_path = os.path.join(self.docs_dir, "entities.json")
- self.assertTrue(os.path.exists(entities_path))
- with open(entities_path, "r") as f:
- entities = json.load(f)
-
- self.assertIn("EntityA", entities)
- self.assertIn("FOUNDATION-001", entities["EntityA"])
- self.assertIn("EntityB", entities)
- self.assertIn("FOUNDATION-001", entities["EntityB"])
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py
deleted file mode 100644
index af31e0a..0000000
--- a/tests/test_orchestrator.py
+++ /dev/null
@@ -1,72 +0,0 @@
-import unittest
-from unittest.mock import patch, MagicMock
-import json
-import os
-import sys
-
-# Setup paths
-SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
-REPO_ROOT = os.path.dirname(SCRIPT_DIR)
-sys.path.append(REPO_ROOT)
-
-from scripts import orchestrator
-from scripts import tasks
-from scripts import comm
-
-class TestOrchestrator(unittest.TestCase):
- def setUp(self):
- with patch('scripts.comm.Comm.register'):
- self.orch = orchestrator.Orchestrator()
-
- @patch('scripts.tasks.list_tasks')
- def test_get_pending_tasks(self, mock_list_tasks):
- mock_tasks = [{"id": "TASK-1", "status": "pending"}]
- mock_list_tasks.return_value = mock_tasks
-
- pending = self.orch.get_pending_tasks()
-
- mock_list_tasks.assert_called_once_with(status="pending", output_format="json")
- self.assertEqual(pending, mock_tasks)
-
- @patch('scripts.tasks.list_tasks')
- def test_get_pending_tasks_string_response(self, mock_list_tasks):
- mock_tasks = [{"id": "TASK-1", "status": "pending"}]
- mock_list_tasks.return_value = json.dumps(mock_tasks)
-
- pending = self.orch.get_pending_tasks()
-
- mock_list_tasks.assert_called_once_with(status="pending", output_format="json")
- self.assertEqual(pending, mock_tasks)
-
- @patch.object(orchestrator.Orchestrator, 'get_pending_tasks')
- @patch('scripts.comm.Comm.list_agents')
- @patch('scripts.comm.Comm.send')
- @patch('scripts.tasks.update_task_status')
- def test_assign_tasks(self, mock_update, mock_send, mock_list_agents, mock_get_pending):
- mock_get_pending.return_value = [{"id": "TASK-1", "status": "pending"}]
- mock_list_agents.return_value = [{"id": "agent-1", "role": "worker"}]
-
- count = self.orch.assign_tasks()
-
- self.assertEqual(count, 1)
- mock_send.assert_called_once()
- args, kwargs = mock_send.call_args
- self.assertEqual(kwargs['recipient_id'], "agent-1")
- self.assertEqual(kwargs['type'], "task_assignment")
- mock_update.assert_called_once_with("TASK-1", "in_progress", output_format="json")
-
- @patch('scripts.comm.Comm.read')
- @patch('scripts.tasks.update_task_status')
- def test_monitor(self, mock_update, mock_read):
- mock_read.return_value = [
- {"type": "task_completion", "content": json.dumps({"task_id": "TASK-1"})},
- {"type": "other", "content": "hello"}
- ]
-
- count = self.orch.monitor()
-
- self.assertEqual(count, 1)
- mock_update.assert_called_once_with("TASK-1", "completed", output_format="json")
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_review.py b/tests/test_review.py
deleted file mode 100644
index c7ce7c3..0000000
--- a/tests/test_review.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import unittest
-import sys
-import os
-import json
-import tempfile
-import io
-
-test_file_path = os.path.abspath(__file__)
-repo_root = os.path.dirname(os.path.dirname(test_file_path))
-
-if repo_root not in sys.path:
- sys.path.append(repo_root)
-
-from scripts.review import check_task
-from scripts import tasks
-from scripts.lib import io as scripts_io
-
-class TestReview(unittest.TestCase):
- def setUp(self):
- self.test_dir = tempfile.TemporaryDirectory()
- self.repo_root = self.test_dir.name
- self.docs_dir = os.path.join(self.repo_root, "docs", "tasks")
-
- # Monkey patch global variables in tasks module
- self.original_repo_root = tasks.REPO_ROOT
- self.original_docs_dir = tasks.DOCS_DIR
- tasks.REPO_ROOT = self.repo_root
- tasks.DOCS_DIR = self.docs_dir
-
- os.makedirs(os.path.join(self.docs_dir, "features"), exist_ok=True)
-
- def tearDown(self):
- tasks.REPO_ROOT = self.original_repo_root
- tasks.DOCS_DIR = self.original_docs_dir
- self.test_dir.cleanup()
-
- def test_check_task_not_found(self):
- # Capture stdout
- captured_output = io.StringIO()
- sys.stdout = captured_output
- try:
- result = check_task("NONEXISTENT-123", output_format="text")
- self.assertFalse(result)
- self.assertIn("Error: Task ID NONEXISTENT-123 not found.", captured_output.getvalue())
- finally:
- sys.stdout = sys.__stdout__
-
- def test_check_task_success(self):
- # Create a mock task
- task_id = "FEATURES-123"
- task_content = "---\nid: FEATURES-123\nstatus: in_progress\n---\n# Feature 123\nSome normal text here."
- task_path = os.path.join(self.docs_dir, "features", f"{task_id}.md")
- scripts_io.write_atomic(task_path, task_content)
-
- captured_output = io.StringIO()
- sys.stdout = captured_output
- try:
- result = check_task(task_id, mock_failure=False, output_format="text")
- self.assertTrue(result)
- self.assertIn("Success: No critical issues found for FEATURES-123.", captured_output.getvalue())
- finally:
- sys.stdout = sys.__stdout__
-
- def test_check_task_mock_failure(self):
- # Create a mock task
- task_id = "FEATURES-123"
- task_content = "---\nid: FEATURES-123\nstatus: in_progress\n---\n# Feature 123\nSome normal text here."
- task_path = os.path.join(self.docs_dir, "features", f"{task_id}.md")
- scripts_io.write_atomic(task_path, task_content)
-
- captured_output = io.StringIO()
- sys.stdout = captured_output
- try:
- result = check_task(task_id, mock_failure=True, output_format="text")
- self.assertFalse(result)
- self.assertIn("Critical Issue: The implementation of FEATURES-123 does not match the spec.", captured_output.getvalue())
- finally:
- sys.stdout = sys.__stdout__
-
- def test_check_task_critical_issue_in_content(self):
- # Create a mock task with CRITICAL_ISSUE in the text
- task_id = "FEATURES-123"
- task_content = "---\nid: FEATURES-123\nstatus: in_progress\n---\n# Feature 123\nHere is a CRITICAL_ISSUE that needs addressing."
- task_path = os.path.join(self.docs_dir, "features", f"{task_id}.md")
- scripts_io.write_atomic(task_path, task_content)
-
- captured_output = io.StringIO()
- sys.stdout = captured_output
- try:
- result = check_task(task_id, mock_failure=False, output_format="text")
- self.assertFalse(result)
- self.assertIn("Critical Issue: The implementation of FEATURES-123 does not match the spec.", captured_output.getvalue())
- finally:
- sys.stdout = sys.__stdout__
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
deleted file mode 100644
index 8f87bae..0000000
--- a/tests/test_tasks.py
+++ /dev/null
@@ -1,356 +0,0 @@
-import unittest
-import os
-import sys
-import tempfile
-import shutil
-import json
-from io import StringIO
-from unittest.mock import patch
-
-# Add scripts directory to path
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../scripts")))
-import tasks
-
-class TestTasks(unittest.TestCase):
- def setUp(self):
- self.test_dir = tempfile.TemporaryDirectory()
- self.repo_root = self.test_dir.name
- self.docs_dir = os.path.join(self.repo_root, "docs", "tasks")
-
- # Monkey patch global variables in tasks module
- self.original_repo_root = tasks.REPO_ROOT
- self.original_docs_dir = tasks.DOCS_DIR
-
- tasks.REPO_ROOT = self.repo_root
- tasks.DOCS_DIR = self.docs_dir
-
- # Capture stdout
- self.held, sys.stdout = sys.stdout, StringIO()
-
- # Init docs structure
- tasks.init_docs()
-
- def tearDown(self):
- tasks.REPO_ROOT = self.original_repo_root
- tasks.DOCS_DIR = self.original_docs_dir
- self.test_dir.cleanup()
- sys.stdout = self.held
-
- def test_init(self):
- self.assertTrue(os.path.exists(os.path.join(self.docs_dir, "foundation")))
- self.assertTrue(os.path.exists(os.path.join(self.repo_root, "docs", "architecture")))
-
- def test_create_review_task(self):
- tasks.create_task("review", "Review PR 123", "Checking implementation")
- files = os.listdir(os.path.join(self.docs_dir, "review"))
- task_files = [f for f in files if f.endswith(".md") and f != ".keep"]
- self.assertEqual(len(task_files), 1)
- self.assertTrue(task_files[0].startswith("REVIEW-"))
-
- def test_create_task_with_related(self):
- tasks.create_task("foundation", "My Linked Task", "Description", part_of=["EPIC-1"], related_to=["TASK-2"])
- output = sys.stdout.getvalue()
- self.assertIn("Created task:", output)
-
- files = os.listdir(os.path.join(self.docs_dir, "foundation"))
- task_files = [f for f in files if f.endswith(".md") and f != ".keep"]
- self.assertEqual(len(task_files), 1)
-
- with open(os.path.join(self.docs_dir, "foundation", task_files[0]), "r") as f:
- content = f.read()
- self.assertIn("title: My Linked Task", content)
- self.assertIn("part_of: [EPIC-1]", content)
- self.assertIn("related_to: [TASK-2]", content)
-
- def test_create_task(self):
- tasks.create_task("foundation", "My Task", "Description")
- output = sys.stdout.getvalue()
- self.assertIn("Created task:", output)
-
- # Find the created file
- files = os.listdir(os.path.join(self.docs_dir, "foundation"))
- task_files = [f for f in files if f.endswith(".md") and f != ".keep"]
- self.assertEqual(len(task_files), 1)
-
- with open(os.path.join(self.docs_dir, "foundation", task_files[0]), "r") as f:
- content = f.read()
- self.assertIn("title: My Task", content)
- self.assertIn("status: pending", content)
-
- def test_list_tasks(self):
- tasks.create_task("foundation", "Task 1", "Desc")
- tasks.create_task("features", "Task 2", "Desc")
-
- # Reset stdout
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- output = sys.stdout.getvalue()
- data = json.loads(output)
- self.assertEqual(len(data), 2)
-
- def test_find_task_moved_file_bug(self):
- # Create a task
- tasks.create_task("foundation", "Moved Task", "Desc")
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_id = data[0]['id']
- filepath = data[0]['filepath']
-
- # Move it to a subfolder (which optimization might miss)
- new_dir = os.path.join(self.docs_dir, "foundation", "archive")
- os.makedirs(new_dir, exist_ok=True)
- new_path = os.path.join(new_dir, os.path.basename(filepath))
- os.rename(filepath, new_path)
-
- # Try to show it
- # This is expected to FAIL with current logic if optimization is strict and not recursive
- found_path = tasks.find_task_file(task_id)
-
- # With fix, it should find it
- self.assertIsNotNone(found_path, "Should find moved task in subdir with fixed logic")
- self.assertEqual(found_path, new_path)
-
- def test_list_tasks_subdir_bug(self):
- # Create task in foundation
- tasks.create_task("foundation", "Normal Task", "Desc")
-
- # Create task in foundation/subdir manually
- subdir = os.path.join(self.docs_dir, "foundation", "subdir")
- os.makedirs(subdir, exist_ok=True)
-
- # Copy the first task file to subdir with different ID
- files = [f for f in os.listdir(os.path.join(self.docs_dir, "foundation")) if f.endswith(".md")]
- src = os.path.join(self.docs_dir, "foundation", files[0])
- dst = os.path.join(subdir, "FOUNDATION-SUB-task.md")
-
- with open(src, "r") as f:
- content = f.read()
- # Hacky ID replace
- content = content.replace("id: " + content.split("id: ")[1].split()[0], "id: FOUNDATION-SUB")
- with open(dst, "w") as f:
- f.write(content)
-
- # List with category filter
- sys.stdout = StringIO()
- tasks.list_tasks(category="foundation", output_format="json")
- output = sys.stdout.getvalue()
- data = json.loads(output)
-
- ids = [t['id'] for t in data]
- # With fix, it should list it
- self.assertIn("FOUNDATION-SUB", ids, "Should list task in subdir with fixed logic")
-
- def test_archive_task(self):
- tasks.create_task("foundation", "Task to Archive", "Desc")
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_id = data[0]['id']
- filepath = data[0]['filepath']
-
- # Archive it
- sys.stdout = StringIO()
- tasks.archive_task(task_id, output_format="json")
- output = json.loads(sys.stdout.getvalue())
- self.assertTrue(output['success'])
-
- new_path = output['new_path']
- self.assertTrue(os.path.exists(new_path))
- self.assertFalse(os.path.exists(filepath))
- self.assertIn("archive", new_path)
-
- # Verify it is NOT listed by default
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- ids = [t['id'] for t in data]
- self.assertNotIn(task_id, ids)
-
- # Verify it IS listed with flag
- sys.stdout = StringIO()
- tasks.list_tasks(include_archived=True, output_format="json")
- data = json.loads(sys.stdout.getvalue())
- ids = [t['id'] for t in data]
- self.assertIn(task_id, ids)
-
- def test_link_part_of(self):
- tasks.create_task("foundation", "Task Child", "Desc")
- tasks.create_task("foundation", "Task Parent", "Desc")
-
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- child_id = [t for t in data if t['title'] == "Task Child"][0]['id']
- parent_id = [t for t in data if t['title'] == "Task Parent"][0]['id']
-
- # Link as part_of
- sys.stdout = StringIO()
- tasks.add_dependency(child_id, parent_id, output_format="json", link_type="part_of")
-
- # Verify
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- child_task = [t for t in data if t['id'] == child_id][0]
- self.assertIn(parent_id, child_task['part_of'])
-
- # Unlink as part_of
- sys.stdout = StringIO()
- tasks.remove_dependency(child_id, parent_id, output_format="json", link_type="part_of")
-
- # Verify
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- child_task = [t for t in data if t['id'] == child_id][0]
- self.assertNotIn(parent_id, child_task['part_of'])
-
- def test_link_related_to(self):
- tasks.create_task("foundation", "Task A", "Desc")
- tasks.create_task("foundation", "Task B", "Desc")
-
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_a_id = [t for t in data if t['title'] == "Task A"][0]['id']
- task_b_id = [t for t in data if t['title'] == "Task B"][0]['id']
-
- # Link as related_to
- sys.stdout = StringIO()
- tasks.add_dependency(task_a_id, task_b_id, output_format="json", link_type="related_to")
-
- # Verify
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_a = [t for t in data if t['id'] == task_a_id][0]
- self.assertIn(task_b_id, task_a['related_to'])
-
- # Unlink as related_to
- sys.stdout = StringIO()
- tasks.remove_dependency(task_a_id, task_b_id, output_format="json", link_type="related_to")
-
- # Verify
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_a = [t for t in data if t['id'] == task_a_id][0]
- self.assertNotIn(task_b_id, task_a['related_to'])
-
- def test_enforce_dependencies(self):
- # Create Task A (Pending)
- tasks.create_task("foundation", "Task A", "Desc")
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_a = [t for t in data if t['title'] == "Task A"][0]
- task_a_id = task_a['id']
-
- # Create Task B (Depends on A)
- tasks.create_task("foundation", "Task B", "Desc", dependencies=[task_a_id])
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_b = [t for t in data if t['title'] == "Task B"][0]
- task_b_id = task_b['id']
-
- # Try to update Task B to in_progress -> Should FAIL because A is pending
- with self.assertRaises(SystemExit):
- # Suppress stderr to keep test output clean
- with patch('sys.stderr', new=StringIO()):
- tasks.update_task_status(task_b_id, "in_progress")
-
- # Verify status did not change
- sys.stdout = StringIO()
- tasks.show_task(task_b_id, output_format="json")
- task_b_updated = json.loads(sys.stdout.getvalue())
- self.assertNotEqual(task_b_updated['status'], "in_progress")
-
- def test_update_with_satisfied_dependencies(self):
- # Create Task A (Completed)
- tasks.create_task("foundation", "Task A", "Desc", status="completed")
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_a = [t for t in data if t['title'] == "Task A"][0]
- task_a_id = task_a['id']
-
- # Create Task B (Depends on A)
- tasks.create_task("foundation", "Task B", "Desc", dependencies=[task_a_id])
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_b = [t for t in data if t['title'] == "Task B"][0]
- task_b_id = task_b['id']
-
- # Update Task B to in_progress -> Should SUCCEED
- tasks.update_task_status(task_b_id, "in_progress")
-
- # Verify status changed
- sys.stdout = StringIO()
- tasks.show_task(task_b_id, output_format="json")
- task_b_updated = json.loads(sys.stdout.getvalue())
- self.assertEqual(task_b_updated['status'], "in_progress")
-
- def test_breakdown_task(self):
- tasks.create_task("features", "Breakdown Feature", "Feature description")
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- task_id = [t for t in data if t['title'] == "Breakdown Feature"][0]['id']
-
- sys.stdout = StringIO()
- tasks.breakdown_task(task_id, output_format="text")
- output = sys.stdout.getvalue()
-
- self.assertIn("=== Micro-Planning Breakdown", output)
- self.assertIn("Title: Breakdown Feature", output)
- self.assertIn("Feature description", output)
- self.assertIn("--- INSTRUCTIONS FOR AI AGENT ---", output)
-
- sys.stdout = StringIO()
- tasks.breakdown_task(task_id, output_format="json")
- output = json.loads(sys.stdout.getvalue())
- self.assertEqual(output['task_id'], task_id)
- self.assertEqual(output['action'], "breakdown")
-
- def test_validate_links(self):
- # Create a task with invalid links
- tasks.create_task("foundation", "Task Invalid Links", "Desc", part_of=["INVALID-EPIC"], related_to=["INVALID-REL"])
-
- # Verify validation fails
- sys.stdout = StringIO()
- tasks.validate_all(output_format="json")
- output = json.loads(sys.stdout.getvalue())
-
- self.assertFalse(output["valid"])
- error_msgs = "\n".join(output["errors"])
- self.assertIn("Invalid part_of 'INVALID-EPIC'", error_msgs)
- self.assertIn("Invalid related_to 'INVALID-REL'", error_msgs)
-
- # Create valid tasks and link to them
- tasks.create_task("foundation", "Valid Epic", "Desc", task_type="epic")
- tasks.create_task("foundation", "Valid Rel", "Desc")
-
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- epic_id = [t for t in data if t['title'] == "Valid Epic"][0]['id']
- rel_id = [t for t in data if t['title'] == "Valid Rel"][0]['id']
- invalid_task_id = [t for t in data if t['title'] == "Task Invalid Links"][0]['id']
-
- # Update the invalid task to valid links
- filepath = tasks.find_task_file(invalid_task_id)
- tasks.update_frontmatter_field(filepath, "part_of", [epic_id])
- tasks.update_frontmatter_field(filepath, "related_to", [rel_id])
-
- # Verify validation succeeds
- sys.stdout = StringIO()
- tasks.validate_all(output_format="json")
- output = json.loads(sys.stdout.getvalue())
-
- self.assertTrue(output["valid"], f"Validation failed with errors: {output.get('errors')}")
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/test_tasks_agile.py b/tests/test_tasks_agile.py
deleted file mode 100644
index d09374d..0000000
--- a/tests/test_tasks_agile.py
+++ /dev/null
@@ -1,119 +0,0 @@
-import unittest
-import os
-import sys
-import tempfile
-import json
-from io import StringIO
-from unittest.mock import patch
-
-# Add scripts directory to path
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../scripts")))
-import tasks
-
-class TestTasksAgile(unittest.TestCase):
- def setUp(self):
- self.test_dir = tempfile.TemporaryDirectory()
- self.repo_root = self.test_dir.name
- self.docs_dir = os.path.join(self.repo_root, "docs", "tasks")
-
- # Monkey patch global variables in tasks module
- self.original_repo_root = tasks.REPO_ROOT
- self.original_docs_dir = tasks.DOCS_DIR
-
- tasks.REPO_ROOT = self.repo_root
- tasks.DOCS_DIR = self.docs_dir
-
- # Capture stdout
- self.held, sys.stdout = sys.stdout, StringIO()
-
- # Init docs structure
- tasks.init_docs()
-
- def tearDown(self):
- tasks.REPO_ROOT = self.original_repo_root
- tasks.DOCS_DIR = self.original_docs_dir
- self.test_dir.cleanup()
- sys.stdout = self.held
-
- def test_create_agile_task(self):
- tasks.create_task(
- "features",
- "Agile Story",
- "As a user...",
- task_type="story",
- sprint="Sprint 1",
- estimate="5"
- )
-
- files = os.listdir(os.path.join(self.docs_dir, "features"))
- task_files = [f for f in files if f.endswith(".md") and f != ".keep"]
- self.assertEqual(len(task_files), 1)
-
- with open(os.path.join(self.docs_dir, "features", task_files[0]), "r") as f:
- content = f.read()
- self.assertIn("type: story", content)
- self.assertIn("sprint: Sprint 1", content)
- self.assertIn("estimate: 5", content)
-
- def test_list_by_sprint(self):
- tasks.create_task("features", "S1 Task", "Desc", sprint="Sprint 1")
- tasks.create_task("features", "S2 Task", "Desc", sprint="Sprint 2")
-
- sys.stdout = StringIO()
- tasks.list_tasks(sprint="Sprint 1", output_format="json")
- data = json.loads(sys.stdout.getvalue())
- self.assertEqual(len(data), 1)
- self.assertEqual(data[0]["title"], "S1 Task")
-
- def test_next_task_priority(self):
- # Create 3 tasks
- # 1. High Priority, Story (Score: Med + HighPrio + Story)
- # 2. Medium Priority, Task (Score: Med + MedPrio + Task)
- # 3. In Progress (Score: High)
-
- tasks.create_task("foundation", "T1", "Desc", priority="high", task_type="story")
- tasks.create_task("foundation", "T2", "Desc", priority="medium", task_type="task")
- tasks.create_task("foundation", "T3", "Desc", priority="low", status="in_progress")
-
- sys.stdout = StringIO()
- tasks.get_next_task(output_format="json")
- best = json.loads(sys.stdout.getvalue())
-
- # T3 should win because it is in_progress (+1000)
- self.assertEqual(best["title"], "T3")
-
- # Now complete T3
- tasks.update_task_status(best["id"], "completed")
-
- sys.stdout = StringIO()
- tasks.get_next_task(output_format="json")
- best = json.loads(sys.stdout.getvalue())
-
- # Now T1 should win (High Prio + Story > Medium Prio + Task)
- self.assertEqual(best["title"], "T1")
-
- def test_next_task_dependency_blocking(self):
- # T1 depends on T2
- tasks.create_task("foundation", "T1", "Desc", priority="high")
- # I need to get the ID of T1, but create_task returns None (prints output).
- # So I list them to find IDs.
-
- sys.stdout = StringIO()
- tasks.list_tasks(output_format="json")
- data = json.loads(sys.stdout.getvalue())
- t1_id = data[0]["id"]
-
- # Create T2 depending on T1
- tasks.create_task("foundation", "T2", "Desc", priority="low", dependencies=[t1_id])
-
- # T1 is pending. T2 depends on T1.
- # Next task should be T1 (unblocked), not T2 (blocked by T1)
-
- sys.stdout = StringIO()
- tasks.get_next_task(output_format="json")
- best = json.loads(sys.stdout.getvalue())
-
- self.assertEqual(best["id"], t1_id)
-
-if __name__ == "__main__":
- unittest.main()