From 6dfa6a444110d75551a943607456ab6041920999 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Sun, 1 Feb 2026 00:17:50 +0000 Subject: [PATCH] feat: tk edit --children --- CHANGELOG.md | 5 +++ README.md | 2 +- features/ticket_edit.feature | 49 +++++++++++++++++++++ ticket | 84 +++++++++++++++++++++++++++++++++--- 4 files changed, 133 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b62c460..b9f08341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### Added +- `edit --children` flag to open a ticket and all its descendants in `$EDITOR` + ## [0.3.1] - 2026-01-28 ### Added diff --git a/README.md b/README.md index 1b5abaf1..6bf29e35 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Commands: blocked [-a X] [-T X] List open/in-progress tickets with unresolved deps closed [--limit=N] [-a X] [-T X] List recently closed tickets (default 20, by mtime) show Display ticket - edit Open ticket in $EDITOR + edit [--children] Open ticket in $EDITOR (--children includes descendants) add-note [text] Append timestamped note (or pipe via stdin) query [jq-filter] Output tickets as JSON, optionally filtered migrate-beads Import tickets from .beads/issues.jsonl diff --git a/features/ticket_edit.feature b/features/ticket_edit.feature index cd7569bf..75341b44 100644 --- a/features/ticket_edit.feature +++ b/features/ticket_edit.feature @@ -22,3 +22,52 @@ Feature: Ticket Edit When I run "ticket edit 0001" in non-TTY mode Then the command should succeed And the output should contain "edit-0001.md" + + Scenario: Edit with --children opens parent and child + Given a ticket exists with ID "edit-child1" and title "Child ticket" with parent "edit-0001" + When I run "ticket edit edit-0001 --children" in non-TTY mode + Then the command should succeed + And the output should contain "edit-0001.md" + And the output should contain "edit-child1.md" + + Scenario: Edit with --children opens nested descendants + Given a ticket exists with ID "edit-mid" and title "Middle ticket" with parent "edit-0001" + And a ticket exists with ID "edit-leaf" and title "Leaf ticket" with parent "edit-mid" + When I run "ticket edit edit-0001 --children" in non-TTY mode + Then the command should succeed + And the output should contain "edit-0001.md" + And the output should contain "edit-mid.md" + And the output should contain "edit-leaf.md" + + Scenario: Edit with --children on leaf ticket shows only that ticket + When I run "ticket edit edit-0001 --children" in non-TTY mode + Then the command should succeed + And the output should contain "edit-0001.md" + And the output line count should be 1 + + Scenario: Edit with --children outputs parent before children + Given a ticket exists with ID "edit-kid" and title "Kid ticket" with parent "edit-0001" + When I run "ticket edit edit-0001 --children" in non-TTY mode + Then the command should succeed + And the output line 1 should contain "edit-0001.md" + And the output line 2 should contain "edit-kid.md" + + Scenario: Edit with --children and partial ID + Given a ticket exists with ID "edit-pc01" and title "Parent for partial" + And a ticket exists with ID "edit-pc02" and title "Child for partial" with parent "edit-pc01" + When I run "ticket edit pc01 --children" in non-TTY mode + Then the command should succeed + And the output should contain "edit-pc01.md" + And the output should contain "edit-pc02.md" + + Scenario: Edit with --children flag before ID + Given a ticket exists with ID "edit-flagfirst" and title "Flag first child" with parent "edit-0001" + When I run "ticket edit --children edit-0001" in non-TTY mode + Then the command should succeed + And the output should contain "edit-0001.md" + And the output should contain "edit-flagfirst.md" + + Scenario: Edit with --children on non-existent ticket + When I run "ticket edit nonexistent --children" + Then the command should fail + And the output should contain "Error: ticket 'nonexistent' not found" diff --git a/ticket b/ticket index 452adda4..711dfc7f 100755 --- a/ticket +++ b/ticket @@ -1301,18 +1301,90 @@ cmd_add_note() { } cmd_edit() { - if [[ $# -lt 1 ]]; then - echo "Usage: ticket edit " >&2 + local children=false + local id="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --children) children=true; shift ;; + -*) echo "Error: unknown flag '$1'" >&2; return 1 ;; + *) id="$1"; shift ;; + esac + done + + if [[ -z "$id" ]]; then + echo "Usage: ticket edit [--children]" >&2 return 1 fi local file - file=$(ticket_path "$1") || return 1 + file=$(ticket_path "$id") || return 1 + + local files=("$file") + + if [[ "$children" == true ]]; then + local target_id + target_id=$(basename "$file" .md) + + local descendants + descendants=$(awk -v root="$target_id" ' + BEGIN { FS=": "; in_front=0 } + FNR==1 { + if (prev_file) store() + id=""; parent=""; in_front=0 + prev_file=FILENAME + } + /^---$/ { in_front = !in_front; next } + in_front && /^id:/ { id = $2 } + in_front && /^parent:/ { parent = $2 } + + function store() { + if (id != "") { + ids[id] = prev_file + if (parent != "") parents[id] = parent + } + } + + END { + if (prev_file) store() + + # Build parent -> children map + for (id in parents) { + p = parents[id] + if (!(p in child_count)) child_count[p] = 0 + child_count[p]++ + children[p, child_count[p]] = id + } + + # Iterative DFS + stack[1] = root + sp = 1 + while (sp > 0) { + node = stack[sp]; sp-- + if (node in visited) continue + visited[node] = 1 + if (node != root) print ids[node] + # Push children in reverse order for correct DFS ordering + n = (node in child_count) ? child_count[node] : 0 + for (i = n; i >= 1; i--) { + ch = children[node, i] + if (!(ch in visited)) { sp++; stack[sp] = ch } + } + } + } + ' "$TICKETS_DIR"/*.md 2>/dev/null) + + while IFS= read -r desc_file; do + [[ -n "$desc_file" ]] && files+=("$desc_file") + done <<< "$descendants" + fi if [ -t 0 ] && [ -t 1 ]; then - "${EDITOR:-vi}" "$file" + "${EDITOR:-vi}" "${files[@]}" else - echo "Edit ticket file: $file" + for f in "${files[@]}"; do + echo "Edit ticket file: $f" + done fi } @@ -1463,7 +1535,7 @@ Commands: blocked [-a X] [-T X] List open/in-progress tickets with unresolved deps closed [--limit=N] [-a X] [-T X] List recently closed tickets (default 20, by mtime) show Display ticket - edit Open ticket in \$EDITOR + edit [--children] Open ticket in \$EDITOR (--children includes descendants) add-note [text] Append timestamped note (or pipe via stdin) query [jq-filter] Output tickets as JSON, optionally filtered migrate-beads Import tickets from .beads/issues.jsonl