diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 436a429..3b30fb4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -90,6 +90,11 @@ "source": "./writing-specs-plugin", "description": "Spec-driven development with search, conflict detection, and reporting" }, + { + "name": "writing-tasks-plugin", + "source": "./writing-tasks-plugin", + "description": "Decompose specs into persistent task files with dependency graph, parallel lane suggestion, and dynamic progress tracking" + }, { "name": "computer-use-plugin", "source": "./computer-use-plugin", diff --git a/writing-tasks-plugin/.claude-plugin/plugin.json b/writing-tasks-plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..f80025c --- /dev/null +++ b/writing-tasks-plugin/.claude-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "writing-tasks-plugin", + "description": "Decompose specs into persistent task files with dependency graph, parallel lane suggestion, and dynamic progress tracking", + "version": "1.0.0", + "author": { + "name": "Stefan Cho" + } +} diff --git a/writing-tasks-plugin/README.md b/writing-tasks-plugin/README.md new file mode 100644 index 0000000..5cfad28 --- /dev/null +++ b/writing-tasks-plugin/README.md @@ -0,0 +1,78 @@ +# writing-tasks-plugin + +Persistent task files from specs, with dependency graph and dynamic progress. + +Natural pair of [`writing-specs-plugin`](../writing-specs-plugin): specs go in, tasks come out. + +## What it does + +- Decomposes `specs/phase-N/NN-name.md` into `tasks/phase-N-slug/NN-name.md` +- Enforces explicit dependencies on every task (no orphans) +- Derives progress, "ready to start" list, and parallel lane suggestions dynamically from task files +- Zero shared files under `tasks/` — each task is its own file so parallel worktrees never collide + +## Commands + +Two commands. Everything else is computed. + +``` +/writing-tasks # smart dispatch: decompose new specs OR show status +/writing-tasks new # manual task (ad-hoc hotfix/refactor) +``` + +## Layout + +``` +specs/ + phase-1/01-monorepo-setup.md + phase-3/02-profile-api-delete.md +tasks/ + phase-1-foundation/ + 01-monorepo-setup.md # id: 1.01 + phase-3-profile/ + 02-profile-api-delete.md # id: 3.02 +``` + +- Phase number cumulates across sprints (never resets) +- Local task number resets per phase (`01`, `02`, ...) +- Task id = `{phase}.{local}` — unambiguous cross-phase reference + +## Frontmatter + +```yaml +--- +id: "3.02" +phase: 3 +title: "DELETE /api/users/me" +spec: "specs/phase-3/02-profile-api-delete.md" +depends_on: ["3.01", "2.04", "5.03"] +blocks: [] +estimate: "M" +status: "todo" +owner: "" +sprint: "" +--- +``` + +## Why persistent files instead of a single TODO.md + +- Every worktree touches at most one task file → zero merge collisions for parallel work +- History of what was planned, what changed, what landed is self-contained per task +- `grep`/`glob` over task files gives any query you need (no separate DB) + +## Why no `README.md` or `DEPENDENCIES.md` under `tasks/` + +Shared files under `tasks/` create merge conflicts when multiple worktrees run in parallel. Progress, lanes, and the dependency graph are derived at query time from task frontmatter — no stored state to go stale. + +## Install + +```bash +/plugin marketplace add . +/plugin install writing-tasks-plugin@devstefancho-claude-plugins +``` + +## Related + +- [`writing-specs-plugin`](../writing-specs-plugin) — writes the specs this plugin decomposes +- [`git-worktree-plugin`](../git-worktree-plugin) — create worktrees named `task-N.NN-*` for parallel work +- [`implement-with-test-plugin`](../implement-with-test-plugin) — implement a task's spec with tests diff --git a/writing-tasks-plugin/skills/writing-tasks/SKILL.md b/writing-tasks-plugin/skills/writing-tasks/SKILL.md new file mode 100644 index 0000000..ef0546d --- /dev/null +++ b/writing-tasks-plugin/skills/writing-tasks/SKILL.md @@ -0,0 +1,234 @@ +--- +name: writing-tasks +description: Decompose specs into persistent task files (tasks/phase-N-slug/NN-name.md) with dependency graph, parallel lane suggestion, and dynamic progress. Use when user mentions task 분리, task 생성, task 쪼개기, spec을 task로, decompose, writing-tasks, 병렬 작업 편성, 의존성 그래프. Proactively trigger when a specs/ directory has unmapped specs or when user wants to track long-running work as persistent task files. +allowed-tools: Read, Write, Glob, Grep, Bash, AskUserQuestion +--- + +# Writing Tasks + +Manages persistent task files under `tasks/` by decomposing specs from `specs/`. Every task has an explicit dependency, progress/lanes are derived dynamically from task files (no shared state files), and parallel-worktree work is safe because each task lives in its own file. + +## Core Principles + +1. **Task file is the only SSOT** — No `README.md`, `DEPENDENCIES.md`, or `TEMPLATE.md` under `tasks/`. All progress/lanes/graphs are computed dynamically from task file frontmatter. +2. **Spec-driven by default** — Running `/writing-tasks` without arguments triggers decomposition from `specs/` to `tasks/`. Manual creation via `new` is the exception. +3. **Dependency is mandatory** — No task can be created without an explicit `depends_on` (empty array must be consciously declared, not a default). +4. **blocks ↔ depends_on auto-sync** — When A.depends_on includes B, the skill automatically adds A to B.blocks. Never let them drift apart. +5. **Validate on every write** — Circular dependencies, missing ids, phantom spec paths are rejected at write time. Users should never see a broken graph. +6. **Parallel-worktree safe** — Each task is its own file. No shared files under `tasks/` means worktrees never collide. + +## Directory Layout (v1 — fixed) + +``` +specs/ + phase-N/NN-slug.md ← input (user-authored or writing-specs) +tasks/ + phase-N-slug/NN-task-slug.md ← one task per file +``` + +- **Phase number**: monotonically increasing, cumulative across sprints. Phase 1 → 2 → … → N. Never resets. +- **Phase directory slug**: required, human-readable (e.g. `phase-1-foundation`, `phase-6-analytics`). +- **Local task number**: 2-digit, resets per phase (`01`, `02`, ... `99`). ID format is `N.NN` (e.g. `3.02` for phase 3, task 2). +- **Cross-phase id reference**: always `N.NN` — never ambiguous. + +Do NOT create: +- `tasks/README.md`, `tasks/DEPENDENCIES.md`, `tasks/TEMPLATE.md` — all derivable from task files. +- Scripts inside the user's repo (e.g. `scripts/task-status.ts`). The skill computes status itself. + +## Task File Frontmatter Schema + +```yaml +--- +id: "N.NN" +phase: N +title: "한줄 제목" +spec: "specs/phase-N/NN-name.md" +depends_on: ["N.NN", ...] +blocks: ["N.NN", ...] +estimate: "S" | "M" | "L" +status: "todo" | "in_progress" | "review" | "done" | "blocked" +owner: "" +sprint: "" # optional, reserved for v2 filtering +--- +``` + +Reject writes if any required field (`id`, `phase`, `title`, `spec`, `depends_on`, `estimate`, `status`) is missing. + +## Commands + +Only two user-facing commands. Everything else is derived. + +### `/writing-tasks` (no args) — smart dispatch + +Detect state and act: + +| State | Action | +|---|---| +| `specs/` has specs without matching tasks | Propose decomposition (see below). Show proposed task list, await approval, then create files. | +| All specs have matching tasks, but work is in progress | Output **status dashboard** (progress %, in_progress tasks, ready tasks, parallel lanes). | +| Detected a new phase directory under `specs/` (e.g. `specs/phase-6/` with no corresponding `tasks/phase-6-*/`) | Ask for the phase slug (e.g. "analytics"), then decompose. | +| All tasks done | Print `All done.` with one-line suggestion (next phase, or archive hint). | + +### `/writing-tasks new ` — manual task creation + +For ad-hoc tasks without a matching spec (hotfixes, refactors). + +Flow: +1. Ask: "Which phase does this belong to?" (multi-select of existing phases + `+ new phase`). +2. Show existing task summary (id, title, status) to help user choose dependencies. +3. Ask: "Select `depends_on`:" (multi-select existing task ids). +4. If user selects none: re-confirm "Is this truly independent (no prerequisites)?" Must opt-in explicitly. +5. Ask for estimate (S/M/L). +6. Generate next local number in that phase. +7. Write task file from template (see `templates/task-template.md`). +8. Auto-sync: add new id to each dependency's `blocks`. +9. Run validate. + +## Decomposition Flow (primary path) + +When `/writing-tasks` detects unmapped specs: + +### Step 1 — Scan + +``` +Glob specs/**/*.md +Glob tasks/**/*.md +``` + +Diff: specs present but not referenced by any task's frontmatter `spec:` field. + +### Step 2 — Propose + +For each unmapped spec, read its content and decide: +- **S (< 200 lines, simple)** → 1 task per spec. +- **M / L (multiple sections, many files)** → propose N tasks per spec (one per major section / milestone). If ambiguous, ask user. + +For each proposed task, infer `depends_on` from **four signals**: +1. **Phase block order** — tasks in lower phases default as candidate prerequisites. +2. **Spec body parsing** — `grep` the spec for references to other spec paths (e.g. `specs/phase-2/04-auth.md`), words like "requires", "선행", "depends on". +3. **Spec frontmatter inheritance** — if the spec has `depends_on` at the file level, carry it over. +4. **Shared module heuristic (flag only)** — if two specs mention the same `packages/*/src/**` path, flag as "potential sequential", require user confirmation (never auto-decide). + +Output a preview table: + +``` +Phase 3 — profile (6 specs → 8 tasks) + 3.01 d1-profile-migration no deps + 3.02 profile-api-patch depends_on: [3.01, 2.04] + 3.03 profile-api-delete depends_on: [3.01, 2.04, 5.03] + ⚠ cross-phase: 5.03 (recommendation_events table required before delete) + ... +``` + +Use `AskUserQuestion` with options: `proceed / edit / cancel`. On `edit`, let the user modify dependencies interactively. + +### Step 3 — Write + +- Create phase directory (`tasks/phase-N-slug/`) if new. Ask for slug if it's a brand-new phase. +- Write each task file from `templates/task-template.md`. +- Fill frontmatter, body sections (dependency reasoning, spec backlink, skeleton checklist from spec sections). +- After all writes: run validate once. + +### Step 4 — Report + +Emit a concise summary: + +``` +✅ Decomposition complete + Phases created: phase-3-profile (new), phase-4-news (new) + Tasks created: 14 + Cross-phase deps detected and documented in body: 2 + Validation: clean +``` + +## Status Dashboard (derived, no stored file) + +When `/writing-tasks` runs with fully-mapped tasks: + +``` +📊 Progress: X / Y done (Z%) + + Phase 1 — foundation ████████░░░░ 6/12 + Phase 2 — auth ██░░░░░░░░░░ 2/9 + ... + +🔵 In progress (N): + <duration since status change> + +✅ Ready to start (N): + <id> <title> (all deps met) + +⚡ Suggested parallel lanes (only show if >= 2 ready tasks): + Lane A: [ids] → touches <shared module> + Lane B: [ids] → touches <different module> + +⚠️ Blocked (N): + <id> waiting on <id> + +⚠️ Validation issues (only if any): + <brief list> +``` + +### Computing "ready" + +A task is ready when `status == "todo"` AND every id in `depends_on` has `status == "done"`. + +### Computing parallel lanes + +For all ready tasks: +1. Read each task's `구현 체크리스트` section, extract file paths / module directories. +2. Group by shared top-level module directory (e.g. `packages/api/src/routes/`). +3. Same group → same lane (sequential). Different groups → different lanes (parallel). +4. Flag conflicts when two lanes touch the same directory but are split by the user manually. + +## Validation Rules (run on every write) + +Reject if any fail: +- **Duplicate id** — two task files with the same `id`. +- **Phantom dependency** — `depends_on` references an id that doesn't exist. +- **Circular dependency** — any cycle in the depends_on graph. +- **Phantom spec** — `spec:` field points to a non-existent file (use `Glob` to check). +- **Missing required field** — any of the required frontmatter fields absent. +- **Asymmetric blocks/depends_on** — if A.depends_on has B, then B.blocks must have A. Auto-fix silently (this is a write-through invariant, not a user error). + +Warn (non-blocking): +- Task with `depends_on: []` but body's "의존성" section is empty — prompt to add reasoning or mark as "독립 task". +- Orphan task — task whose `spec:` was deleted (spec file no longer exists). + +## Incremental Updates + +On re-run, preserve existing task files: +- **New spec** → add new task(s), never overwrite existing. +- **Modified spec** → do NOT rewrite the matching task. Instead ask: "Spec `X` was modified. Resync task `N.NN`? [show diff]" +- **Deleted spec** → mark matching task as orphan (warning). Do not auto-delete. +- **Task completed** (`status: done`) → never modify its body on resync unless user explicitly asks. + +## Task Body Template + +See [templates/task-template.md](templates/task-template.md). Core sections: +- `## 의존성` — list of `- id (title) — reason` for each `depends_on`. Cross-phase must have a reason. +- `## 사전 준비` — checkboxes for environment/secret setup. +- `## 구현 체크리스트` — checkboxes, typically one per file or logical unit. Checked as commits land. +- `## Definition of Done` — verification criteria. +- `## 리스크 / 메모` — gotchas. + +## Worktree Integration + +The skill detects `.claude/worktrees/task-N.NN-*` branch naming. When invoked inside such a worktree: +- Auto-detect which task the worktree belongs to (parse branch name). +- Offer to transition `todo → in_progress` on that task. +- Show only that task's checklist as "Next up" in the status output. + +## What This Skill Does Not Do + +- Does not create worktrees — use `git-worktree-plugin` for that. +- Does not create specs — use `writing-specs-plugin` for that (the natural pair). +- Does not run tests or implement code — use `implement-with-test-plugin`. +- Does not manage todos in Obsidian vault — use `task-manager` (`/td`). + +## Interaction Style + +- Keep outputs tight. Status dashboard fits on one screen. +- Never create shared files the user didn't ask for. Only `tasks/phase-N-slug/NN-name.md`. +- All user decisions go through `AskUserQuestion`. +- When proposing decompositions, show the inference trail (which signal → which dependency) so the user can correct. diff --git a/writing-tasks-plugin/skills/writing-tasks/templates/task-template.md b/writing-tasks-plugin/skills/writing-tasks/templates/task-template.md new file mode 100644 index 0000000..6ec4af2 --- /dev/null +++ b/writing-tasks-plugin/skills/writing-tasks/templates/task-template.md @@ -0,0 +1,39 @@ +--- +id: "{{ID}}" +phase: {{PHASE}} +title: "{{TITLE}}" +spec: "{{SPEC_PATH}}" +depends_on: {{DEPENDS_ON}} +blocks: {{BLOCKS}} +estimate: "{{ESTIMATE}}" +status: "todo" +owner: "" +sprint: "" +--- + +# Task {{ID}} — {{TITLE}} + +> Spec: [`{{SPEC_PATH}}`]({{SPEC_REL_PATH}}) + +## 의존성 + +{{DEPENDENCY_LINES}} + +## 사전 준비 + +- [ ] {{PREREQ_1}} +- [ ] {{PREREQ_2}} + +## 구현 체크리스트 + +{{CHECKLIST_ITEMS}} + +## Definition of Done + +- [ ] 로컬 검증 절차 통과 +- [ ] 자동화된 테스트 통과 +- [ ] 관측 가능성 (로그/메트릭/감사) 확인 + +## 리스크 / 메모 + +- {{RISK_OR_NOTE}}