Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/tasks/INDEX.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ created: 2026-02-03 00:47:09
category: foundation
dependencies:
type: task
part_of: [EPIC-TEST]
related_to: [REL-TEST]
---

# Test Create V2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: FOUNDATION-20260306-022244-SBP
status: pending
status: completed
title: Implement parent and related fields in Frontmatter
priority: medium
created: 2026-03-06 02:22:44
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
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
2 changes: 1 addition & 1 deletion docs/tasks/foundation/TEST-GRAPH-V2.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: Test V2 Graph Features
priority: low
created: 2026-02-03 01:00:00
category: foundation
part_of: [FOUNDATION-20260203-EVAL]
part_of: [FOUNDATION-20260309-111149-LVP]
dependencies: [FOUNDATION-20260203-EVAL-MEMORY]
type: task
estimate: 1
Expand Down
36 changes: 20 additions & 16 deletions scripts/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,25 +922,27 @@ def validate_all(output_format="text"):
if "type" in frontmatter and frontmatter["type"] not in VALID_TYPES:
errors.append(f"{file}: Invalid type '{frontmatter['type']}'")

# Parse dependencies
deps_str = frontmatter.get("dependencies") or ""
# Use shared parsing logic
deps = []
if deps_str:
cleaned = deps_str.strip(" []")
if cleaned:
deps = [d.strip() for d in cleaned.split(",") if d.strip()]
# 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}
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: Dependency Validation & Cycle Detection
# Pass 2: Link Validation & Cycle Detection
visited = set()
recursion_stack = set()

Expand All @@ -966,12 +968,14 @@ def detect_cycle(curr_id, path):
return False

for task_id, info in all_tasks.items():
# Check dependencies exist
for dep_id in info["deps"]:
if dep_id not in all_tasks:
errors.append(f"{os.path.basename(info['path'])}: Invalid dependency '{dep_id}' (task not found)")

# Check cycles
# 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):
Expand Down
37 changes: 37 additions & 0 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,5 +315,42 @@ def test_breakdown_task(self):
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()