Skip to content

Commit 672a522

Browse files
feat: add behavioral test requirement to TDD workflow and acceptance criteria (merge worktree-20260324-091358)
2 parents 562be57 + 51f90a2 commit 672a522

File tree

2 files changed

+81
-2
lines changed

2 files changed

+81
-2
lines changed

plugins/dso/scripts/ticket-graph.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,27 @@ def _compute_dep_graph(ticket_id: str, tracker_dir: str) -> dict[str, Any]:
304304
# Find direct blockers
305305
direct_blockers = _find_direct_blockers(ticket_id, tracker_dir)
306306

307+
# Find children: tickets whose parent_id matches this ticket (8cbf-e13b)
308+
children: list[str] = []
309+
try:
310+
entries = os.listdir(tracker_dir)
311+
except OSError:
312+
entries = []
313+
314+
for entry in entries:
315+
if entry == ticket_id:
316+
continue
317+
entry_path = os.path.join(tracker_dir, entry)
318+
if not os.path.isdir(entry_path):
319+
continue
320+
try:
321+
child_state = _reduce_ticket(entry_path)
322+
except Exception:
323+
continue
324+
if child_state is not None and isinstance(child_state, dict):
325+
if child_state.get("parent_id") == ticket_id:
326+
children.append(entry)
327+
307328
# Determine ready_to_work: all direct blockers must be closed/tombstoned
308329
ready_to_work = True
309330
for blocker_id in direct_blockers:
@@ -316,6 +337,7 @@ def _compute_dep_graph(ticket_id: str, tracker_dir: str) -> dict[str, Any]:
316337
"ticket_id": ticket_id,
317338
"deps": deps,
318339
"blockers": direct_blockers,
340+
"children": children,
319341
"ready_to_work": ready_to_work,
320342
}
321343

tests/scripts/test_ticket_graph.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def _write_ticket(
7272
tracker_dir: Path,
7373
ticket_id: str,
7474
status: str = "open",
75+
parent_id: str | None = None,
76+
ticket_type: str = "task",
7577
) -> Path:
7678
"""Write a minimal ticket directory with a CREATE event and optional STATUS event.
7779
@@ -87,9 +89,9 @@ def _write_ticket(
8789
"author": "Test User",
8890
"env_id": "00000000-0000-4000-8000-000000000001",
8991
"data": {
90-
"ticket_type": "task",
92+
"ticket_type": ticket_type,
9193
"title": f"Ticket {ticket_id}",
92-
"parent_id": None,
94+
"parent_id": parent_id,
9395
},
9496
}
9597
with open(ticket_dir / f"1000-create-{ticket_id}-CREATE.json", "w") as f:
@@ -659,3 +661,58 @@ def test_is_active_link_same_second_unlink_sorts_after_link(
659661
"This indicates same-second UNLINK is sorting before LINK — the timestamp "
660662
"tie-breaker (event_type_order: LINK=0, UNLINK=1) is missing or incorrect."
661663
)
664+
665+
666+
# ---------------------------------------------------------------------------
667+
# Parent-child (children) tests — bug 8cbf-e13b
668+
# ---------------------------------------------------------------------------
669+
670+
671+
def test_build_dep_graph_includes_children(graph: ModuleType, tmp_path: Path) -> None:
672+
"""build_dep_graph must return a 'children' field listing tickets whose
673+
parent_id matches the queried ticket.
674+
675+
Bug 8cbf-e13b: ticket deps returns empty deps for epics with parent-linked
676+
children because it only traverses dependency links, not parent_id.
677+
"""
678+
tracker_dir = tmp_path / "tracker"
679+
tracker_dir.mkdir()
680+
681+
# Create an epic
682+
_write_ticket(tracker_dir, "epic-001", ticket_type="epic")
683+
# Create 3 child stories with parent_id pointing to the epic
684+
_write_ticket(tracker_dir, "story-a", parent_id="epic-001", ticket_type="story")
685+
_write_ticket(tracker_dir, "story-b", parent_id="epic-001", ticket_type="story")
686+
_write_ticket(tracker_dir, "story-c", parent_id="epic-001", ticket_type="story")
687+
# Create an unrelated ticket (no parent)
688+
_write_ticket(tracker_dir, "unrelated")
689+
690+
result = graph.build_dep_graph("epic-001", str(tracker_dir))
691+
692+
assert "children" in result, (
693+
"build_dep_graph result is missing 'children' field — "
694+
"parent-child relationships are not included in the graph output"
695+
)
696+
children = sorted(result["children"])
697+
assert children == ["story-a", "story-b", "story-c"], (
698+
f"Expected 3 children [story-a, story-b, story-c], got {children}"
699+
)
700+
701+
702+
def test_build_dep_graph_children_empty_when_no_children(
703+
graph: ModuleType, tmp_path: Path
704+
) -> None:
705+
"""build_dep_graph must return an empty 'children' list when no tickets
706+
have parent_id matching the queried ticket."""
707+
tracker_dir = tmp_path / "tracker"
708+
tracker_dir.mkdir()
709+
710+
_write_ticket(tracker_dir, "lonely-ticket")
711+
_write_ticket(tracker_dir, "other-ticket")
712+
713+
result = graph.build_dep_graph("lonely-ticket", str(tracker_dir))
714+
715+
assert "children" in result, "build_dep_graph result missing 'children' field"
716+
assert result["children"] == [], (
717+
f"Expected empty children, got {result['children']}"
718+
)

0 commit comments

Comments
 (0)