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
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
"pluginRoot": "."
},
"plugins": [
{
"name": "todo",
"source": "./commands/todo",
"description": "0.1.0 (2026-06-07) /todo — cross-domain TODO + multi-track launcher. Reads the repo's `DOMAINS.tape` roster + each domain snapshot, ranks domains by OPEN milestone count (`- [ ]`), and renders a numbered dashboard (★ active · 5-cell progress bar · open count · @goal snippet, sorted open-desc). Select a subset to drive in parallel: `/todo <n> <m> …` picks by number; `/todo <N> track` auto-picks the top-N most-open domains as N tracks (e.g. `/todo 2 track` → the two domains with the most open work). The pick PERSISTS a track set under `~/.sidecar/todo/<repo-key>.tracks` and prints a per-track launch plan (`track i: <NAME> → /domain set <NAME> && /cycle-bg`); the MODEL then fans out ONE background Agent per track — each runs `/domain set <NAME>` then autonomously drains that domain's open milestones (isolation worktree · one `/pr-cycle` per logical unit), the same command-prints-plan → model-fans-out contract as `/cycle`. Verbs — bare/`ls`/`list` (dashboard) · `<n…>` or `<N> track` (select + plan) · `go`/`plan` (re-print the saved plan) · `status` (show track set) · `clear` (drop it). Pairs with the `domain` plugin (roster + snapshots are its SSOT) but is strictly READ-ONLY over the repo — it never edits a domain doc / verdict / log; the only state written is the track set. Implemented in bash (`bin/_todo.sh`); the `/domain todo <task>` log verb is unrelated (this is a separate launcher plugin).",
"version": "0.1.0"
},
{
"name": "commons",
"source": "./hooks/commons",
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ For the full audit trail, see `git log`.

---

## 2026-06-07 — 📋 todo 0.1.0 — 크로스도메인 TODO + 멀티트랙 런처 (신규)

28개 도메인을 한눈에 보고 N개를 골라 병렬로 굴리는 런처. `DOMAINS.tape` 로스터 +
각 스냅샷을 읽어 **열린 마일스톤(`- [ ]`) 많은 순**으로 번호 매긴 대시보드를 그림.

- 📋 **대시보드** — `/todo` = `★ 활성 · 진행바 · open 수 · @goal` 표 (open desc 정렬).
- 🚦 **선택 → 멀티트랙** — `/todo <n> <m> …` 번호 선택 · `/todo <N> track` = open 최다
top-N 자동 선택 (예 `/todo 2 track`). 선택은 트랙셋(`~/.sidecar/todo/<key>.tracks`)에
저장되고, 트랙별 런치플랜(`track i: <NAME> → /domain set <NAME> && /cycle-bg`)을 출력.
- 🤖 **모델 팬아웃 계약** — /cycle 과 동일: 커맨드는 플랜만 찍고, 모델이 트랙당
background Agent 1개씩 팬아웃(도메인별 자율 드레인 · isolation worktree · track별 /pr-cycle).
- 🔒 **읽기 전용** — 도메인 doc/verdict/log 절대 미수정. 쓰는 상태는 트랙셋 파일 1개뿐.
- 검증: 28도메인 대시보드 렌더 · `2 track` top-2 선택 · go 재출력 · clear · sh 문법 ·
g22 lockstep(plugin 0.1.0 ↔ marketplace 0.1.0). `/domain todo <task>` 로그 verb 와는 무관.

## 2026-06-07 — 🧹 pr-cycle 0.5.0 — 머지 후 worktree sweep (누적 누수 차단)

`/cycle-bg` 를 돌릴수록 `.claude/worktrees/agent-*` worktree 디렉터리가 계속 쌓이던
Expand Down
9 changes: 9 additions & 0 deletions commands/todo/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "todo",
"description": "0.1.0 (2026-06-07) /todo — cross-domain TODO + multi-track launcher. Reads the repo's `DOMAINS.tape` roster + each domain snapshot, ranks domains by OPEN milestone count (`- [ ]`), and renders a numbered dashboard (★ active · 5-cell progress bar · open count · @goal snippet, sorted open-desc). Select a subset to drive in parallel: `/todo <n> <m> …` picks by number; `/todo <N> track` auto-picks the top-N most-open domains as N tracks (e.g. `/todo 2 track` → the two domains with the most open work). The pick PERSISTS a track set under `~/.sidecar/todo/<repo-key>.tracks` and prints a per-track launch plan (`track i: <NAME> → /domain set <NAME> && /cycle-bg`); the MODEL then fans out ONE background Agent per track — each runs `/domain set <NAME>` then autonomously drains that domain's open milestones (isolation worktree · one `/pr-cycle` per logical unit), the same command-prints-plan → model-fans-out contract as `/cycle`. Verbs — bare/`ls`/`list` (dashboard) · `<n…>` or `<N> track` (select + plan) · `go`/`plan` (re-print the saved plan) · `status` (show track set) · `clear` (drop it). Pairs with the `domain` plugin (roster + snapshots are its SSOT) but is strictly READ-ONLY over the repo — it never edits a domain doc / verdict / log; the only state written is the track set. Implemented in bash (`bin/_todo.sh`); the `/domain todo <task>` log verb is unrelated (this is a separate launcher plugin).",
"version": "0.1.0",
"author": { "name": "dancinlab" },
"repository": "https://github.com/dancinlab/sidecar",
"license": "MIT",
"keywords": ["claude-code", "command", "todo", "domain", "multi-track", "launcher", "orchestration"]
}
106 changes: 106 additions & 0 deletions commands/todo/bin/_todo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# _todo — cross-domain TODO + multi-track launcher for hexa-codex-style repos.
# Reads the DOMAINS.tape roster + each domain snapshot, ranks domains by OPEN
# milestone count, lets the user pick a subset (by number, or top-N via
# `<N> track`), persists the track set, and prints a per-track launch plan for
# the model to fan out (one background Agent per track, /domain set + drain).
# Read-only over the repo; the only state it writes is the track set under
# $TODO_HOME. No domain doc / verdict / log is ever modified (advisory only).
set -uo pipefail

ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "✗ /todo: not a git repo"; exit 0; }
ROSTER="$ROOT/DOMAINS.tape"
STATE_DIR="${TODO_HOME:-$HOME/.sidecar/todo}"
mkdir -p "$STATE_DIR"
KEY="$(printf '%s' "$ROOT" | cksum | cut -d' ' -f1)"
TRACKS="$STATE_DIR/$KEY.tracks"

# Best-effort active domain for (root) from the domain plugin's store (cosmetic ★).
_active() {
local tsv="$HOME/.claude/domain-active.tsv"
[ -f "$tsv" ] || return 0
awk -F'\t' -v r="$ROOT" '$1==r{n=$NF} END{print n}' "$tsv" 2>/dev/null
}

# Emit one row per domain: NAME|open|done|pct|goal — sorted by OPEN desc, then NAME.
_rows() {
[ -f "$ROSTER" ] || return 0
grep -E '^@domain ' "$ROSTER" \
| sed -E 's/^@domain +([A-Za-z0-9_+-]+) *:= *"([^"]+)".*/\1\t\2/' \
| while IFS="$(printf '\t')" read -r name path; do
local snap="$ROOT/${path#./}"
[ -f "$snap" ] || continue
local open done tot pct goal
open=$(grep -cE '^- \[ \]' "$snap" 2>/dev/null); open=${open:-0}
done=$(grep -ciE '^- \[x\]' "$snap" 2>/dev/null); done=${done:-0}
tot=$((open + done)); pct=0; [ "$tot" -gt 0 ] && pct=$((done * 100 / tot))
goal=$(grep -m1 -E '^@goal:' "$snap" 2>/dev/null | sed -E 's/^@goal:[*[:space:]]*//; s/\*\*//g' | cut -c1-44)
printf '%s|%s|%s|%s|%s\n' "$name" "$open" "$done" "$pct" "$goal"
done | sort -t'|' -k2,2nr -k1,1
}

_bar() { # pct -> 5-cell ▓/░ bar
local p=$1 i s="" f
f=$(((p * 5 + 50) / 100))
for i in 1 2 3 4 5; do [ "$i" -le "$f" ] && s="${s}▓" || s="${s}░"; done
printf '%s' "$s"
}

dashboard() {
local active; active="$(_active)"
echo "📋 /todo — cross-domain TODO (도메인별 열린 작업 · open 많은 순)"
echo " # 도메인 진행 open 목표"
local n=0
while IFS='|' read -r name open done pct goal; do
[ -z "$name" ] && continue
n=$((n + 1))
local star=" "; [ "$name" = "$active" ] && star="★"
printf ' %s%2d %-17s %s %3d%% %4d %s\n' "$star" "$n" "$name" "$(_bar "$pct")" "$pct" "$open" "$goal"
done < <(_rows)
[ "$n" = 0 ] && echo " (DOMAINS.tape 없음 또는 도메인 0개)"
if [ -s "$TRACKS" ]; then
echo " ── 현재 트랙셋 ──"; sed 's/^/ 🚦 /' "$TRACKS"
fi
echo " 선택: /todo <번호…> · 자동 top-N: /todo <N> track · 실행계획: /todo go · 해제: /todo clear"
}

plan() {
[ -s "$TRACKS" ] || { echo "트랙셋이 비어있음 — 먼저 /todo <번호…> 또는 /todo <N> track"; return 0; }
local rows; rows="$(_rows)"
local n; n=$(grep -c . "$TRACKS")
echo "🚦 멀티트랙 — $n track (도메인별 병렬 자율 드레인)"
local i=0 name open
while read -r name; do
[ -z "$name" ] && continue
i=$((i + 1))
open=$(printf '%s\n' "$rows" | grep "^$name|" | cut -d'|' -f2)
printf ' track %d: %-17s → /domain set %s && /cycle-bg (%s open)\n' "$i" "$name" "$name" "${open:-?}"
done < "$TRACKS"
echo "▶ 모델: 각 track 을 background Agent 로 팬아웃 — 도메인별 자율 드레인 · isolation worktree · track별 /pr-cycle."
}

pick() { # $@ = "<n> <m> …" OR "<N> track"
local rows; rows="$(_rows)"
[ -n "$rows" ] || { echo "✗ /todo: 도메인 없음 (DOMAINS.tape 확인)"; return 0; }
local names=() i nm
if [ "$#" -ge 2 ] && [ "$2" = "track" ] && printf '%s' "$1" | grep -qE '^[0-9]+$'; then
while IFS= read -r nm; do [ -n "$nm" ] && names+=("$nm"); done < <(printf '%s\n' "$rows" | head -n "$1" | cut -d'|' -f1)
else
for i in "$@"; do
printf '%s' "$i" | grep -qE '^[0-9]+$' || continue
nm="$(printf '%s\n' "$rows" | sed -n "${i}p" | cut -d'|' -f1)"
[ -n "$nm" ] && names+=("$nm")
done
fi
[ "${#names[@]}" -gt 0 ] || { echo "✗ /todo: 선택된 도메인 없음 — /todo 로 번호 먼저 확인"; return 0; }
printf '%s\n' "${names[@]}" > "$TRACKS"
plan
}

case "${1:-}" in
""|ls|list) dashboard ;;
go|plan) plan ;;
status) [ -s "$TRACKS" ] && { echo "🚦 현재 트랙셋:"; sed 's/^/ /' "$TRACKS"; } || echo "트랙셋 비어있음 — /todo <번호…>" ;;
clear) : > "$TRACKS"; echo "✅ 트랙셋 해제됨" ;;
*) pick "$@" ;;
esac
14 changes: 14 additions & 0 deletions commands/todo/commands/todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
description: /todo — cross-domain TODO + multi-track launcher. Reads the repo's DOMAINS.tape roster + each domain snapshot, ranks domains by OPEN milestone count (`- [ ]`), and renders a numbered dashboard (★ active · progress bar · open count · @goal). Select a subset to drive in parallel — `/todo <n> <m> …` picks by number, `/todo <N> track` auto-picks the top-N most-open domains as N tracks (e.g. `/todo 2 track`). The pick persists a TRACK SET (under ~/.sidecar/todo) and prints a per-track launch plan; the MODEL then fans out one background Agent per track (each runs `/domain set <NAME>` then autonomously drains that domain's open milestones, isolation worktree, one /pr-cycle per logical unit). Verbs — bare/ls (dashboard) · `<n…>` / `<N> track` (select) · go/plan (re-print plan) · status (show track set) · clear. Read-only over the repo; never edits a domain doc/verdict/log.
argument-hint: "[<n> <m> … | <N> track | go | status | clear]"
allowed-tools: Bash
---

!`ROOT="$CLAUDE_PLUGIN_ROOT"
if [ ! -d "$ROOT/bin" ]; then
V="$(ls -1 "$HOME/.claude/plugins/cache/sidecar/todo" 2>/dev/null | sort -V | tail -1)"
[ -n "$V" ] && ROOT="$HOME/.claude/plugins/cache/sidecar/todo/$V"
fi
S="$ROOT/bin/_todo.sh"
[ -f "$S" ] || { echo "✗ _todo.sh not found — /reload-plugins or hx install sidecar"; exit 1; }
bash "$S" $ARGUMENTS`