From b8abff84db09397b3aad7247985330a0701a4e00 Mon Sep 17 00:00:00 2001 From: jepegit Date: Sun, 19 Apr 2026 19:59:14 +0200 Subject: [PATCH 1/3] Enhance issue-flow lifecycle with new commands and smart dispatcher - Introduced four new commands: `/issue-plan`, `/issue-pause`, `/issue-cleanup`, and `/issue-yolo` to streamline the issue management process. - Updated `/issue-close` to delegate post-merge branch hygiene to `/issue-cleanup`. - Implemented `/iflow` as a smart dispatcher to automatically route to the appropriate command based on the current issue state. - Enhanced documentation in `HISTORY.md` to reflect these changes and provide clear guidance on the new command functionalities. This update improves the overall workflow efficiency and user experience in managing issues. --- .cursor/commands/iflow.md | 75 ++++++++ .cursor/commands/issue-cleanup.md | 60 ++++++ .cursor/commands/issue-close.md | 20 +- .cursor/commands/issue-pause.md | 57 ++++++ .cursor/commands/issue-plan.md | 80 ++++++++ .cursor/commands/issue-start.md | 30 ++- .cursor/commands/issue-yolo.md | 71 +++++++ .cursor/rules/issueflow-rules.mdc | 21 ++- .cursor/skills/issueflow-iflow/SKILL.md | 55 ++++++ .../skills/issueflow-issue-cleanup/SKILL.md | 44 +++++ .cursor/skills/issueflow-issue-close/SKILL.md | 16 +- .cursor/skills/issueflow-issue-pause/SKILL.md | 44 +++++ .cursor/skills/issueflow-issue-plan/SKILL.md | 49 +++++ .cursor/skills/issueflow-issue-start/SKILL.md | 32 ++-- .cursor/skills/issueflow-issue-yolo/SKILL.md | 49 +++++ .../01-current-issues/issue39_original.md | 51 ++++++ .../01-current-issues/issue39_status.md | 58 ++++++ HISTORY.md | 6 + docs/cursor-issue-workflow.md | 173 +++++++++++++++--- src/issue_flow/templates/commands/iflow.md.j2 | 75 ++++++++ .../templates/commands/issue-cleanup.md.j2 | 60 ++++++ .../templates/commands/issue-close.md.j2 | 20 +- .../templates/commands/issue-pause.md.j2 | 57 ++++++ .../templates/commands/issue-plan.md.j2 | 80 ++++++++ .../templates/commands/issue-start.md.j2 | 30 ++- .../templates/commands/issue-yolo.md.j2 | 71 +++++++ .../docs/cursor-issue-workflow.md.j2 | 173 +++++++++++++++--- .../templates/rules/issueflow-rules.mdc.j2 | 21 ++- .../skills/issueflow_iflow/SKILL.md.j2 | 55 ++++++ .../issueflow_issue_cleanup/SKILL.md.j2 | 44 +++++ .../skills/issueflow_issue_close/SKILL.md.j2 | 16 +- .../skills/issueflow_issue_pause/SKILL.md.j2 | 44 +++++ .../skills/issueflow_issue_plan/SKILL.md.j2 | 49 +++++ .../skills/issueflow_issue_start/SKILL.md.j2 | 32 ++-- .../skills/issueflow_issue_yolo/SKILL.md.j2 | 49 +++++ src/issue_flow/templating.py | 25 +++ tests/test_templating.py | 117 +++++++++++- 37 files changed, 1859 insertions(+), 150 deletions(-) create mode 100644 .cursor/commands/iflow.md create mode 100644 .cursor/commands/issue-cleanup.md create mode 100644 .cursor/commands/issue-pause.md create mode 100644 .cursor/commands/issue-plan.md create mode 100644 .cursor/commands/issue-yolo.md create mode 100644 .cursor/skills/issueflow-iflow/SKILL.md create mode 100644 .cursor/skills/issueflow-issue-cleanup/SKILL.md create mode 100644 .cursor/skills/issueflow-issue-pause/SKILL.md create mode 100644 .cursor/skills/issueflow-issue-plan/SKILL.md create mode 100644 .cursor/skills/issueflow-issue-yolo/SKILL.md create mode 100644 .issueflows/01-current-issues/issue39_original.md create mode 100644 .issueflows/01-current-issues/issue39_status.md create mode 100644 src/issue_flow/templates/commands/iflow.md.j2 create mode 100644 src/issue_flow/templates/commands/issue-cleanup.md.j2 create mode 100644 src/issue_flow/templates/commands/issue-pause.md.j2 create mode 100644 src/issue_flow/templates/commands/issue-plan.md.j2 create mode 100644 src/issue_flow/templates/commands/issue-yolo.md.j2 create mode 100644 src/issue_flow/templates/skills/issueflow_iflow/SKILL.md.j2 create mode 100644 src/issue_flow/templates/skills/issueflow_issue_cleanup/SKILL.md.j2 create mode 100644 src/issue_flow/templates/skills/issueflow_issue_pause/SKILL.md.j2 create mode 100644 src/issue_flow/templates/skills/issueflow_issue_plan/SKILL.md.j2 create mode 100644 src/issue_flow/templates/skills/issueflow_issue_yolo/SKILL.md.j2 diff --git a/.cursor/commands/iflow.md b/.cursor/commands/iflow.md new file mode 100644 index 0000000..bb508de --- /dev/null +++ b/.cursor/commands/iflow.md @@ -0,0 +1,75 @@ +# Smart dispatcher for the issue-flow lifecycle + +`/iflow` inspects the state of the focus issue and **dispatches** to the next logical command in the linear lifecycle — `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`. It never does work those commands don't already do; it just picks the right one for you. + +Off-path commands (`/issue-pause`, `/issue-cleanup`, `/issue-yolo`) are **not** auto-dispatched. Invoke them directly when you need them. + +## Input + +Optional free-form text after the command. `/iflow` forwards the raw trailing text to whichever command it dispatches to. Examples: + +- `/iflow` — dispatch with no extra args. +- `/iflow 42` — forwards `42` (useful when state resolves to `/issue-init`). +- `/iflow bump minor` — forwards `bump minor` (meaningful when state resolves to `/issue-close`; otherwise the downstream command will ignore or ask). +- `/iflow stick to the existing logger` — forwards the hint (useful when state resolves to `/issue-plan` or `/issue-start`). + +## Steps + +0. **Resolve the focus issue number `N`.** + - Run `git branch --show-current`. If it matches `^(\d+)-.+`, the leading digits are the **authoritative** `N`. + - List `issue_*` groups in `.issueflows/01-current-issues/`, and also check `.issueflows/02-partly-solved-issues/` and `.issueflows/03-solved-issues/` for archived groups matching `N`. + - Pick `N` using this precedence: + 1. **Branch-derived `N` wins**, regardless of whether a group for `N` exists in `01-current-issues/`. State A (dispatch `/issue-init`) will simply apply when no `issue_*` files are present yet. If `issue_*` is archived under `02-partly-solved-issues/` or `03-solved-issues/`, warn the user that `/issue-init`'s archived-issue guard will ask for an explicit confirmation before re-opening. + 2. Else if exactly one group exists in `01-current-issues/`, use that `N`. + 3. Else if there are **no** groups at all in `01-current-issues/` (and no branch-derived `N`), fall through to dispatch `/issue-init` (state A); `/issue-init` itself will ask for a number. + 4. Else (no branch-derived `N`, multiple groups in `01-current-issues/`), **stop** and ask the user which issue to act on. Do not guess. + +1. **Detect state and choose the dispatch target.** In priority order: + + | State | Condition | Dispatch to | Reason to report | + |-------|-----------|-------------|------------------| + | A | No `issue_original.md` for the focus issue (or no focus issue at all) | `/issue-init` | "no `*_original.md` yet" | + | B | `issue_original.md` exists, no `issue_plan.md` | `/issue-plan` | "no plan file yet" | + | C | Plan exists, and either no status file or the status file does **not** contain `- [x] Done` | `/issue-start` | "plan is confirmed but status is not `- [x] Done`" | + | D | Status file contains `- [x] Done` (case-insensitive on `done`) | `/issue-close` | "status marks the issue `- [x] Done`" | + + Use the first row whose condition matches. Never dispatch to more than one command in a single `/iflow` run. + +2. **Announce and dispatch.** Before running the downstream command, print one line explaining the decision, for example: + + ``` + /iflow -> /issue-plan (issue #42: no plan file yet) + ``` + + Then run the exact same logic as the chosen command (follow `.cursor/commands/.md`), forwarding the user's trailing text verbatim. The downstream command keeps all its own checkpoints (plan confirmation in `/issue-plan`, `/issue-start`'s soft stop when the plan is missing, `/issue-close`'s unrelated-changes prompt, etc.). + +3. **Report.** Summary should include: + - the focus issue number and how it was resolved (branch-derived / only group / user-specified) + - which command was dispatched to and why + - the downstream command's own output + - a one-line hint when an **off-path** command is the natural next step, e.g.: + - state **D** + PR likely merged → "after the PR merges, run `/issue-cleanup`" + - mid-stream context switch needed → "to park this work, run `/issue-pause`" + - tiny fix you want in one shot → "consider `/issue-yolo` next time" + +## Constraints + +- `/iflow` never skips a downstream command's own prompts. If the downstream step asks a question, surface it normally. +- `/iflow` never auto-dispatches to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. Those are explicit choices only. +- If the focus issue cannot be resolved (multiple active issues, branch ambiguous), stop and ask. Do not pick one silently. +- Do not modify files beyond what the downstream command would normally modify. `/iflow` itself writes nothing. + +## Example invocations + +- `/iflow` on a fresh branch `42-fix-login` with no `issueflows` files yet + -> dispatches to `/issue-init` which infers `#42` from the branch. +- `/iflow` on branch `42-fix-login` while unrelated groups (`issue99_*`, `issue100_*`) still sit in `01-current-issues/` + -> branch-derived `N=42` wins; dispatches to `/issue-init` (state A) which captures #42 and archives the other groups per its own sweep. +- `/iflow` after `/issue-init 42` has run and `issue42_original.md` exists, but there is no `issue42_plan.md` yet + -> dispatches to `/issue-plan`. +- `/iflow` after `/issue-plan` has written and the user confirmed `issue42_plan.md`, with no `issue42_status.md` or an unchecked `- [ ] Done` + -> dispatches to `/issue-start`. +- `/iflow bump patch` after implementation is done and `issue42_status.md` contains `- [x] Done` + -> dispatches to `/issue-close bump patch`. +- `/iflow` on branch `99-partial-work` after the issue was paused (files now under `02-partly-solved-issues/`) + -> branch-derived `N=99` wins; dispatches to `/issue-init` (state A since `01-` has no `issue99_*`). `/iflow` warns up front that `/issue-init`'s archived-issue guard will trigger and ask for explicit confirmation before restoring the group to `01-current-issues/`. diff --git a/.cursor/commands/issue-cleanup.md b/.cursor/commands/issue-cleanup.md new file mode 100644 index 0000000..490d04c --- /dev/null +++ b/.cursor/commands/issue-cleanup.md @@ -0,0 +1,60 @@ +# Post-merge branch and folder cleanup + +Run this after a PR has been merged (typically the one opened by `/issue-close`). It detects the merge, switches back to the default branch, and — with a **single consolidated confirm** — deletes every local branch whose commits are already in the default branch (including squash-merged branches). + +`/issue-cleanup` is the only command in the workflow that touches local branches destructively (via `git branch -d`, never `-D`). + +## Input + +Optional free-form text after the command. Examples: + +- **No extra text** — detect the current branch's PR, clean that up, plus any other local branches already merged into the default. +- A branch name — clean up that specific branch instead of the current one (e.g. `/issue-cleanup 42-fix-login`). + +## Steps + +1. **Detect the default branch.** + - Prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. + - Else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`. + - Else fall back to `main`. + +2. **Identify the branch to clean.** + - If the user passed a branch name, use it. + - Else use the current branch (`git branch --show-current`). If the current branch **is** the default, skip to step 5 (group sweep only); there is no issue branch to merge-check. + +3. **Check PR / merge state for the issue branch.** + - Prefer `gh pr view --json state,mergedAt,mergeCommit,headRefName`. + - If `gh` is unavailable, approximate with `git fetch --prune` then `git cherry origin/ `. All commits prefixed `-` means the branch is effectively merged (including squash-merges). + - **If the branch is not merged:** + - Remind the user that the working copy is still on the issue branch (not the default). Suggest `git switch ` before starting unrelated work. + - Tell them to re-run `/issue-cleanup` after the PR merges. + - **Stop.** Do not delete anything. + +4. **Consolidated confirm for post-merge cleanup.** Gather the full action list into a single yes/no prompt: + - `git switch ` + - `git pull --ff-only` + - `git fetch --prune` + - List **every** local branch whose tip is already reachable from `origin/` (use `git for-each-ref --format='%(refname:short)' refs/heads/` combined with `git cherry origin/ ` to catch squash-merges). Present the list explicitly and ask **once** before running `git branch -d ` for each. + - Never use `-D` automatically. If `-d` refuses (unmerged changes), report that branch and leave it alone. + +5. **Optional folder sweep** (safe; no destructive git). + - Group files in `.issueflows/01-current-issues/` by issue number (`issue_*`). + - For each group whose status file contains `- [x] Done` (case-insensitive on `done`), move the group to `.issueflows/03-solved-issues/`. + - Groups without a checked `Done` stay put — do not route them to `.issueflows/02-partly-solved-issues/` from `/issue-cleanup`. That is `/issue-pause`'s job and requires a human decision. + +## Output + +Report: +- default branch resolved +- PR/merge status of the target branch +- post-merge commands run (if any) +- list of local branches deleted (with `-d` output per branch) +- list of branches skipped because `-d` refused, with a one-line reason +- folder sweep summary (`issue` → `03-solved-issues/`, or "nothing to sweep") + +## Constraints + +- Never use `git branch -D` or `git push --force`. +- Never delete the default branch. +- If anything is ambiguous (detached HEAD, multiple remotes, missing tracking info), report and stop rather than guessing. +- Do not open or update PRs. Do not bump versions. Those belong to `/issue-close`. diff --git a/.cursor/commands/issue-close.md b/.cursor/commands/issue-close.md index d134e41..db40e84 100644 --- a/.cursor/commands/issue-close.md +++ b/.cursor/commands/issue-close.md @@ -1,6 +1,6 @@ # Close out the current issue -Run this when implementation is done and you are ready to land the work. +Run this when implementation is done and you are ready to land the work (commit, push, PR). Post-merge branch hygiene now lives in a separate command, `/issue-cleanup`, which you run **after** the PR is merged. ## Input @@ -10,7 +10,7 @@ Optional text after the command (same line). Examples: - **`bump`** or **`patch`** — bump the **patch** semver (e.g. `1.2.0` → `1.2.1`). - **`bump minor`** or **`minor`** — bump **minor**. - **`bump major`** or **`major`** — bump **major**. -- **Free text** — if it clearly asks to release or bump the version, infer `patch`, `minor`, or `major` from wording (e.g. “bugfix release” → patch); if unclear, ask once. +- **Free text** — if it clearly asks to release or bump the version, infer `patch`, `minor`, or `major` from wording (e.g. "bugfix release" → patch); if unclear, ask once. Other optional notes still apply: branch name, PR title, draft PR, skip issue doc update, commit all changes, etc. @@ -43,19 +43,11 @@ Other optional notes still apply: branch name, PR title, draft PR, skip issue do - Open a PR against the default branch (e.g. `main`). - Describe the change, how to test it, and link the GitHub issue (e.g. `Closes #123` or `Refs #123` in the PR body). -7. **Post-merge branch cleanup** - - Detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`; fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`, else `main`). - - Detect merge status of this PR with `gh pr view --json state,mergedAt,mergeCommit,headRefName`. If `gh` is unavailable, approximate with `git fetch --prune` followed by `git rev-list ..origin/` and `git cherry origin/ ` (all commits marked `-` means squash-merged). - - **If the PR is merged:** - - Ask once whether to run the standard post-merge cleanup. On yes, run: `git switch && git pull --ff-only && git fetch --prune`. These are non-destructive because the issue branch is already merged. - - List local branches whose tip is already reachable from `origin/` (including squash-merged ones detected via `git cherry`). Present them as a single group and ask **once** (one consolidated yes/no listing every branch) before running `git branch -d ` for each. Never use `-D` automatically; if `-d` refuses, report the branch and stop touching it. - - **If the PR is not yet merged:** - - Remind the user that the working copy is still on the issue branch, not the default branch. Suggest `git switch ` before starting any unrelated work so new changes don't accidentally land on the issue branch. Tell them to re-run `/issue-close` after the PR merges so the post-merge cleanup actually runs. - -8. **After review** +7. **After review** - Address feedback, push updates, and merge when approved and CI is green. - - Re-run `/issue-close` after the merge to pick up the post-merge cleanup in step 7. + - Remind the user that the working copy is still on the issue branch, not the default. Suggest `git switch ` before starting unrelated work so new changes don't accidentally land on the issue branch. + - Once the PR is merged, run **`/issue-cleanup`** to switch back to the default branch, `git pull --ff-only`, `git fetch --prune`, and delete local branches whose commits are already in the default branch (single consolidated confirm). `/issue-close` no longer does post-merge cleanup itself. ## Output -Summarize what was committed, pushed, and the PR URL (or next step if blocked). +Summarize what was committed, pushed, and the PR URL (or next step if blocked). Remind the user to run `/issue-cleanup` after the PR merges. diff --git a/.cursor/commands/issue-pause.md b/.cursor/commands/issue-pause.md new file mode 100644 index 0000000..3f732db --- /dev/null +++ b/.cursor/commands/issue-pause.md @@ -0,0 +1,57 @@ +# Pause work on the current issue + +Use this when you need to switch context without closing the issue. The command records where things stand, archives the issue group into `.issueflows/02-partly-solved-issues/`, and (optionally) parks your working tree safely. + +## Input + +Optional free-form text after the command. Examples: + +- **No extra text** — update status from what is visible in the diff and current TODOs. +- Short note — used as the "Remaining work" text verbatim (e.g. `/issue-pause waiting on design input from @user`). + +## Steps + +0. **Locate the focus issue.** In `.issueflows/01-current-issues/`, identify the `issue_*` group (original, plan, status). If multiple groups exist and the focus is ambiguous, ask which one to pause. If there is none, **stop** and tell the user there is nothing to pause. + +1. **Update the status file.** Create or update `issue_status.md` in `.issueflows/01-current-issues/` with: + + ```markdown + # Status for issue #: + + - [ ] Done + + ## Done so far + - Short bullets of what has landed or been tried. + + ## Remaining work + - Explicit next steps so a future you (or someone else) can resume. + + ## Paused on + <date, branch name, and any blockers — e.g. waiting on external input, blocked on another PR>. + ``` + + Keep any earlier content the user wrote; only add / update the sections above. The `- [ ] Done` checkbox **must** remain unchecked — a pause is not a close. + +2. **Move the issue group.** Move every `issue<N>_*` file from `.issueflows/01-current-issues/` to `.issueflows/02-partly-solved-issues/`. Report the moves. + +3. **Working-tree guard.** Run `git status --porcelain`. Report what is dirty. Then offer, as **one** consolidated prompt, up to three actions and let the user pick any combination: + - **WIP commit** — stage all tracked changes and commit with a message like `WIP: pause issue #<N> — <short note>`. Never silently include untracked files; list them and ask. + - **Switch to default branch** — detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`) and run `git switch <default>` (only after the WIP commit if the tree is dirty, to avoid `git switch` refusing). + - **Stay put** — leave branch and working tree untouched. + +4. **Branch hygiene note (non-destructive).** Tell the user the issue branch (typically `<N>-<slug>`) is still on the remote / local list. `/issue-pause` never deletes branches — resume with `/issue-init <N>` (or just check out the branch) when ready to continue. The matching folder is now `.issueflows/02-partly-solved-issues/`. + +## Output + +Report: +- status file path updated +- issue group moves (source → destination) +- working-tree actions taken (WIP commit SHA, branch switched to, or "left as-is") +- next step hint (e.g. "to resume: `/issue-init <N>` will pull the group back into `.issueflows/01-current-issues/` after confirming the archived-issue guard") + +## Constraints + +- The focus issue's `- [ ] Done` checkbox must stay unchecked. `/issue-pause` is **not** `/issue-close`. +- Do not delete branches. Do not `git reset` or `git stash drop`. +- Do not open a PR. Do not bump the version. Those are `/issue-close`. +- Do not run tests. Pausing should be quick. diff --git a/.cursor/commands/issue-plan.md b/.cursor/commands/issue-plan.md new file mode 100644 index 0000000..8c15fac --- /dev/null +++ b/.cursor/commands/issue-plan.md @@ -0,0 +1,80 @@ +# Plan the current issue before any code is written + +The issue should already be captured in `.issueflows/01-current-issues/issue<N>_original.md` (via `/issue-init`). Use this command to write a structured plan and get explicit user confirmation **before** `/issue-start` starts editing code. + +## Input + +Optional free-form text after the command. Examples: + +- **No extra text** — plan the focus issue using the `*_original.md` alone. +- Short constraints / design hints (e.g. "stick to the existing logger", "split into two PRs") — incorporate into the plan. + +## Steps + +0. **Locate the focus issue.** In `.issueflows/01-current-issues/`, find the `issue<N>_original.md`. If there is none, or multiple ambiguous groups, **stop** and ask the user. Suggest running `/issue-init` first. + +0.5 **Branch status preflight** (non-destructive — report, do not delete). + - Detect the default branch: `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. If `gh` is unavailable, fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`, else `main`. + - Run `git fetch --prune`. + - Report current branch, clean/dirty working tree (`git status --porcelain`), and ahead/behind counts vs `origin/<default>` (`git rev-list --left-right --count origin/<default>...HEAD`). + - If on the default branch, note it and suggest creating an issue branch (`git switch -c <N>-<short-slug>`) — do **not** auto-run it. Planning itself does not require a branch switch. + +1. **Read the issue.** Load `issue<N>_original.md` and any existing `issue<N>_status.md`. Do not rewrite them from this command. + +2. **Explore, then propose.** Do enough read-only research (search, read files, check existing tests) to design the change. Keep it proportional to the issue — small fix = short plan. + +3. **Write `issue<N>_plan.md`** in `.issueflows/01-current-issues/` with these sections: + + ```markdown + # Plan for issue #<N>: <title> + + ## Goal + One or two sentences on the desired outcome. + + ## Constraints + - Project rules, coding standards, back-compat, scope limits. + + ## Approach + Concrete design: data flow, affected modules, ordering of steps. + + ## Files to touch + - path/one.py — what changes + - path/two.md.j2 — what changes + + ## Test strategy + - Existing tests to re-run (e.g. `uv run pytest`) + - New tests or manual checks + + ## Open questions + - Anything that needs the user's call before coding. + ``` + + Keep it terse but specific. Link to files with markdown links when helpful. + +4. **Scope check.** If the plan is broad (many unrelated files, several independent deliverables, or mixes refactors with feature work), **propose splitting** into smaller issues or phased PRs and ask the user before continuing. + +5. **Confirm with the user.** Present the plan and **stop**. Ask one of: + - **Accept** — the plan is good; next run `/issue-start` to implement it. + - **Revise** — call out sections to change; update `issue<N>_plan.md` in place and re-confirm. + - **Abort** — remove `issue<N>_plan.md` (with the user's OK) or leave it as a draft. + + Do **not** proceed to implementation from `/issue-plan`. That belongs to `/issue-start`. + +6. **If `issue<N>_plan.md` already exists.** Do not overwrite silently. Ask whether to: + - **Update in place** (replace after the user sees the new content). + - **Keep both** (append a timestamped suffix like `issue<N>_plan.v2.md`). + - **Leave as is**. + +## Output + +Report: +- path written (e.g. `.issueflows/01-current-issues/issue<N>_plan.md`) +- brief summary of the plan +- whether the user has confirmed, revised, or paused on it +- any branch preflight warnings + +## Constraints + +- Read-only on source code; `/issue-plan` writes **only** the plan file under `.issueflows/01-current-issues/`. +- Do not move files between `01-` / `02-` / `03-` folders from `/issue-plan`. +- Do not run tests or package managers; that is `/issue-start` and `/issue-close`. diff --git a/.cursor/commands/issue-start.md b/.cursor/commands/issue-start.md index d452b17..3b3b685 100644 --- a/.cursor/commands/issue-start.md +++ b/.cursor/commands/issue-start.md @@ -1,13 +1,16 @@ -# Start working with current issue +# Start working with the current issue -The issue should already be explained in a markdown file in `.issueflows/01-current-issues`. +`/issue-start` **implements** the plan that `/issue-plan` already wrote. Planning itself now lives in `/issue-plan` — this command no longer produces the plan. + +The issue should already be explained in `.issueflows/01-current-issues/issue<N>_original.md` (from `/issue-init`) and a confirmed plan in `issue<N>_plan.md` (from `/issue-plan`). ## Input -If additional input is added, use that for further detailed guidance + +If additional input is added, use that as implementation hints (scope, constraints, design preferences). It does **not** replace the plan file — update `issue<N>_plan.md` via `/issue-plan` first if the plan itself needs to change. ## Steps -0. If the issue markdown file is not present, or it is ambiguous which one to select, ask. Could it be that the user has not run the /issue-init command? +0. **Find the focus issue.** Look in `.issueflows/01-current-issues/` for `issue<N>_original.md`. If missing or multiple groups are ambiguous, ask. Could the user have skipped `/issue-init`? 0.5 **Branch status preflight** (non-destructive — report, do not delete). - Detect the default branch: `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. If `gh` is unavailable, use `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`, else fall back to `main`. @@ -26,8 +29,21 @@ If additional input is added, use that for further detailed guidance - Never move the focus issue's own files. - Report every move (source -> destination, grouped by issue number) in the opening summary. -1. Plan. If not in plan mode, stop and ask for a confirmation. +1. **Plan precondition.** Look for `issue<N>_plan.md` in `.issueflows/01-current-issues/`. + - **Plan file present:** read it. Treat it as the source of truth for scope and approach. Continue to step 2. + - **Plan file missing:** **do not hard-stop.** Ask the user: + > "No plan file found for issue #N. How should I proceed? + > (a) Run `/issue-plan` now, then continue into implementation once you confirm the plan. + > (b) Proceed without a plan — I'll implement directly and note the skipped plan in the status file. + > (c) Abort." + Wait for an explicit choice. On **(a)**, run the `/issue-plan` flow first (including its user-confirmation stop), then return here. On **(b)**, add a short `- Skipped /issue-plan on <date>` note to `issue<N>_status.md` and continue. On **(c)**, stop. + +2. **Implement** the plan. Prefer minimal, focused diffs. Match existing code style and tooling. Follow project rules under `.cursor/rules/issueflow-rules.mdc` (e.g. `uv run` for Python, `uv add` / `uv remove` / `uv sync` for dependencies). + +3. **Update the status file.** After meaningful progress, update (or create) `issue<N>_status.md` under `.issueflows/01-current-issues/` with a `- [ ] Done` checkbox that stays unchecked until fully resolved. Record what has landed and what remains so `/issue-pause` or `/issue-close` has accurate context. + +4. **Hand off.** When the implementation is ready to ship, tell the user to run `/issue-close` (optionally with `bump`/`patch`/`minor`/`major`). Parking work mid-stream goes through `/issue-pause`. -2. Check that the plan is not too broad. If too broad, ask if it should be split into several parts. +## Output -3. Implement the steps of the plan +Summarize what was implemented, how it matches the plan, what remains, and any branch / sweep warnings surfaced during the preflight. diff --git a/.cursor/commands/issue-yolo.md b/.cursor/commands/issue-yolo.md new file mode 100644 index 0000000..12825a1 --- /dev/null +++ b/.cursor/commands/issue-yolo.md @@ -0,0 +1,71 @@ +# All-in-one flow for small, low-risk issues + +Chains `/issue-init` → `/issue-plan` → `/issue-start` → `/issue-close` in a single run, with safeguards. Use this **only** for minor fixes, docs tweaks, and similar low-risk changes where a full plan/review loop would be overkill. + +For anything non-trivial, use the individual commands so you get confirmation checkpoints. + +## Input + +Same as `/issue-init` (issue number, URL, or empty to infer from the branch). Optional extra tokens are forwarded to the downstream commands: + +- `bump` / `patch` / `minor` / `major` — forwarded to `/issue-close` for the version bump. +- `draft` — open a draft PR in `/issue-close`. +- Free-form notes — used as the plan/commit context. + +## Preflight (abort on any failure) + +1. **Refuse on default branch.** If the current branch is `main` / `master` / the detected default, **stop** and tell the user to create or switch to an issue branch first. Do not offer to create one silently from `/issue-yolo`. + +2. **Refuse with dirty unrelated changes.** Run `git status --porcelain`. If anything is uncommitted that is **not** obviously related to the target issue (ask the user if it's not clear), **stop**. Suggest committing / stashing first. + +3. **Tests must pass up front.** Run `uv run pytest` (or the repo's documented test command). If any test fails, **stop** before the chain starts. Yolo never ships on a broken baseline. + +4. **Single consolidated confirm.** Present the full planned chain explicitly, for example: + + ``` + /issue-yolo will run, without further prompts: + + 1. /issue-init 123 + 2. /issue-plan (auto-confirmed, short plan) + 3. /issue-start (implement directly) + 4. /issue-close patch (tests, bump, commit, push, PR) + + Target branch: 123-fix-typo + Repo: owner/repo + + Proceed? [y/N] + ``` + + Require an explicit yes. Any other input aborts. + +## Chain + +Once the preflight has passed and the user confirmed: + +1. **`/issue-init`** — capture the issue (or skip if the `*_original.md` already exists for the focus issue). + +2. **`/issue-plan`** — auto-confirmed. Write a **short** `issue<N>_plan.md` (Goal + Approach + Files to touch + Test strategy). Do not stop for user confirmation; the consolidated confirm above already covered it. If the scope check reveals the change is not actually small (touches many unrelated files, mixes refactors, etc.), **abort** the yolo chain and tell the user to run the commands individually. + +3. **`/issue-start`** — implement the plan. No additional plan-mode prompt. + +4. **Tests again.** Re-run `uv run pytest`. If anything fails, **stop**. Do not commit, push, or open a PR. Tell the user what failed and point at the work in progress. + +5. **`/issue-close`** — run the full close flow (optional version bump if the user passed `bump`/`patch`/`minor`/`major`, issue-folder update, commit, push, PR). Do **not** chain `/issue-cleanup` automatically — the PR has not merged yet. + +## Post-run + +- Leave the user on the issue branch with the PR URL. +- Remind them to re-run `/issue-cleanup` once the PR is merged. + +## Output + +Report, in order: +- preflight results (default-branch check, dirty tree check, initial test run) +- which downstream commands ran and where they stopped (if any) +- final state: commit SHA, PR URL (or reason the chain aborted) + +## Constraints + +- Do not override any of the downstream commands' own constraints (no `-D` in `/issue-cleanup`, etc.). `/issue-yolo` is a chain, not a free pass. +- If **any** downstream step requires a decision (unrelated changes in `git status`, ambiguous version bump, merge conflict, failed test), **stop** and hand back to the user. Never paper over a prompt just because the chain is running. +- Never run `/issue-cleanup` from `/issue-yolo`. Branch deletion always needs the user to see the merged PR first. diff --git a/.cursor/rules/issueflow-rules.mdc b/.cursor/rules/issueflow-rules.mdc index 9b50f15..e6253ae 100644 --- a/.cursor/rules/issueflow-rules.mdc +++ b/.cursor/rules/issueflow-rules.mdc @@ -72,14 +72,29 @@ issue-flow/ ### Working on issues -After each iteration, update the documents in `.issueflows/01-current-issues` (should contain one file labelled `_original` that consists of the original issue description, and supplementary status files describing what has been done, current status, and remaining work). +After each iteration, update the documents in `.issueflows/01-current-issues` (should contain one file labelled `_original` with the original issue description, a `_plan` file with the confirmed approach, and supplementary status files describing what has been done, current status, and remaining work). Use an explicit status checkbox in the status file: - `- [x] Done` when fully resolved - `- [ ] Done` when not fully resolved +### Command lifecycle + +If you just want the next right step, run **`/iflow`** — it detects state (by file presence under `.issueflows/01-current-issues/` and the status-file `- [x] Done` marker) and dispatches to `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`. It never auto-dispatches to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo` — those stay explicit. + +The full slash-command lifecycle is: + +1. **`/issue-init`** — capture the GitHub issue as `issue<N>_original.md`. +2. **`/issue-plan`** — design the approach in `issue<N>_plan.md` and get explicit confirmation before any code changes. +3. **`/issue-start`** — implement the confirmed plan. Asks to run `/issue-plan` first if the plan file is missing. +4. **`/issue-pause`** *(optional)* — park work mid-stream: update status, move the issue group to `02-partly-solved-issues`, optional WIP commit. +5. **`/issue-close`** — tests, optional `uv version --bump`, status update, commit, push, PR. Does not delete branches. +6. **`/issue-cleanup`** — post-merge: switch to default, `git pull --ff-only`, `git fetch --prune`, `git branch -d` on merged local branches under a single consolidated confirm. Never `-D`. + +`/issue-yolo` chains `init → plan → start → close` for small, low-risk issues with up-front safeguards (clean tree, passing tests, single consolidated confirm). + ### When finishing an issue -If the issue is fully resolved (no additional subtasks present), move the original and status markdown files to `.issueflows/03-solved-issues`. Else, move them to `.issueflows/02-partly-solved-issues`. +If the issue is fully resolved (no additional subtasks present), move the original, plan, and status markdown files to `.issueflows/03-solved-issues`. Else, move them to `.issueflows/02-partly-solved-issues`. ### Scripts that can help us when working on issues @@ -90,7 +105,7 @@ If you want, you can put small scripts etc. that you have made and think could b - Do issue work on an **issue branch** named like `<N>-<short-slug>`, not on the default branch. - Before starting or continuing work on an issue branch, run `git fetch --prune` and check where the branch sits relative to `origin/<default>` (ahead/behind). A branch that is "several commits ahead" after a merged PR usually means the PR was squash-merged and the local branch is stale. -- **Assume squash-merges on GitHub.** After a PR merges: switch to the default branch, `git pull --ff-only`, `git fetch --prune`, and delete the local issue branch with `git branch -d <branch>` (never `-D` automatically). `/issue-close` does this with a single consolidated confirm. +- **Assume squash-merges on GitHub.** After a PR merges: run **`/issue-cleanup`** — it switches to the default branch, runs `git pull --ff-only`, `git fetch --prune`, and deletes merged local branches with `git branch -d <branch>` under a single consolidated confirm (never `-D` automatically). `/issue-close` no longer does this step itself. - If an issue is already archived under `.issueflows/02-partly-solved-issues` or `.issueflows/03-solved-issues`, the matching local branch is stale; don't resume work on it silently — switch back to the default branch and, if the issue really needs re-opening, do it deliberately through `/issue-init` (which will ask for a second confirmation). diff --git a/.cursor/skills/issueflow-iflow/SKILL.md b/.cursor/skills/issueflow-iflow/SKILL.md new file mode 100644 index 0000000..350d3e3 --- /dev/null +++ b/.cursor/skills/issueflow-iflow/SKILL.md @@ -0,0 +1,55 @@ +--- +name: issueflow-iflow +description: >- + Run the /iflow smart dispatcher: detect where the focus issue stands in the + lifecycle (via files in .issueflows/01-current-issues/ and + status markers) and dispatch to /issue-init, /issue-plan, /issue-start, or + /issue-close. Forwards trailing args verbatim. Never auto-dispatches to + /issue-pause, /issue-cleanup, or /issue-yolo. +disable-model-invocation: true +--- + +# issue-flow — iflow smart dispatcher (`/iflow`) + +Follow this skill when the user wants to run **the right next step** in the issue-flow lifecycle without remembering which specific command applies. Matches `.cursor/commands/iflow.md`. + +## When to use + +- The user runs `/iflow`, mentions **iflow**, or asks "what's the next step?" during an issue-flow lifecycle. +- You want a single entry point that routes to `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close` based on current state. + +Do **not** use this skill for `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. Those are explicit-only commands. + +## Instructions + +1. **Resolve the focus issue number `N`.** + - `git branch --show-current`. If it matches `^(\d+)-.+`, the leading digits are the **authoritative** `N`. + - List `issue<n>_*` groups in `.issueflows/01-current-issues/`, and also check `.issueflows/02-partly-solved-issues/` and `.issueflows/03-solved-issues/` for archived groups matching `N`. + - Pick `N` by precedence: + 1. **Branch-derived `N` wins**, regardless of whether a group for `N` exists in `01-current-issues/`. State **A** will apply when no `issue<N>_*` files are present yet. If `issue<N>_*` is archived under `02-partly-solved-issues/` or `03-solved-issues/`, warn the user that `/issue-init`'s archived-issue guard will ask for an explicit confirmation before re-opening. + 2. No branch-derived `N`, exactly one group exists in `01-current-issues/` → use it. + 3. No branch-derived `N`, no groups at all → state **A** (dispatch `/issue-init`; it will ask for a number). + 4. No branch-derived `N`, multiple groups → **stop and ask**. + +2. **Detect state and choose the dispatch target** (first match wins): + + - **A** — no `issue<N>_original.md` (or no focus issue) → dispatch to **`/issueflow-issue-init`**. Reason: "no `*_original.md` yet". + - **B** — original exists, no `issue<N>_plan.md` → dispatch to **`/issueflow-issue-plan`**. Reason: "no plan file yet". + - **C** — plan exists, and status file is missing or its `- [x] Done` is unchecked → dispatch to **`/issueflow-issue-start`**. Reason: "plan is confirmed but status is not `- [x] Done`". + - **D** — status file contains `- [x] Done` (case-insensitive on `done`) → dispatch to **`/issueflow-issue-close`**. Reason: "status marks the issue `- [x] Done`". + +3. **Announce and dispatch.** Print one line like `/iflow -> /issue-plan (issue #N: no plan file yet)` and then follow the chosen command's playbook. Forward the user's trailing text verbatim. + +4. **Respect downstream checkpoints.** Never suppress the downstream command's own prompts (plan confirmation, unrelated-changes prompt, etc.). `/iflow` adds no new confirmation layer of its own. + +5. **Report.** Summarize: focus issue `N` and how it was resolved, which command was dispatched and why, the downstream output, and a one-line hint when an off-path command is the natural next step: + - state **D** + PR likely merged → "after the PR merges, run `/issue-cleanup`" + - mid-stream context switch needed → "to park this work, run `/issue-pause`" + - tiny fix that would benefit from a single-shot chain → "consider `/issue-yolo` next time" + +## Constraints + +- Never auto-dispatch to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. +- If the focus issue cannot be resolved (multiple groups, branch ambiguous), stop and ask. +- Do not modify files beyond what the downstream command would normally modify. `/iflow` itself writes nothing — all file changes come from the dispatched command. +- Dispatch to at most one command per `/iflow` invocation. diff --git a/.cursor/skills/issueflow-issue-cleanup/SKILL.md b/.cursor/skills/issueflow-issue-cleanup/SKILL.md new file mode 100644 index 0000000..ad92700 --- /dev/null +++ b/.cursor/skills/issueflow-issue-cleanup/SKILL.md @@ -0,0 +1,44 @@ +--- +name: issueflow-issue-cleanup +description: >- + Run the /issue-cleanup workflow: detect merge status, switch to the default + branch, and — with a single consolidated confirm — delete every local branch + already reachable from origin/<default> (including squash-merges). Never -D. +disable-model-invocation: true +--- + +# issue-flow — issue cleanup (`/issue-cleanup`) + +Follow this skill when the user wants to **run post-merge branch hygiene** after a PR has been merged, matching `.cursor/commands/issue-cleanup.md`. + +## When to use + +- The user runs `/issue-cleanup`, mentions **issue-cleanup**, or asks you to delete local branches whose PRs have merged. +- The PR opened by `/issue-close` just merged and the user wants the standard post-merge tidy-up. + +## Instructions + +1. **Detect the default branch.** Prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`. + +2. **Identify the target branch.** If the user named a branch after `/issue-cleanup`, use it. Else use the current branch (`git branch --show-current`). If the current branch **is** the default, skip to step 4 (folder sweep only). + +3. **Check PR / merge state.** Prefer `gh pr view <branch> --json state,mergedAt,mergeCommit,headRefName`. If `gh` is unavailable, approximate with `git fetch --prune` then `git cherry origin/<default> <branch>` (all commits marked `-` means squash-merged). + - **If not merged:** remind the user that the working copy is still on the issue branch; suggest `git switch <default>` before unrelated work and re-run `/issue-cleanup` after the PR merges. **Stop.** Do not delete anything. + - **If merged:** continue. + +4. **Consolidated confirm** — one yes/no prompt listing every action: + - `git switch <default>` + - `git pull --ff-only` + - `git fetch --prune` + - Every local branch whose tip is already reachable from `origin/<default>` (include squash-merges via `git cherry`). List them explicitly before running `git branch -d <branch>` for each. Never use `-D`; if `-d` refuses, report the branch and move on. + +5. **Optional folder sweep** (safe; no destructive git). In `.issueflows/01-current-issues/`, for each `issue<N>_*` group whose status file contains `- [x] Done` (case-insensitive on `done`), move the group to `.issueflows/03-solved-issues/`. Leave groups without a checked `Done` in place — routing them to `.issueflows/02-partly-solved-issues/` is `/issue-pause`'s job. + +6. **Report.** Summarize: default branch, PR/merge status, commands run, branches deleted, branches skipped (with reason), folder sweep result. + +## Constraints + +- Never use `git branch -D` or `git push --force`. +- Never delete the default branch. +- If anything is ambiguous (detached HEAD, multiple remotes, missing tracking info), report and stop rather than guess. +- Do not open or update PRs. Do not bump versions. Those belong to `/issue-close`. diff --git a/.cursor/skills/issueflow-issue-close/SKILL.md b/.cursor/skills/issueflow-issue-close/SKILL.md index a3c2eff..c8ed4bd 100644 --- a/.cursor/skills/issueflow-issue-close/SKILL.md +++ b/.cursor/skills/issueflow-issue-close/SKILL.md @@ -2,7 +2,8 @@ name: issueflow-issue-close description: >- Run the /issue-close workflow: verify tests, optional uv semver bump, update - issue status and folder locations, commit, push, and open a PR with a clear summary. + issue status and folder locations, commit, push, and open a PR with a clear + summary. Post-merge branch cleanup now lives in /issue-cleanup. disable-model-invocation: true --- @@ -10,6 +11,8 @@ disable-model-invocation: true Follow this skill when the user wants to **finish and land** work: tests, optional version bump, issue-folder updates, git, and PR. Match `.cursor/commands/issue-close.md`. +Post-merge branch hygiene now lives in `/issue-cleanup` — this skill no longer deletes branches. + ## When to use - The user runs `/issue-close`, mentions **issue-close**, or asks to commit, push, or open a PR after issue-flow work. @@ -31,7 +34,7 @@ When a bump applies: read `.cursor/skills/issueflow-version-bump/SKILL.md`, run 2. **Optional version bump** — If the user asked for a bump (see above), follow `.cursor/skills/issueflow-version-bump/SKILL.md` and run `uv version --bump <patch|minor|major>`. If there is no bumpable `pyproject.toml`, skip and continue. -3. **Issue tracking** — Under `.issueflows/01-current-issues/`, update the status file: remaining work, checklists, and **`- [x] Done`** only when the issue is fully resolved. If fully resolved, move that issue’s markdown files (`issue<n>_*`) to `.issueflows/03-solved-issues/`. If partially resolved, move to `.issueflows/02-partly-solved-issues/`. Follow any stricter rules in `.cursor/rules/issueflow-rules.mdc` if present. +3. **Issue tracking** — Under `.issueflows/01-current-issues/`, update the status file: remaining work, checklists, and **`- [x] Done`** only when the issue is fully resolved. If fully resolved, move that issue's markdown files (`issue<n>_*`) to `.issueflows/03-solved-issues/`. If partially resolved, move to `.issueflows/02-partly-solved-issues/`. Follow any stricter rules in `.cursor/rules/issueflow-rules.mdc` if present. 4. **Commit** — First check `git status`; if there are unrelated uncommitted changes, surface them and ask the user whether to include them — do not auto-include or drop silently. Then stage intentionally (include `pyproject.toml` and `uv.lock` if changed after a bump); write a commit message in full sentences describing what changed and why. @@ -41,13 +44,12 @@ When a bump applies: read `.cursor/skills/issueflow-version-bump/SKILL.md`, run 7. **Pull request** — Open (or update) a PR against the default branch. Body should explain the change, how to test, and link the GitHub issue (`Closes #n` / `Refs #n`). -8. **Post-merge branch cleanup** — Detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`; fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`). Detect merge status with `gh pr view <branch> --json state,mergedAt,mergeCommit,headRefName`. If `gh` is unavailable, approximate with `git fetch --prune` + `git cherry origin/<default> <branch>` (all commits marked `-` means squash-merged). - - **If merged:** ask once whether to run the standard cleanup. On yes: `git switch <default> && git pull --ff-only && git fetch --prune`. Then list every local branch whose tip is already reachable from `origin/<default>` (including squash-merged ones) and ask **once** (one consolidated yes/no listing every branch) before running `git branch -d <branch>` for each. Never use `-D` automatically; if `-d` refuses, report the branch and leave it alone. - - **If not yet merged:** remind the user the working copy is still on the issue branch (not the default). Suggest `git switch <default>` before starting unrelated work, and tell them to re-run `/issue-close` after the PR merges so the post-merge cleanup runs. +8. **After review** — Remind the user the working copy is still on the issue branch (not the default). Suggest `git switch <default>` before starting unrelated work. Tell them to run **`/issue-cleanup`** once the PR is merged so the standard post-merge cleanup runs (switch to default, `git pull --ff-only`, `git fetch --prune`, `git branch -d` on merged local branches under a single consolidated confirm). -9. **Output** — Summarize commit, push result, PR URL, and (when applicable) which local branches were deleted during post-merge cleanup. If blocked, report the next step. +9. **Output** — Summarize commit, push result, PR URL, and next step (`/issue-cleanup` after merge, or "blocked on …" if stuck). ## Constraints -- Do not skip failing tests without the user’s explicit agreement. +- Do not skip failing tests without the user's explicit agreement. - Prefer focused commits; do not rewrite unrelated history unless asked. +- Never delete branches from `/issue-close`. Branch deletion belongs to `/issue-cleanup`. diff --git a/.cursor/skills/issueflow-issue-pause/SKILL.md b/.cursor/skills/issueflow-issue-pause/SKILL.md new file mode 100644 index 0000000..16d5f79 --- /dev/null +++ b/.cursor/skills/issueflow-issue-pause/SKILL.md @@ -0,0 +1,44 @@ +--- +name: issueflow-issue-pause +description: >- + Run the /issue-pause workflow: update the status file with remaining work, + move the issue group to .issueflows/02-partly-solved-issues/, + and optionally make a WIP commit and switch to the default branch. +disable-model-invocation: true +--- + +# issue-flow — issue pause (`/issue-pause`) + +Follow this skill when the user wants to **park work on the current issue** without closing it, matching `.cursor/commands/issue-pause.md`. + +## When to use + +- The user runs `/issue-pause`, mentions **issue-pause**, or asks to park / stash / shelve work on an issue to switch context. +- The issue is **not** done — this is not `/issue-close`. + +## Instructions + +1. **Find the focus issue.** In `.issueflows/01-current-issues/`, identify the `issue<N>_*` group. If multiple groups exist and the focus is ambiguous, ask. If none exist, **stop** and say there is nothing to pause. + +2. **Update `issue<N>_status.md`.** Create or update it under `.issueflows/01-current-issues/` with: + - `- [ ] Done` (must remain **unchecked** — a pause is not a close). + - **Done so far** — short bullets of what has landed or been tried. + - **Remaining work** — explicit next steps so work can resume later. + - **Paused on** — date, branch name, any blockers. + + Preserve earlier user-written content; only add or update these sections. If the user passed a short note after `/issue-pause`, use it verbatim as the **Remaining work** text. + +3. **Move the issue group.** Move every `issue<N>_*` file from `.issueflows/01-current-issues/` to `.issueflows/02-partly-solved-issues/`. Report each move. + +4. **Working-tree guard.** Run `git status --porcelain`. Report what is dirty, then offer — as **one** consolidated prompt — any combination of: + - **WIP commit** — stage tracked changes and commit `WIP: pause issue #<N> — <short note>`. List untracked files separately and ask before including them. + - **Switch to default branch** — detect default (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`) and run `git switch <default>`. Only after the WIP commit if the tree is dirty. + - **Stay put** — leave branch and working tree untouched. + +5. **Report.** Summarize the status update, the issue-group moves, working-tree actions taken, and remind the user how to resume (running `/issue-init <N>` re-opens the archived issue after its archived-issue guard, or they can simply switch back to the issue branch). + +## Constraints + +- The focus issue's `- [ ] Done` checkbox **must** stay unchecked. `/issue-pause` is not `/issue-close`. +- Do not delete branches. Do not `git reset`, `git stash drop`, or force-push. +- Do not open a PR. Do not bump versions. Do not run tests. diff --git a/.cursor/skills/issueflow-issue-plan/SKILL.md b/.cursor/skills/issueflow-issue-plan/SKILL.md new file mode 100644 index 0000000..04e50c4 --- /dev/null +++ b/.cursor/skills/issueflow-issue-plan/SKILL.md @@ -0,0 +1,49 @@ +--- +name: issueflow-issue-plan +description: >- + Run the /issue-plan workflow: read the focus issue in + .issueflows/01-current-issues/, draft a structured plan + in issue<N>_plan.md, and get explicit user confirmation before any + implementation starts. +disable-model-invocation: true +--- + +# issue-flow — issue plan (`/issue-plan`) + +Follow this skill when the user wants to **design the approach** for an issue before touching code, matching `.cursor/commands/issue-plan.md`. + +## When to use + +- The user runs `/issue-plan`, mentions **issue-plan**, or asks you to design the approach / write a plan for the current issue. +- You want a clear, confirmed plan before `/issue-start` begins editing code. + +## Instructions + +1. **Find the focus issue.** Look in `.issueflows/01-current-issues/` for `issue<N>_original.md`. If it is missing or multiple groups are ambiguous, **stop** and ask. Suggest `/issue-init` first. + +2. **Branch status preflight** (non-destructive). Detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`). Run `git fetch --prune`. Report current branch, clean/dirty working tree, and ahead/behind vs `origin/<default>`. If on the default branch, suggest creating an issue branch (`git switch -c <N>-<short-slug>`) but do **not** auto-run it — planning itself does not require a branch switch. + +3. **Read context.** Load `issue<N>_original.md` and any existing `issue<N>_status.md`. Explore read-only: search code, read the files most likely to change, check existing tests. + +4. **Write `issue<N>_plan.md`** under `.issueflows/01-current-issues/` with these sections: + - **Goal** — one or two sentences. + - **Constraints** — project rules, back-compat, scope limits. + - **Approach** — concrete design, data flow, ordering. + - **Files to touch** — path + what changes for each. + - **Test strategy** — `uv run pytest` (or equivalents) and any new tests. + - **Open questions** — anything that needs the user's call before coding. + + Keep it terse but specific. Use markdown links to files when useful. + +5. **Scope check.** If the plan is broad (many unrelated files, mixes refactors with feature work, multiple independent deliverables), propose splitting into smaller issues or phased PRs before finalizing the plan. + +6. **Confirm with the user.** Present the plan and **stop**. Accept one of: **Accept** (ready for `/issue-start`), **Revise** (update `issue<N>_plan.md` in place and re-confirm), or **Abort**. + +7. **Conflict on existing `issue<N>_plan.md`.** Do not overwrite silently. Offer: update in place (after review), keep both (`issue<N>_plan.v2.md`), or leave as is. + +## Constraints + +- `/issue-plan` is **read-only on source code**. The only file it writes is `.issueflows/01-current-issues/issue<N>_plan.md`. +- Do not move files between `01-` / `02-` / `03-` folders from `/issue-plan`. +- Do not run tests or package managers; that belongs to `/issue-start` and `/issue-close`. +- Do not proceed to implementation from this skill. Hand off to `/issue-start` once the user confirms. diff --git a/.cursor/skills/issueflow-issue-start/SKILL.md b/.cursor/skills/issueflow-issue-start/SKILL.md index 96b4287..e5b1fe2 100644 --- a/.cursor/skills/issueflow-issue-start/SKILL.md +++ b/.cursor/skills/issueflow-issue-start/SKILL.md @@ -1,15 +1,15 @@ --- name: issueflow-issue-start description: >- - Run the /issue-start workflow: pick the current issue markdown, plan (with - confirmation when not in plan mode), guard scope, then implement with project - conventions (e.g. uv run). + Run the /issue-start workflow: pick the current issue, read issue<N>_plan.md + (offer to run /issue-plan if missing), then implement with project conventions + (e.g. uv run). disable-model-invocation: true --- # issue-flow — issue start (`/issue-start`) -Follow this skill when the user wants to **begin implementation** from issue notes, matching `.cursor/commands/issue-start.md` and project rules. +Follow this skill when the user wants to **begin implementation** from issue notes, matching `.cursor/commands/issue-start.md` and project rules. Planning itself lives in `/issue-plan`; this skill is now implementation-only. ## When to use @@ -24,21 +24,27 @@ Follow this skill when the user wants to **begin implementation** from issue not 3. **Sweep stale current issues** (auto-safe) — Group files in `.issueflows/01-current-issues/` by `issueNN_` prefix. For every group **other than the focus issue**, move the whole group to `.issueflows/03-solved-issues/` if any of its status files contains `- [x] Done` (case-insensitive on `done`), otherwise move it to `.issueflows/02-partly-solved-issues/`. Never move the focus issue's files. Report every move. -4. **Plan first** — Produce a concrete plan (steps, files touched, tests). If you are **not** in plan mode, **stop and ask for explicit confirmation** before implementing, per the command definition. +4. **Plan precondition** — Look for `issue<N>_plan.md` in `.issueflows/01-current-issues/`. + - **Plan present:** read it and treat it as the source of truth for scope and approach. + - **Plan missing:** do **not** hard-stop. Ask the user to choose one of: + - **Run `/issue-plan` now**, then continue into implementation after they confirm the plan. + - **Proceed without a plan** — add a short `- Skipped /issue-plan on <date>` note to `issue<N>_status.md` and continue. + - **Abort.** -5. **Scope check** — If the plan is broad, propose splitting into phases and ask whether to narrow scope before coding. +5. **Implement** — Execute the plan (or the explicitly-acknowledged plan-less path). Prefer minimal, focused diffs. Match existing code style and tooling. -6. **Implement** — Execute the confirmed plan. Prefer minimal, focused diffs. Match existing code style and tooling. - -7. **Project conventions** +6. **Project conventions** - Run Python via **`uv run`** (scripts, pytest, tools), not bare `python`, unless the user overrides. - Manage dependencies with **`uv add` / `uv remove` / `uv sync`** only. - - After meaningful progress, update or create a status markdown file under `.issueflows/01-current-issues/` (e.g. `issue<number>_status.md`) with an explicit **Done** checkbox: `- [ ] Done` until fully resolved, then `- [x] Done`. + - After meaningful progress, update or create `issue<N>_status.md` under `.issueflows/01-current-issues/` with an explicit `- [ ] Done` checkbox that stays unchecked until fully resolved. Record what has landed and what remains. + +7. **Hand off** — When the implementation is ready to ship, tell the user to run `/issue-close` (optionally with `bump`/`patch`/`minor`/`major`). Parking work mid-stream goes through `/issue-pause`. -8. **Reporting** — Summarize what changed, what remains, and where the issue docs live. Include any branch warnings from step 2 and any issue-group moves from step 3. +8. **Reporting** — Summarize what changed, what remains, and where the issue docs live. Include any branch warnings from step 2, any group moves from step 3, and whether the plan was followed or explicitly skipped. ## Constraints -- Do not invent issue text; treat `*_original.md` as read-only source of requirements unless the user asks to edit it. -- The stale sweep in step 3 is the **only** automatic move `/issue-start` performs, and it never touches the focus issue's own files. Do not move the focus issue's files between `01-` / `02-` / `03-` folders during `/issue-start`. +- Do not invent issue text; treat `*_original.md` as a read-only source of requirements unless the user asks to edit it. +- The stale sweep in step 3 is the **only** automatic folder move `/issue-start` performs, and it never touches the focus issue's own files. - Never delete or force-update git branches from `/issue-start`. +- Do not write or modify `issue<N>_plan.md` from here — changes to the plan go through `/issue-plan`. diff --git a/.cursor/skills/issueflow-issue-yolo/SKILL.md b/.cursor/skills/issueflow-issue-yolo/SKILL.md new file mode 100644 index 0000000..fe6a4a2 --- /dev/null +++ b/.cursor/skills/issueflow-issue-yolo/SKILL.md @@ -0,0 +1,49 @@ +--- +name: issueflow-issue-yolo +description: >- + Run the /issue-yolo workflow: preflight (no default branch, clean tree, + passing tests), single consolidated confirm, then chain init → plan → start + → close for small, low-risk issues. Stops on any ambiguity. +disable-model-invocation: true +--- + +# issue-flow — issue yolo (`/issue-yolo`) + +Follow this skill when the user wants to **blast through a small, low-risk issue** in one shot, matching `.cursor/commands/issue-yolo.md`. + +Use only for minor fixes, doc tweaks, and similar low-risk changes. Anything non-trivial should go through the individual commands. + +## When to use + +- The user runs `/issue-yolo`, `/issue-fast`, mentions **issue-yolo**, or asks to "just do it" for a small issue. +- The task is obviously small and the user has accepted that there will be no mid-run confirmation checkpoints. + +## Preflight (abort on any failure) + +1. **Refuse on default branch.** If the current branch is `main` / `master` / the detected default, **stop** and tell the user to create or switch to an issue branch first. Do not silently create one from yolo. + +2. **Refuse with dirty unrelated changes.** Run `git status --porcelain`. If anything uncommitted is not clearly part of the target issue, ask once; if still unclear, **stop**. Suggest committing or stashing first. + +3. **Tests must pass up front.** Run `uv run pytest` (or the repo's documented test command). On any failure, **stop** before the chain starts. + +4. **Single consolidated confirm.** Present the full planned chain explicitly (issue reference, target branch, repo, downstream commands including any `bump` / `patch` / `draft` flags). Require an explicit yes; any other input aborts. + +## Chain + +Once preflight has passed and the user confirmed: + +1. **`/issueflow-issue-init`** — capture the issue (or skip if `*_original.md` already exists for the focus issue). +2. **`/issueflow-issue-plan`** — write a **short** `issue<N>_plan.md` (Goal + Approach + Files to touch + Test strategy). Auto-confirm — the consolidated confirm above covered it. If the scope check reveals the change is not actually small, **abort the yolo chain** and tell the user to run the commands individually. +3. **`/issueflow-issue-start`** — implement the plan without an additional plan-mode prompt. +4. **Re-run tests.** `uv run pytest` again. On failure, **stop** before commit / push / PR. +5. **`/issueflow-issue-close`** — run the full close flow (optional version bump if the user passed `bump` / `patch` / `minor` / `major`, issue-folder update, commit, push, PR). Do **not** chain `/issueflow-issue-cleanup` automatically — the PR has not merged yet. + +## Post-run + +Leave the user on the issue branch with the PR URL. Remind them to re-run `/issue-cleanup` once the PR merges. + +## Constraints + +- Do not override downstream commands' own constraints (no `-D`, no force-push, etc.). `/issue-yolo` is a chain, not a free pass. +- If **any** downstream step requires a human decision (unrelated changes in `git status`, ambiguous version bump, merge conflict, failed test), **stop** and hand back to the user. +- Never run `/issue-cleanup` from this skill. Branch deletion always needs the user to see the merged PR first. diff --git a/.issueflows/01-current-issues/issue39_original.md b/.issueflows/01-current-issues/issue39_original.md new file mode 100644 index 0000000..2d6ed23 --- /dev/null +++ b/.issueflows/01-current-issues/issue39_original.md @@ -0,0 +1,51 @@ +# Issue #39: Expand issue-flow workflow with additional slash commands + +Source: https://github.com/jepegit/issue-flow/issues/39 + +## Original issue text + +## Description +The current **Init → Start → Close** workflow is a great foundation. However, to reduce friction during complex tasks, context switching, and post-merge cleanup, we should introduce a more granular set of commands. + +## Proposed New Commands + +### 1. `/issue-plan` +**Role:** Formalize the "thinking" phase before any code is touched. +* **Logic:** Move the planning steps currently in `/issue-start` into this dedicated command. +* **Output:** Generate an `issue<N>_plan.md` file and require explicit user confirmation before moving to implementation. + +### 2. `/issue-pause` +**Role:** Safely park work to switch context. +* **Logic:** + * Update the status file with "Remaining work". + * Immediately move the issue group to `.issueflows/02-partly-solved-issues/`. + * Suggest a WIP commit or switch the user back to the default branch (`main`/`master`). + +### 3. `/issue-cleanup` +**Role:** Dedicated "janitor" for post-merge maintenance. +* **Logic:** * Extract the post-merge logic from `/issue-close` (Step 7). + * Detect merged PRs via `gh pr view`. + * Perform the "consolidated confirm" to delete local branches whose tips are reachable from the default branch. + +### 4. `/issue-yolo` (or `/issue-fast`) +**Role:** An "all-in-one" command for minor bugfixes and documentation. +* **Logic:** Chained execution of `init` -> `start` -> `implement` -> `close` in a single flow. +* **Safeguards:** * Must abort if tests (e.g., `uv run pytest`) fail. + * Refuse to run if the working tree is dirty with unrelated changes. + * Single confirmation at the start before performing automated commit/push/PR. + +## Implementation Tasks +- [ ] Create Jinja2 templates for `/issue-plan.md.j2`. +- [ ] Create Jinja2 templates for `/issue-pause.md.j2`. +- [ ] Create Jinja2 templates for `/issue-cleanup.md.j2`. +- [ ] Create Jinja2 templates for `/issue-yolo.md.j2`. +- [ ] Update `cursor-issue-workflow.md` to reflect the expanded lifecycle. +- [ ] Update Agent Skills to include these new playbooks. + +## Expected End-to-End Flow +1. **`/issue-init`**: Capture. +2. **`/issue-plan`**: Define approach. +3. **`/issue-start`**: Execute implementation. +4. **`/issue-pause`**: (Optional) Park work. +5. **`/issue-close`**: Land work. +6. **`/issue-cleanup`**: Final branch/folder hygiene. diff --git a/.issueflows/01-current-issues/issue39_status.md b/.issueflows/01-current-issues/issue39_status.md new file mode 100644 index 0000000..5be8154 --- /dev/null +++ b/.issueflows/01-current-issues/issue39_status.md @@ -0,0 +1,58 @@ +# Status for issue #39: Expand issue-flow workflow with additional slash commands + +- [x] Done + +## Done so far + +- Added four new command templates under `src/issue_flow/templates/commands/`: + - `issue-plan.md.j2` — writes `issue<N>_plan.md` (Goal / Constraints / Approach / Files to touch / Test strategy / Open questions) and stops for user confirmation. + - `issue-pause.md.j2` — updates status with **Remaining work**, moves the `issue<N>_*` group to `.issueflows/02-partly-solved-issues/`, offers a consolidated WIP-commit + switch-to-default prompt. + - `issue-cleanup.md.j2` — owns post-merge branch hygiene (detect merge via `gh pr view`, consolidated single confirm, `git branch -d` on merged local branches including squash-merges; never `-D`). + - `issue-yolo.md.j2` — chains `init → plan → start → close` with up-front safeguards (refuses on default, refuses with dirty unrelated changes, requires `uv run pytest` passing, single consolidated confirm). Does not chain `/issue-cleanup`. +- Added matching skill templates under `src/issue_flow/templates/skills/` (`issueflow_issue_plan`, `issueflow_issue_pause`, `issueflow_issue_cleanup`, `issueflow_issue_yolo`). +- **Strict migration** done per user decision: + - `/issue-start` no longer plans. It reads `issue<N>_plan.md`; when missing, it offers three options (run `/issue-plan` now, proceed without a plan and note it in the status file, or abort) instead of hard-stopping. + - `/issue-close` no longer deletes branches. Step 7 now points users at `/issue-cleanup` after the PR merges. All other close steps are unchanged. + - The matching skills (`issueflow-issue-start`, `issueflow-issue-close`) were updated to mirror the command changes. +- Wired 8 new entries into `TEMPLATE_MANIFEST` in `src/issue_flow/templating.py` (total is now 17). +- Refreshed `docs/cursor-issue-workflow.md.j2` with the seven-command table, nine-skill table, updated end-to-end flow diagram, and a section per new command. +- Updated `rules/issueflow-rules.mdc.j2` with a **Command lifecycle** section describing the new flow and reworded the **Branch hygiene** bullet to point at `/issue-cleanup`. +- Ran `uv run issue-flow update` so the in-tree `.cursor/commands/`, `.cursor/skills/`, `.cursor/rules/`, and `docs/` files match the new templates (17 files refreshed). +- Updated `tests/test_templating.py`: bumped the manifest-count assertion (9 → 17), added coverage for every new command + skill entry, replaced the old `/issue-close` post-merge assertion with assertions against `/issue-cleanup`, and added smoke tests for the new command templates. +- Added an `## [Unreleased]` bullet to `HISTORY.md` describing the new commands and the two breaking migrations (planning out of `/issue-start`, cleanup out of `/issue-close`). +- **Added `/iflow` smart dispatcher** to soften the added complexity: it inspects focus-issue state (branch-derived `N` preferred, else single group, else ask) and dispatches to the right linear-flow command (`/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`) based on which files exist and whether the status file is marked `- [x] Done`. Forwards trailing args verbatim. Never auto-dispatches to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. + - New templates: `src/issue_flow/templates/commands/iflow.md.j2` + `src/issue_flow/templates/skills/issueflow_iflow/SKILL.md.j2`. + - Manifest grew from 17 to 19 entries; in-tree scaffold regenerated via `uv run issue-flow update`. + - Promoted `/iflow` as the quick-start entry point in `docs/cursor-issue-workflow.md.j2` (new "Smart dispatcher" section, new row in both the commands and skills tables) and added a one-liner at the top of the **Command lifecycle** section in `rules/issueflow-rules.mdc.j2`. + - Test coverage: bumped `test_manifest_entry_count` (17 → 19), added `/iflow` + `issueflow_iflow` to `test_manifest_has_expected_commands_and_skills`, and added `test_iflow_describes_state_machine` asserting the dispatch table mentions all four downstream commands, the `_original.md` / `_plan.md` / `- [x] Done` state keywords, and the off-path commands (as "not auto-dispatched"). + +## Implementation tasks from the original issue + +- [x] Create Jinja2 templates for `/issue-plan.md.j2`. +- [x] Create Jinja2 templates for `/issue-pause.md.j2`. +- [x] Create Jinja2 templates for `/issue-cleanup.md.j2`. +- [x] Create Jinja2 templates for `/issue-yolo.md.j2`. +- [x] Update `cursor-issue-workflow.md` to reflect the expanded lifecycle. +- [x] Update Agent Skills to include these new playbooks. + +## Tests + +- `uv run pytest` — **43 passed** (bumped from 42 after adding `test_iflow_treats_branch_derived_n_as_authoritative`). + +## Remaining work + +None. Ready for `/issue-close` (optionally `bump minor` given this is a meaningful feature addition with two breaking behavior changes). + +## Notes / decisions + +- **Migration strategy**: strict (per user). `/issue-start` no longer plans, `/issue-close` no longer cleans up merged branches. Existing users must adopt `/issue-plan` and `/issue-cleanup` explicitly. +- **`/issue-implement`**: not added. Yolo chains `init → plan → start → close` (user confirmed this was loose wording in the issue body, not a fifth command). +- **Yolo naming**: kept `/issue-yolo` (user's default preference). +- **Soft-stop refinement on `/issue-start`**: added after the initial plan — when `issue<N>_plan.md` is missing, `/issue-start` asks "run /issue-plan now / proceed without / abort" rather than hard-stopping, preserving an escape hatch for trivial tasks. +- **`/iflow` scoping (post-implementation addition)**: user confirmed two narrow choices to keep the dispatcher simple: + - **Never auto-dispatch to `/issue-cleanup`** — users run it explicitly after merge (no `gh pr view` lookup inside `/iflow`). + - **Off-path commands stay direct** — no `/iflow pause`, `/iflow yolo`, or `--force` subcommands. `/iflow` only routes the linear `init → plan → start → close` flow. +- **`/iflow` focus-resolution refinement (post-implementation)**: step 0, rule 1 of `/iflow` now treats a branch-derived `N` as **authoritative**, not merely "used if it matches a group under `01-current-issues/`". This fixes two rough edges: + - *Fresh branch + unrelated groups in `01-`*: on `42-fix-login` with `issue99_*` already present, `/iflow` no longer stops-and-asks; it uses `N=42` and dispatches to `/issue-init` (state A), which captures #42 and archives the unrelated group via its own sweep. + - *Resuming paused work*: on a branch whose `issue<N>_*` lives in `02-partly-solved-issues/` or `03-solved-issues/`, `/iflow` now warns up front that `/issue-init`'s archived-issue guard will ask for an explicit re-open confirmation, instead of silently dispatching to state A. + - Skill mirror and new test `test_iflow_treats_branch_derived_n_as_authoritative` added; scaffold regenerated; HISTORY sub-bullet updated. diff --git a/HISTORY.md b/HISTORY.md index 17a05c6..04c4242 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -13,6 +13,12 @@ than the GitHub release notes they link to. - Rename `ISSUEFLOW_CURSOR_DIR` to the more tool-agnostic `ISSUEFLOW_AGENT_DIR` (#36). - `/issue-close` flags unrelated uncommitted changes and reminds about the issue branch after the PR is opened (#37). - Branch and folder hygiene added to `/issue-init`, `/issue-start`, and `/issue-close`: non-destructive preflight reporting of current branch, ahead/behind counts, and working-tree state; automatic sweep of stale entries in `.issueflows/01-current-issues/` based on the `- [x] Done` marker in status files (#38, addresses #31). +- **Expanded slash-command lifecycle (#39).** Four new commands — `/issue-plan`, `/issue-pause`, `/issue-cleanup`, `/issue-yolo` — plus matching Agent Skills. + - `/issue-plan` writes a structured `issue<N>_plan.md` (Goal, Constraints, Approach, Files to touch, Test strategy, Open questions) and requires explicit user confirmation before any code is touched. The planning step was removed from `/issue-start` (**breaking**); `/issue-start` now reads the plan file and offers to run `/issue-plan` first if it is missing (with a "proceed without plan" escape hatch for trivial work). + - `/issue-pause` parks work mid-stream: updates the status file's **Remaining work** section, moves the issue group to `.issueflows/02-partly-solved-issues/`, and optionally makes a WIP commit and/or switches back to the default branch under a single consolidated confirm. + - `/issue-cleanup` now owns post-merge branch hygiene (detect merge via `gh pr view`, consolidated single confirm, `git branch -d` on every local branch reachable from `origin/<default>` — never `-D`). This logic was removed from `/issue-close` step 7 (**breaking**); `/issue-close` now points users at `/issue-cleanup` after the PR merges. + - `/issue-yolo` chains `init → plan → start → close` for small, low-risk issues with up-front safeguards (refuses on default branch, refuses with dirty unrelated changes, requires `uv run pytest` to pass, single consolidated confirm). Never chains `/issue-cleanup`. + - **Quick start `/iflow` smart dispatcher.** Inspects the focus issue (a branch-derived `N` from an `<N>-<slug>` branch is authoritative — it wins even when `issue<N>_*` files don't exist yet or unrelated groups sit in `.issueflows/01-current-issues/`; otherwise falls back to the single group in `01-`, else asks) and dispatches to `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close` based on which files exist and whether the status file is marked `- [x] Done`. Warns up front when the focus issue is archived under `02-partly-solved-issues/` or `03-solved-issues/` so the user knows `/issue-init`'s archived-issue guard will ask for an explicit re-open confirmation. Forwards trailing args verbatim. Never auto-dispatches to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo` — those stay explicit. ## [0.2.2] - 2026-04-17 diff --git a/docs/cursor-issue-workflow.md b/docs/cursor-issue-workflow.md index 53b8205..fee02f8 100644 --- a/docs/cursor-issue-workflow.md +++ b/docs/cursor-issue-workflow.md @@ -1,12 +1,19 @@ # Cursor issue workflow (slash commands) -This repo uses three Cursor **slash commands** under `.cursor/commands/` that line up with how we track GitHub issues in `.issueflows/01-current-issues/`. Use them in order when you pick up work from GitHub and want the assistant to follow the same steps. +This repo uses eight Cursor **slash commands** under `.cursor/commands/` that line up with how we track GitHub issues in `.issueflows/01-current-issues/`. + +**Quick start: just run `/iflow`.** It inspects the state of the focus issue and dispatches to the right linear-flow command (`/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`) — so you don't have to remember which step is next. | Command | File | Role | |--------|------|------| +| `/iflow` | `iflow.md` | **Smart dispatcher.** Detect current state and run `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close` automatically. Never auto-dispatches to pause / cleanup / yolo. | | `/issue-init` | `issue-init.md` | Pull an issue from GitHub into the repo as a local markdown file and tidy older current issues. | -| `/issue-start` | `issue-start.md` | Plan and implement the work described in `.issueflows/01-current-issues/`. | +| `/issue-plan` | `issue-plan.md` | Write a structured `issue<N>_plan.md` and get explicit user confirmation before any code is touched. | +| `/issue-start` | `issue-start.md` | Implement the confirmed plan (no planning step of its own any more). | +| `/issue-pause` | `issue-pause.md` | Park work safely: update status, move the issue group to `02-partly-solved-issues/`, optional WIP commit and branch switch. | | `/issue-close` | `issue-close.md` | Finish: tests, optional semver bump (`uv version --bump …`), issue-folder housekeeping, commit, push, PR. | +| `/issue-cleanup` | `issue-cleanup.md` | Post-merge hygiene: switch to default, `git pull --ff-only`, `git fetch --prune`, delete merged local branches (single consolidated confirm). | +| `/issue-yolo` | `issue-yolo.md` | All-in-one for small, low-risk issues: chains `init → plan → start → close` with up-front safeguards and a single confirmation. | --- @@ -16,9 +23,14 @@ This repo uses three Cursor **slash commands** under `.cursor/commands/` that li | Skill folder | Invoke (examples) | Role | |--------------|-------------------|------| -| `issueflow-issue-init` | `/issueflow-issue-init` or attach `@issueflow-issue-init` | Same flow as `/issue-init` (resolve reference, `gh`, archive, write `*_original.md`). | -| `issueflow-issue-start` | `/issueflow-issue-start` | Plan + confirmation + scope + implement from `.issueflows/01-current-issues/`. | +| `issueflow-iflow` | `/issueflow-iflow` or attach `@issueflow-iflow` | Smart dispatcher — same state machine as `/iflow`. | +| `issueflow-issue-init` | `/issueflow-issue-init` or attach `@issueflow-issue-init` | Same flow as `/issue-init`. | +| `issueflow-issue-plan` | `/issueflow-issue-plan` | Same flow as `/issue-plan` (write & confirm plan). | +| `issueflow-issue-start` | `/issueflow-issue-start` | Read the plan, implement from `.issueflows/01-current-issues/`. | +| `issueflow-issue-pause` | `/issueflow-issue-pause` | Update status, move issue group to `02-partly-solved-issues/`, optional WIP commit + branch switch. | | `issueflow-issue-close` | `/issueflow-issue-close` | Tests, optional bump, status checkboxes, move issue docs, commit, push, PR. | +| `issueflow-issue-cleanup` | `/issueflow-issue-cleanup` | Post-merge cleanup (single consolidated confirm, never `-D`). | +| `issueflow-issue-yolo` | `/issueflow-issue-yolo` | Chain `init → plan → start → close` with safeguards. | | `issueflow-version-bump` | `@issueflow-version-bump` (often used from `/issue-close`) | Bump `[project]` version in `pyproject.toml` via `uv version --bump patch|minor|major`. | Each skill sets `disable-model-invocation: true` so it is included when you **explicitly** invoke it, not on every chat. See [Agent Skills](https://cursor.com/docs/context/skills) in the Cursor docs. @@ -29,10 +41,33 @@ Each skill sets `disable-model-invocation: true` so it is included when you **ex Two recurring pain points the commands actively help with: -- **Stale local branches that look "several commits ahead of main" after a squash-merged PR.** `/issue-close` detects merge status via `gh pr view`, and once the PR is merged it offers (with one consolidated confirm) to switch back to the default branch, `git pull --ff-only`, `git fetch --prune`, and run `git branch -d` on every local branch whose commits are already in the default branch (including squash-merged ones). Destructive flags like `-D` are never used automatically. +- **Stale local branches that look "several commits ahead of main" after a squash-merged PR.** `/issue-cleanup` detects merge status via `gh pr view`, and once the PR is merged it offers (with one consolidated confirm) to switch back to the default branch, `git pull --ff-only`, `git fetch --prune`, and run `git branch -d` on every local branch whose commits are already in the default branch (including squash-merged ones). Destructive flags like `-D` are never used automatically. `/issue-close` no longer performs this step itself. - **Left-overs in `.issueflows/01-current-issues/`.** Both `/issue-init` (when a new issue is captured) and `/issue-start` (before implementation begins) sweep that folder: every `issue<n>_*` group **other than the focus issue** is moved automatically to `.issueflows/03-solved-issues/` if a status file contains `- [x] Done`, otherwise to `.issueflows/02-partly-solved-issues/`. -All three commands also run a short **branch-status preflight**: `git fetch --prune`, current branch, ahead/behind vs the default branch, and a warning when the current branch's leading digits refer to an issue already archived in `02-`/`03-`. +All the commands that touch git also run a short **branch-status preflight**: `git fetch --prune`, current branch, ahead/behind vs the default branch, and a warning when the current branch's leading digits refer to an issue already archived in `02-`/`03-`. + +--- + +## 0. `/iflow` — smart dispatcher (quick start) + +**When:** Any time you want the next right step without remembering which specific command applies. + +**What you pass:** Nothing, or the same arguments the target command would take (e.g. `/iflow 42` on a fresh branch, `/iflow bump minor` when the issue is done). `/iflow` forwards the trailing text verbatim. + +**How it decides:** + +| State of the focus issue | Dispatches to | +|--------------------------|---------------| +| No `issue<N>_original.md` (or no focus issue yet) | `/issue-init` | +| `original` exists, no `issue<N>_plan.md` | `/issue-plan` | +| Plan exists, status file missing or `- [ ] Done` | `/issue-start` | +| Status file contains `- [x] Done` | `/issue-close` | + +**Focus-issue resolution:** prefer the leading digits of the current branch when it matches `^<N>-.+`; else the single group in `.issueflows/01-current-issues/`; else ask. + +**Not auto-dispatched:** `/issue-pause`, `/issue-cleanup`, and `/issue-yolo`. `/iflow` will mention them in its output when relevant (e.g. "after the PR merges, run `/issue-cleanup`") but never picks them for you. + +**Result:** One of the four linear commands runs, with its own normal checkpoints intact. --- @@ -49,32 +84,66 @@ All three commands also run a short **branch-status preflight**: `git fetch --pr - **Archive:** Other files already in `.issueflows/01-current-issues/` (grouped by issue number, e.g. `issue121_*`) may be **moved** to `.issueflows/02-partly-solved-issues/` or `.issueflows/03-solved-issues/`, based on whether a status file for that issue contains a checked **Done** line (`- [x] Done`). The new issue's files are never moved as part of this step. - If the target `issue<number>_original.md` already exists, the assistant should not overwrite it without asking. -**Result:** One canonical "original issue" file under `.issueflows/01-current-issues/` plus optional archive moves. You can add or edit a separate `issue<number>_status.md` (or similar) by hand or with the assistant as work progresses. +**Result:** One canonical "original issue" file under `.issueflows/01-current-issues/` plus optional archive moves. --- -## 2. `/issue-start` — plan and implement +## 2. `/issue-plan` — design the approach -**When:** The issue is represented in `.issueflows/01-current-issues/` (at minimum the `*_original.md` file) and you are ready to code. +**When:** The issue is captured (`*_original.md` exists) and you want a confirmed plan **before** any code changes. -**What you pass:** Optional extra instructions (scope, constraints, design preferences). +**What you pass:** Optional free-form hints (constraints, design preferences). + +**What the assistant does:** + +1. Finds the focus issue in `.issueflows/01-current-issues/`. +2. Runs the branch-status preflight (non-destructive). +3. Reads the original issue and any prior status. +4. Writes **`issue<N>_plan.md`** with sections: **Goal**, **Constraints**, **Approach**, **Files to touch**, **Test strategy**, **Open questions**. +5. Runs a scope check — if the change is broad, proposes splitting into smaller issues or phases. +6. **Stops and asks for explicit confirmation**: accept, revise, or abort. `/issue-plan` never implements code itself. + +**Result:** A confirmed `issue<N>_plan.md` ready for `/issue-start` to execute. + +--- + +## 3. `/issue-start` — implement the plan + +**When:** The issue has a confirmed `issue<N>_plan.md` (from `/issue-plan`) and you are ready to code. + +**What you pass:** Optional implementation hints. **What the assistant does:** 1. Confirms **which** issue file applies if several exist or things are ambiguous. -2. **Branch status preflight** — `git fetch --prune`, report current branch and ahead/behind vs the default branch, warn if the current branch looks stale (leading digits point at an issue already in `02-`/`03-`) or if you are still on the default branch. -3. **Sweeps stale current issues** — moves every `issue<n>_*` group **other than the focus issue** to `.issueflows/03-solved-issues/` (if a status file contains `- [x] Done`) or `.issueflows/02-partly-solved-issues/`. -4. **Plans** the work. If you are not in plan mode, it should stop and ask you to confirm before large changes. -5. Checks the plan is **not too broad**; may suggest splitting into smaller chunks. -6. **Implements** the plan (code, tests, and updates to issue status docs as appropriate for the task). +2. **Branch status preflight** — `git fetch --prune`, report current branch and ahead/behind vs the default branch, warn if the current branch looks stale or if you are still on the default branch. +3. **Sweeps stale current issues** — moves every `issue<n>_*` group **other than the focus issue** to `.issueflows/03-solved-issues/` (done) or `.issueflows/02-partly-solved-issues/` (not done). +4. **Plan precondition** — reads `issue<N>_plan.md`. If missing, asks the user to choose: run `/issue-plan` now, proceed without a plan (note in status file), or abort. Does **not** hard-stop. +5. **Implements** the plan. Updates the status markdown as work progresses. + +**Result:** Implementation aligned with the confirmed plan and project rules (tests with `uv run`, dependency management with `uv`, etc.). + +--- + +## 4. `/issue-pause` — park work safely + +**When:** You need to stop partway through an issue (context switch, blocked on input) without closing it. -**Result:** Implementation aligned with the markdown in `.issueflows/01-current-issues/` and project rules (tests with `uv run`, dependency management with `uv`, etc.). +**What you pass:** Optional short note that becomes the **Remaining work** text. + +**What the assistant does:** + +1. Updates `issue<N>_status.md` with **Done so far**, **Remaining work**, and **Paused on** sections. The `- [ ] Done` checkbox stays unchecked. +2. Moves the whole `issue<N>_*` group from `.issueflows/01-current-issues/` to `.issueflows/02-partly-solved-issues/`. +3. Offers, as **one** consolidated prompt, a WIP commit and/or `git switch <default>`. Never deletes branches, never force-pushes, never runs tests. + +**Result:** The issue is safely archived under `02-partly-solved-issues/` with clear resume notes. Re-open via `/issue-init <N>` (which will ask for the archived-issue confirmation). --- -## 3. `/issue-close` — land the work +## 5. `/issue-close` — land the work -**When:** Implementation is done and you want to ship (commit, push, PR). +**When:** Implementation is done and you want to ship (commit, push, PR). Post-merge branch cleanup is a **separate** step — see `/issue-cleanup` below. **What you pass:** Optional notes (branch name, PR title, draft PR, or "skip issue doc update"). You can also ask for a **semver bump** in the same line, for example: @@ -89,32 +158,78 @@ The bump runs **after** tests and **before** issue-folder moves and **before** c 1. **Sanity check** — e.g. `uv run pytest`, review the diff. 2. **Optional version bump** — if requested, follow `.cursor/skills/issueflow-version-bump/SKILL.md` and run `uv version --bump …` from the project root. -3. **Issue folders** — update status markdown; use `- [x] Done` only when fully resolved. Move completed issue files from `.issueflows/01-current-issues/` to `.issueflows/03-solved-issues/`, or partly done work to `.issueflows/02-partly-solved-issues/` (see project rules). -4. **Commit** — focused staging and a clear message (include `pyproject.toml` / `uv.lock` if the bump changed them). Sync with the default branch using `git pull --ff-only` so unrelated history never sneaks in silently. +3. **Issue folders** — update status markdown; use `- [x] Done` only when fully resolved. Move completed issue files from `.issueflows/01-current-issues/` to `.issueflows/03-solved-issues/`, or partly done work to `.issueflows/02-partly-solved-issues/`. +4. **Commit** — focused staging and a clear message (include `pyproject.toml` / `uv.lock` if the bump changed them). Sync with the default branch using `git pull --ff-only`. 5. **Push** — to your usual remote (e.g. `origin`). 6. **Pull request** — open against the default branch; link the GitHub issue (`Closes #n` / `Refs #n`). -7. **Post-merge branch cleanup** — once the PR is merged, re-run `/issue-close` (or simply ask the assistant to do the cleanup). It will detect the merge, offer to `git switch <default> && git pull --ff-only && git fetch --prune`, and ask once before running `git branch -d` on every local branch already reachable from the default (including squash-merged ones). Nothing destructive happens without your yes. -8. **After review** — address comments, merge when ready; then come back for step 7. +7. **After review** — remind you the working copy is still on the issue branch; once the PR merges, run `/issue-cleanup` for the post-merge tidy-up. + +**Result:** Commit, push, PR link. No branches are deleted from `/issue-close` itself. + +--- + +## 6. `/issue-cleanup` — post-merge branch hygiene + +**When:** The PR opened by `/issue-close` has merged on GitHub. + +**What you pass:** Nothing (acts on the current branch) or an explicit branch name. + +**What the assistant does:** + +1. Detects the default branch. +2. Detects merge state via `gh pr view` (falls back to `git cherry origin/<default> <branch>` to catch squash-merges). +3. If **not merged**: reminds you to stay off the default for unrelated work and re-run after merge. +4. If **merged**: one consolidated yes/no prompt covering `git switch <default>`, `git pull --ff-only`, `git fetch --prune`, and `git branch -d` on every local branch whose tip is already reachable from the default (including squash-merged ones). Never `-D`; if `-d` refuses, reports and moves on. +5. Optional safe folder sweep: moves any `issue<N>_*` group whose status file says `- [x] Done` to `.issueflows/03-solved-issues/`. -**Result:** Short summary of commit, push, PR link, and (after merge) which local branches were deleted — or what is blocked. +**Result:** Working tree on the default, merged local branches deleted (with consent), folders tidy. + +--- + +## 7. `/issue-yolo` — all-in-one for small issues + +**When:** The change is genuinely small and low-risk (typo, one-line fix, doc tweak) and you want to skip the usual checkpoints. For anything bigger, use the individual commands. + +**Preflight (any failure aborts before the chain starts):** + +- Refuses to run on `main` / `master`. +- Refuses if `git status --porcelain` shows unrelated uncommitted changes. +- Runs `uv run pytest` up front; refuses if anything fails. +- **Single consolidated confirmation** listing the full planned chain (issue, branch, repo, downstream flags). + +**Chain:** `/issue-init` → `/issue-plan` (auto-confirmed short plan; aborts if the scope check reveals the change isn't actually small) → `/issue-start` → `uv run pytest` again → `/issue-close` (with any forwarded `bump`/`patch`/`minor`/`major`/`draft`). Does **not** run `/issue-cleanup` — the PR hasn't merged yet. + +**Result:** A commit, push, and PR ready for review — or an abort at the first ambiguity. --- ## End-to-end flow +Tip: at any point in the linear flow below, you can just run `/iflow` and it will dispatch to the right step based on current state. + ```text GitHub issue - │ /issue-init + │ /issue-init (or /iflow) ▼ -.issueflows/01-current-issues/issueN_original.md (+ optional status files) +.issueflows/01-current-issues/issueN_original.md + │ /issue-plan + ▼ +issueN_plan.md (user confirmed) │ /issue-start ▼ Code + tests (+ status updates during work) - │ /issue-close + │ /issue-close [optional: bump patch/minor/major] ▼ -Optional `uv version --bump …` → Commit → push → PR → merge +Commit → push → PR │ - └── issue docs in .issueflows/03-solved-issues/ or .issueflows/02-partly-solved-issues/ when appropriate + │ (PR merges on GitHub) + │ /issue-cleanup + ▼ +Default branch, stale local branches deleted (with single confirm) + +Detours: + /issue-pause — park mid-stream; moves issueN_* to 02-partly-solved-issues/ + /issue-yolo — chain init → plan → start → close for tiny fixes (safeguarded) ``` -The command definitions are the source of truth: `.cursor/commands/issue-init.md`, `issue-start.md`, and `issue-close.md`. The skill packages under `.cursor/skills/` repeat the same workflows for explicit invocation. This document is a readable overview only. +The command definitions under `.cursor/commands/` are the source of truth. The skill packages under `.cursor/skills/` repeat the same workflows for explicit invocation. This document is a readable overview only. diff --git a/src/issue_flow/templates/commands/iflow.md.j2 b/src/issue_flow/templates/commands/iflow.md.j2 new file mode 100644 index 0000000..2d6f328 --- /dev/null +++ b/src/issue_flow/templates/commands/iflow.md.j2 @@ -0,0 +1,75 @@ +# Smart dispatcher for the issue-flow lifecycle + +`/iflow` inspects the state of the focus issue and **dispatches** to the next logical command in the linear lifecycle — `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`. It never does work those commands don't already do; it just picks the right one for you. + +Off-path commands (`/issue-pause`, `/issue-cleanup`, `/issue-yolo`) are **not** auto-dispatched. Invoke them directly when you need them. + +## Input + +Optional free-form text after the command. `/iflow` forwards the raw trailing text to whichever command it dispatches to. Examples: + +- `/iflow` — dispatch with no extra args. +- `/iflow 42` — forwards `42` (useful when state resolves to `/issue-init`). +- `/iflow bump minor` — forwards `bump minor` (meaningful when state resolves to `/issue-close`; otherwise the downstream command will ignore or ask). +- `/iflow stick to the existing logger` — forwards the hint (useful when state resolves to `/issue-plan` or `/issue-start`). + +## Steps + +0. **Resolve the focus issue number `N`.** + - Run `git branch --show-current`. If it matches `^(\d+)-.+`, the leading digits are the **authoritative** `N`. + - List `issue<n>_*` groups in `{{ issueflows_dir }}/{{ current_issues_folder }}/`, and also check `{{ issueflows_dir }}/{{ partly_solved_folder }}/` and `{{ issueflows_dir }}/{{ solved_folder }}/` for archived groups matching `N`. + - Pick `N` using this precedence: + 1. **Branch-derived `N` wins**, regardless of whether a group for `N` exists in `{{ current_issues_folder }}/`. State A (dispatch `/issue-init`) will simply apply when no `issue<N>_*` files are present yet. If `issue<N>_*` is archived under `{{ partly_solved_folder }}/` or `{{ solved_folder }}/`, warn the user that `/issue-init`'s archived-issue guard will ask for an explicit confirmation before re-opening. + 2. Else if exactly one group exists in `{{ current_issues_folder }}/`, use that `N`. + 3. Else if there are **no** groups at all in `{{ current_issues_folder }}/` (and no branch-derived `N`), fall through to dispatch `/issue-init` (state A); `/issue-init` itself will ask for a number. + 4. Else (no branch-derived `N`, multiple groups in `{{ current_issues_folder }}/`), **stop** and ask the user which issue to act on. Do not guess. + +1. **Detect state and choose the dispatch target.** In priority order: + + | State | Condition | Dispatch to | Reason to report | + |-------|-----------|-------------|------------------| + | A | No `issue<N>_original.md` for the focus issue (or no focus issue at all) | `/issue-init` | "no `*_original.md` yet" | + | B | `issue<N>_original.md` exists, no `issue<N>_plan.md` | `/issue-plan` | "no plan file yet" | + | C | Plan exists, and either no status file or the status file does **not** contain `- [x] Done` | `/issue-start` | "plan is confirmed but status is not `- [x] Done`" | + | D | Status file contains `- [x] Done` (case-insensitive on `done`) | `/issue-close` | "status marks the issue `- [x] Done`" | + + Use the first row whose condition matches. Never dispatch to more than one command in a single `/iflow` run. + +2. **Announce and dispatch.** Before running the downstream command, print one line explaining the decision, for example: + + ``` + /iflow -> /issue-plan (issue #42: no plan file yet) + ``` + + Then run the exact same logic as the chosen command (follow `{{ agent_dir }}/commands/<command>.md`), forwarding the user's trailing text verbatim. The downstream command keeps all its own checkpoints (plan confirmation in `/issue-plan`, `/issue-start`'s soft stop when the plan is missing, `/issue-close`'s unrelated-changes prompt, etc.). + +3. **Report.** Summary should include: + - the focus issue number and how it was resolved (branch-derived / only group / user-specified) + - which command was dispatched to and why + - the downstream command's own output + - a one-line hint when an **off-path** command is the natural next step, e.g.: + - state **D** + PR likely merged → "after the PR merges, run `/issue-cleanup`" + - mid-stream context switch needed → "to park this work, run `/issue-pause`" + - tiny fix you want in one shot → "consider `/issue-yolo` next time" + +## Constraints + +- `/iflow` never skips a downstream command's own prompts. If the downstream step asks a question, surface it normally. +- `/iflow` never auto-dispatches to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. Those are explicit choices only. +- If the focus issue cannot be resolved (multiple active issues, branch ambiguous), stop and ask. Do not pick one silently. +- Do not modify files beyond what the downstream command would normally modify. `/iflow` itself writes nothing. + +## Example invocations + +- `/iflow` on a fresh branch `42-fix-login` with no `issueflows` files yet + -> dispatches to `/issue-init` which infers `#42` from the branch. +- `/iflow` on branch `42-fix-login` while unrelated groups (`issue99_*`, `issue100_*`) still sit in `{{ current_issues_folder }}/` + -> branch-derived `N=42` wins; dispatches to `/issue-init` (state A) which captures #42 and archives the other groups per its own sweep. +- `/iflow` after `/issue-init 42` has run and `issue42_original.md` exists, but there is no `issue42_plan.md` yet + -> dispatches to `/issue-plan`. +- `/iflow` after `/issue-plan` has written and the user confirmed `issue42_plan.md`, with no `issue42_status.md` or an unchecked `- [ ] Done` + -> dispatches to `/issue-start`. +- `/iflow bump patch` after implementation is done and `issue42_status.md` contains `- [x] Done` + -> dispatches to `/issue-close bump patch`. +- `/iflow` on branch `99-partial-work` after the issue was paused (files now under `{{ partly_solved_folder }}/`) + -> branch-derived `N=99` wins; dispatches to `/issue-init` (state A since `01-` has no `issue99_*`). `/iflow` warns up front that `/issue-init`'s archived-issue guard will trigger and ask for explicit confirmation before restoring the group to `{{ current_issues_folder }}/`. diff --git a/src/issue_flow/templates/commands/issue-cleanup.md.j2 b/src/issue_flow/templates/commands/issue-cleanup.md.j2 new file mode 100644 index 0000000..3b7b183 --- /dev/null +++ b/src/issue_flow/templates/commands/issue-cleanup.md.j2 @@ -0,0 +1,60 @@ +# Post-merge branch and folder cleanup + +Run this after a PR has been merged (typically the one opened by `/issue-close`). It detects the merge, switches back to the default branch, and — with a **single consolidated confirm** — deletes every local branch whose commits are already in the default branch (including squash-merged branches). + +`/issue-cleanup` is the only command in the workflow that touches local branches destructively (via `git branch -d`, never `-D`). + +## Input + +Optional free-form text after the command. Examples: + +- **No extra text** — detect the current branch's PR, clean that up, plus any other local branches already merged into the default. +- A branch name — clean up that specific branch instead of the current one (e.g. `/issue-cleanup 42-fix-login`). + +## Steps + +1. **Detect the default branch.** + - Prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. + - Else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`. + - Else fall back to `main`. + +2. **Identify the branch to clean.** + - If the user passed a branch name, use it. + - Else use the current branch (`git branch --show-current`). If the current branch **is** the default, skip to step 5 (group sweep only); there is no issue branch to merge-check. + +3. **Check PR / merge state for the issue branch.** + - Prefer `gh pr view <branch> --json state,mergedAt,mergeCommit,headRefName`. + - If `gh` is unavailable, approximate with `git fetch --prune` then `git cherry origin/<default> <branch>`. All commits prefixed `-` means the branch is effectively merged (including squash-merges). + - **If the branch is not merged:** + - Remind the user that the working copy is still on the issue branch (not the default). Suggest `git switch <default>` before starting unrelated work. + - Tell them to re-run `/issue-cleanup` after the PR merges. + - **Stop.** Do not delete anything. + +4. **Consolidated confirm for post-merge cleanup.** Gather the full action list into a single yes/no prompt: + - `git switch <default>` + - `git pull --ff-only` + - `git fetch --prune` + - List **every** local branch whose tip is already reachable from `origin/<default>` (use `git for-each-ref --format='%(refname:short)' refs/heads/` combined with `git cherry origin/<default> <branch>` to catch squash-merges). Present the list explicitly and ask **once** before running `git branch -d <branch>` for each. + - Never use `-D` automatically. If `-d` refuses (unmerged changes), report that branch and leave it alone. + +5. **Optional folder sweep** (safe; no destructive git). + - Group files in `{{ issueflows_dir }}/{{ current_issues_folder }}/` by issue number (`issue<N>_*`). + - For each group whose status file contains `- [x] Done` (case-insensitive on `done`), move the group to `{{ issueflows_dir }}/{{ solved_folder }}/`. + - Groups without a checked `Done` stay put — do not route them to `{{ issueflows_dir }}/{{ partly_solved_folder }}/` from `/issue-cleanup`. That is `/issue-pause`'s job and requires a human decision. + +## Output + +Report: +- default branch resolved +- PR/merge status of the target branch +- post-merge commands run (if any) +- list of local branches deleted (with `-d` output per branch) +- list of branches skipped because `-d` refused, with a one-line reason +- folder sweep summary (`issue<N>` → `{{ solved_folder }}/`, or "nothing to sweep") + +## Constraints + +- Never use `git branch -D` or `git push --force`. +- Never delete the default branch. +- If anything is ambiguous (detached HEAD, multiple remotes, missing tracking info), report and stop rather than guessing. +- Do not open or update PRs. Do not bump versions. Those belong to `/issue-close`. diff --git a/src/issue_flow/templates/commands/issue-close.md.j2 b/src/issue_flow/templates/commands/issue-close.md.j2 index 088b438..8d19dc4 100644 --- a/src/issue_flow/templates/commands/issue-close.md.j2 +++ b/src/issue_flow/templates/commands/issue-close.md.j2 @@ -1,6 +1,6 @@ # Close out the current issue -Run this when implementation is done and you are ready to land the work. +Run this when implementation is done and you are ready to land the work (commit, push, PR). Post-merge branch hygiene now lives in a separate command, `/issue-cleanup`, which you run **after** the PR is merged. ## Input @@ -10,7 +10,7 @@ Optional text after the command (same line). Examples: - **`bump`** or **`patch`** — bump the **patch** semver (e.g. `1.2.0` → `1.2.1`). - **`bump minor`** or **`minor`** — bump **minor**. - **`bump major`** or **`major`** — bump **major**. -- **Free text** — if it clearly asks to release or bump the version, infer `patch`, `minor`, or `major` from wording (e.g. “bugfix release” → patch); if unclear, ask once. +- **Free text** — if it clearly asks to release or bump the version, infer `patch`, `minor`, or `major` from wording (e.g. "bugfix release" → patch); if unclear, ask once. Other optional notes still apply: branch name, PR title, draft PR, skip issue doc update, commit all changes, etc. @@ -43,19 +43,11 @@ Other optional notes still apply: branch name, PR title, draft PR, skip issue do - Open a PR against the default branch (e.g. `main`). - Describe the change, how to test it, and link the GitHub issue (e.g. `Closes #123` or `Refs #123` in the PR body). -7. **Post-merge branch cleanup** - - Detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`; fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`, else `main`). - - Detect merge status of this PR with `gh pr view <branch> --json state,mergedAt,mergeCommit,headRefName`. If `gh` is unavailable, approximate with `git fetch --prune` followed by `git rev-list <branch>..origin/<default>` and `git cherry origin/<default> <branch>` (all commits marked `-` means squash-merged). - - **If the PR is merged:** - - Ask once whether to run the standard post-merge cleanup. On yes, run: `git switch <default> && git pull --ff-only && git fetch --prune`. These are non-destructive because the issue branch is already merged. - - List local branches whose tip is already reachable from `origin/<default>` (including squash-merged ones detected via `git cherry`). Present them as a single group and ask **once** (one consolidated yes/no listing every branch) before running `git branch -d <branch>` for each. Never use `-D` automatically; if `-d` refuses, report the branch and stop touching it. - - **If the PR is not yet merged:** - - Remind the user that the working copy is still on the issue branch, not the default branch. Suggest `git switch <default>` before starting any unrelated work so new changes don't accidentally land on the issue branch. Tell them to re-run `/issue-close` after the PR merges so the post-merge cleanup actually runs. - -8. **After review** +7. **After review** - Address feedback, push updates, and merge when approved and CI is green. - - Re-run `/issue-close` after the merge to pick up the post-merge cleanup in step 7. + - Remind the user that the working copy is still on the issue branch, not the default. Suggest `git switch <default>` before starting unrelated work so new changes don't accidentally land on the issue branch. + - Once the PR is merged, run **`/issue-cleanup`** to switch back to the default branch, `git pull --ff-only`, `git fetch --prune`, and delete local branches whose commits are already in the default branch (single consolidated confirm). `/issue-close` no longer does post-merge cleanup itself. ## Output -Summarize what was committed, pushed, and the PR URL (or next step if blocked). +Summarize what was committed, pushed, and the PR URL (or next step if blocked). Remind the user to run `/issue-cleanup` after the PR merges. diff --git a/src/issue_flow/templates/commands/issue-pause.md.j2 b/src/issue_flow/templates/commands/issue-pause.md.j2 new file mode 100644 index 0000000..e32bf6b --- /dev/null +++ b/src/issue_flow/templates/commands/issue-pause.md.j2 @@ -0,0 +1,57 @@ +# Pause work on the current issue + +Use this when you need to switch context without closing the issue. The command records where things stand, archives the issue group into `{{ issueflows_dir }}/{{ partly_solved_folder }}/`, and (optionally) parks your working tree safely. + +## Input + +Optional free-form text after the command. Examples: + +- **No extra text** — update status from what is visible in the diff and current TODOs. +- Short note — used as the "Remaining work" text verbatim (e.g. `/issue-pause waiting on design input from @user`). + +## Steps + +0. **Locate the focus issue.** In `{{ issueflows_dir }}/{{ current_issues_folder }}/`, identify the `issue<N>_*` group (original, plan, status). If multiple groups exist and the focus is ambiguous, ask which one to pause. If there is none, **stop** and tell the user there is nothing to pause. + +1. **Update the status file.** Create or update `issue<N>_status.md` in `{{ issueflows_dir }}/{{ current_issues_folder }}/` with: + + ```markdown + # Status for issue #<N>: <title> + + - [ ] Done + + ## Done so far + - Short bullets of what has landed or been tried. + + ## Remaining work + - Explicit next steps so a future you (or someone else) can resume. + + ## Paused on + <date, branch name, and any blockers — e.g. waiting on external input, blocked on another PR>. + ``` + + Keep any earlier content the user wrote; only add / update the sections above. The `- [ ] Done` checkbox **must** remain unchecked — a pause is not a close. + +2. **Move the issue group.** Move every `issue<N>_*` file from `{{ issueflows_dir }}/{{ current_issues_folder }}/` to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. Report the moves. + +3. **Working-tree guard.** Run `git status --porcelain`. Report what is dirty. Then offer, as **one** consolidated prompt, up to three actions and let the user pick any combination: + - **WIP commit** — stage all tracked changes and commit with a message like `WIP: pause issue #<N> — <short note>`. Never silently include untracked files; list them and ask. + - **Switch to default branch** — detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`) and run `git switch <default>` (only after the WIP commit if the tree is dirty, to avoid `git switch` refusing). + - **Stay put** — leave branch and working tree untouched. + +4. **Branch hygiene note (non-destructive).** Tell the user the issue branch (typically `<N>-<slug>`) is still on the remote / local list. `/issue-pause` never deletes branches — resume with `/issue-init <N>` (or just check out the branch) when ready to continue. The matching folder is now `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. + +## Output + +Report: +- status file path updated +- issue group moves (source → destination) +- working-tree actions taken (WIP commit SHA, branch switched to, or "left as-is") +- next step hint (e.g. "to resume: `/issue-init <N>` will pull the group back into `{{ issueflows_dir }}/{{ current_issues_folder }}/` after confirming the archived-issue guard") + +## Constraints + +- The focus issue's `- [ ] Done` checkbox must stay unchecked. `/issue-pause` is **not** `/issue-close`. +- Do not delete branches. Do not `git reset` or `git stash drop`. +- Do not open a PR. Do not bump the version. Those are `/issue-close`. +- Do not run tests. Pausing should be quick. diff --git a/src/issue_flow/templates/commands/issue-plan.md.j2 b/src/issue_flow/templates/commands/issue-plan.md.j2 new file mode 100644 index 0000000..625deb0 --- /dev/null +++ b/src/issue_flow/templates/commands/issue-plan.md.j2 @@ -0,0 +1,80 @@ +# Plan the current issue before any code is written + +The issue should already be captured in `{{ issueflows_dir }}/{{ current_issues_folder }}/issue<N>_original.md` (via `/issue-init`). Use this command to write a structured plan and get explicit user confirmation **before** `/issue-start` starts editing code. + +## Input + +Optional free-form text after the command. Examples: + +- **No extra text** — plan the focus issue using the `*_original.md` alone. +- Short constraints / design hints (e.g. "stick to the existing logger", "split into two PRs") — incorporate into the plan. + +## Steps + +0. **Locate the focus issue.** In `{{ issueflows_dir }}/{{ current_issues_folder }}/`, find the `issue<N>_original.md`. If there is none, or multiple ambiguous groups, **stop** and ask the user. Suggest running `/issue-init` first. + +0.5 **Branch status preflight** (non-destructive — report, do not delete). + - Detect the default branch: `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. If `gh` is unavailable, fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`, else `main`. + - Run `git fetch --prune`. + - Report current branch, clean/dirty working tree (`git status --porcelain`), and ahead/behind counts vs `origin/<default>` (`git rev-list --left-right --count origin/<default>...HEAD`). + - If on the default branch, note it and suggest creating an issue branch (`git switch -c <N>-<short-slug>`) — do **not** auto-run it. Planning itself does not require a branch switch. + +1. **Read the issue.** Load `issue<N>_original.md` and any existing `issue<N>_status.md`. Do not rewrite them from this command. + +2. **Explore, then propose.** Do enough read-only research (search, read files, check existing tests) to design the change. Keep it proportional to the issue — small fix = short plan. + +3. **Write `issue<N>_plan.md`** in `{{ issueflows_dir }}/{{ current_issues_folder }}/` with these sections: + + ```markdown + # Plan for issue #<N>: <title> + + ## Goal + One or two sentences on the desired outcome. + + ## Constraints + - Project rules, coding standards, back-compat, scope limits. + + ## Approach + Concrete design: data flow, affected modules, ordering of steps. + + ## Files to touch + - path/one.py — what changes + - path/two.md.j2 — what changes + + ## Test strategy + - Existing tests to re-run (e.g. `uv run pytest`) + - New tests or manual checks + + ## Open questions + - Anything that needs the user's call before coding. + ``` + + Keep it terse but specific. Link to files with markdown links when helpful. + +4. **Scope check.** If the plan is broad (many unrelated files, several independent deliverables, or mixes refactors with feature work), **propose splitting** into smaller issues or phased PRs and ask the user before continuing. + +5. **Confirm with the user.** Present the plan and **stop**. Ask one of: + - **Accept** — the plan is good; next run `/issue-start` to implement it. + - **Revise** — call out sections to change; update `issue<N>_plan.md` in place and re-confirm. + - **Abort** — remove `issue<N>_plan.md` (with the user's OK) or leave it as a draft. + + Do **not** proceed to implementation from `/issue-plan`. That belongs to `/issue-start`. + +6. **If `issue<N>_plan.md` already exists.** Do not overwrite silently. Ask whether to: + - **Update in place** (replace after the user sees the new content). + - **Keep both** (append a timestamped suffix like `issue<N>_plan.v2.md`). + - **Leave as is**. + +## Output + +Report: +- path written (e.g. `{{ issueflows_dir }}/{{ current_issues_folder }}/issue<N>_plan.md`) +- brief summary of the plan +- whether the user has confirmed, revised, or paused on it +- any branch preflight warnings + +## Constraints + +- Read-only on source code; `/issue-plan` writes **only** the plan file under `{{ issueflows_dir }}/{{ current_issues_folder }}/`. +- Do not move files between `01-` / `02-` / `03-` folders from `/issue-plan`. +- Do not run tests or package managers; that is `/issue-start` and `/issue-close`. diff --git a/src/issue_flow/templates/commands/issue-start.md.j2 b/src/issue_flow/templates/commands/issue-start.md.j2 index 8468c0a..d515654 100644 --- a/src/issue_flow/templates/commands/issue-start.md.j2 +++ b/src/issue_flow/templates/commands/issue-start.md.j2 @@ -1,13 +1,16 @@ -# Start working with current issue +# Start working with the current issue -The issue should already be explained in a markdown file in `{{ issueflows_dir }}/{{ current_issues_folder }}`. +`/issue-start` **implements** the plan that `/issue-plan` already wrote. Planning itself now lives in `/issue-plan` — this command no longer produces the plan. + +The issue should already be explained in `{{ issueflows_dir }}/{{ current_issues_folder }}/issue<N>_original.md` (from `/issue-init`) and a confirmed plan in `issue<N>_plan.md` (from `/issue-plan`). ## Input -If additional input is added, use that for further detailed guidance + +If additional input is added, use that as implementation hints (scope, constraints, design preferences). It does **not** replace the plan file — update `issue<N>_plan.md` via `/issue-plan` first if the plan itself needs to change. ## Steps -0. If the issue markdown file is not present, or it is ambiguous which one to select, ask. Could it be that the user has not run the /issue-init command? +0. **Find the focus issue.** Look in `{{ issueflows_dir }}/{{ current_issues_folder }}/` for `issue<N>_original.md`. If missing or multiple groups are ambiguous, ask. Could the user have skipped `/issue-init`? 0.5 **Branch status preflight** (non-destructive — report, do not delete). - Detect the default branch: `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`. If `gh` is unavailable, use `git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's|^origin/||'`, else fall back to `main`. @@ -26,8 +29,21 @@ If additional input is added, use that for further detailed guidance - Never move the focus issue's own files. - Report every move (source -> destination, grouped by issue number) in the opening summary. -1. Plan. If not in plan mode, stop and ask for a confirmation. +1. **Plan precondition.** Look for `issue<N>_plan.md` in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. + - **Plan file present:** read it. Treat it as the source of truth for scope and approach. Continue to step 2. + - **Plan file missing:** **do not hard-stop.** Ask the user: + > "No plan file found for issue #N. How should I proceed? + > (a) Run `/issue-plan` now, then continue into implementation once you confirm the plan. + > (b) Proceed without a plan — I'll implement directly and note the skipped plan in the status file. + > (c) Abort." + Wait for an explicit choice. On **(a)**, run the `/issue-plan` flow first (including its user-confirmation stop), then return here. On **(b)**, add a short `- Skipped /issue-plan on <date>` note to `issue<N>_status.md` and continue. On **(c)**, stop. + +2. **Implement** the plan. Prefer minimal, focused diffs. Match existing code style and tooling. Follow project rules under `{{ agent_dir }}/rules/issueflow-rules.mdc` (e.g. `uv run` for Python, `uv add` / `uv remove` / `uv sync` for dependencies). + +3. **Update the status file.** After meaningful progress, update (or create) `issue<N>_status.md` under `{{ issueflows_dir }}/{{ current_issues_folder }}/` with a `- [ ] Done` checkbox that stays unchecked until fully resolved. Record what has landed and what remains so `/issue-pause` or `/issue-close` has accurate context. + +4. **Hand off.** When the implementation is ready to ship, tell the user to run `/issue-close` (optionally with `bump`/`patch`/`minor`/`major`). Parking work mid-stream goes through `/issue-pause`. -2. Check that the plan is not too broad. If too broad, ask if it should be split into several parts. +## Output -3. Implement the steps of the plan +Summarize what was implemented, how it matches the plan, what remains, and any branch / sweep warnings surfaced during the preflight. diff --git a/src/issue_flow/templates/commands/issue-yolo.md.j2 b/src/issue_flow/templates/commands/issue-yolo.md.j2 new file mode 100644 index 0000000..12825a1 --- /dev/null +++ b/src/issue_flow/templates/commands/issue-yolo.md.j2 @@ -0,0 +1,71 @@ +# All-in-one flow for small, low-risk issues + +Chains `/issue-init` → `/issue-plan` → `/issue-start` → `/issue-close` in a single run, with safeguards. Use this **only** for minor fixes, docs tweaks, and similar low-risk changes where a full plan/review loop would be overkill. + +For anything non-trivial, use the individual commands so you get confirmation checkpoints. + +## Input + +Same as `/issue-init` (issue number, URL, or empty to infer from the branch). Optional extra tokens are forwarded to the downstream commands: + +- `bump` / `patch` / `minor` / `major` — forwarded to `/issue-close` for the version bump. +- `draft` — open a draft PR in `/issue-close`. +- Free-form notes — used as the plan/commit context. + +## Preflight (abort on any failure) + +1. **Refuse on default branch.** If the current branch is `main` / `master` / the detected default, **stop** and tell the user to create or switch to an issue branch first. Do not offer to create one silently from `/issue-yolo`. + +2. **Refuse with dirty unrelated changes.** Run `git status --porcelain`. If anything is uncommitted that is **not** obviously related to the target issue (ask the user if it's not clear), **stop**. Suggest committing / stashing first. + +3. **Tests must pass up front.** Run `uv run pytest` (or the repo's documented test command). If any test fails, **stop** before the chain starts. Yolo never ships on a broken baseline. + +4. **Single consolidated confirm.** Present the full planned chain explicitly, for example: + + ``` + /issue-yolo will run, without further prompts: + + 1. /issue-init 123 + 2. /issue-plan (auto-confirmed, short plan) + 3. /issue-start (implement directly) + 4. /issue-close patch (tests, bump, commit, push, PR) + + Target branch: 123-fix-typo + Repo: owner/repo + + Proceed? [y/N] + ``` + + Require an explicit yes. Any other input aborts. + +## Chain + +Once the preflight has passed and the user confirmed: + +1. **`/issue-init`** — capture the issue (or skip if the `*_original.md` already exists for the focus issue). + +2. **`/issue-plan`** — auto-confirmed. Write a **short** `issue<N>_plan.md` (Goal + Approach + Files to touch + Test strategy). Do not stop for user confirmation; the consolidated confirm above already covered it. If the scope check reveals the change is not actually small (touches many unrelated files, mixes refactors, etc.), **abort** the yolo chain and tell the user to run the commands individually. + +3. **`/issue-start`** — implement the plan. No additional plan-mode prompt. + +4. **Tests again.** Re-run `uv run pytest`. If anything fails, **stop**. Do not commit, push, or open a PR. Tell the user what failed and point at the work in progress. + +5. **`/issue-close`** — run the full close flow (optional version bump if the user passed `bump`/`patch`/`minor`/`major`, issue-folder update, commit, push, PR). Do **not** chain `/issue-cleanup` automatically — the PR has not merged yet. + +## Post-run + +- Leave the user on the issue branch with the PR URL. +- Remind them to re-run `/issue-cleanup` once the PR is merged. + +## Output + +Report, in order: +- preflight results (default-branch check, dirty tree check, initial test run) +- which downstream commands ran and where they stopped (if any) +- final state: commit SHA, PR URL (or reason the chain aborted) + +## Constraints + +- Do not override any of the downstream commands' own constraints (no `-D` in `/issue-cleanup`, etc.). `/issue-yolo` is a chain, not a free pass. +- If **any** downstream step requires a decision (unrelated changes in `git status`, ambiguous version bump, merge conflict, failed test), **stop** and hand back to the user. Never paper over a prompt just because the chain is running. +- Never run `/issue-cleanup` from `/issue-yolo`. Branch deletion always needs the user to see the merged PR first. diff --git a/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 b/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 index 13ace50..020a477 100644 --- a/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 +++ b/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 @@ -1,12 +1,19 @@ # Cursor issue workflow (slash commands) -This repo uses three Cursor **slash commands** under `{{ agent_dir }}/commands/` that line up with how we track GitHub issues in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. Use them in order when you pick up work from GitHub and want the assistant to follow the same steps. +This repo uses eight Cursor **slash commands** under `{{ agent_dir }}/commands/` that line up with how we track GitHub issues in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. + +**Quick start: just run `/iflow`.** It inspects the state of the focus issue and dispatches to the right linear-flow command (`/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`) — so you don't have to remember which step is next. | Command | File | Role | |--------|------|------| +| `/iflow` | `iflow.md` | **Smart dispatcher.** Detect current state and run `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close` automatically. Never auto-dispatches to pause / cleanup / yolo. | | `/issue-init` | `issue-init.md` | Pull an issue from GitHub into the repo as a local markdown file and tidy older current issues. | -| `/issue-start` | `issue-start.md` | Plan and implement the work described in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. | +| `/issue-plan` | `issue-plan.md` | Write a structured `issue<N>_plan.md` and get explicit user confirmation before any code is touched. | +| `/issue-start` | `issue-start.md` | Implement the confirmed plan (no planning step of its own any more). | +| `/issue-pause` | `issue-pause.md` | Park work safely: update status, move the issue group to `{{ partly_solved_folder }}/`, optional WIP commit and branch switch. | | `/issue-close` | `issue-close.md` | Finish: tests, optional semver bump (`uv version --bump …`), issue-folder housekeeping, commit, push, PR. | +| `/issue-cleanup` | `issue-cleanup.md` | Post-merge hygiene: switch to default, `git pull --ff-only`, `git fetch --prune`, delete merged local branches (single consolidated confirm). | +| `/issue-yolo` | `issue-yolo.md` | All-in-one for small, low-risk issues: chains `init → plan → start → close` with up-front safeguards and a single confirmation. | --- @@ -16,9 +23,14 @@ This repo uses three Cursor **slash commands** under `{{ agent_dir }}/commands/` | Skill folder | Invoke (examples) | Role | |--------------|-------------------|------| -| `issueflow-issue-init` | `/issueflow-issue-init` or attach `@issueflow-issue-init` | Same flow as `/issue-init` (resolve reference, `gh`, archive, write `*_original.md`). | -| `issueflow-issue-start` | `/issueflow-issue-start` | Plan + confirmation + scope + implement from `{{ issueflows_dir }}/{{ current_issues_folder }}/`. | +| `issueflow-iflow` | `/issueflow-iflow` or attach `@issueflow-iflow` | Smart dispatcher — same state machine as `/iflow`. | +| `issueflow-issue-init` | `/issueflow-issue-init` or attach `@issueflow-issue-init` | Same flow as `/issue-init`. | +| `issueflow-issue-plan` | `/issueflow-issue-plan` | Same flow as `/issue-plan` (write & confirm plan). | +| `issueflow-issue-start` | `/issueflow-issue-start` | Read the plan, implement from `{{ issueflows_dir }}/{{ current_issues_folder }}/`. | +| `issueflow-issue-pause` | `/issueflow-issue-pause` | Update status, move issue group to `{{ partly_solved_folder }}/`, optional WIP commit + branch switch. | | `issueflow-issue-close` | `/issueflow-issue-close` | Tests, optional bump, status checkboxes, move issue docs, commit, push, PR. | +| `issueflow-issue-cleanup` | `/issueflow-issue-cleanup` | Post-merge cleanup (single consolidated confirm, never `-D`). | +| `issueflow-issue-yolo` | `/issueflow-issue-yolo` | Chain `init → plan → start → close` with safeguards. | | `issueflow-version-bump` | `@issueflow-version-bump` (often used from `/issue-close`) | Bump `[project]` version in `pyproject.toml` via `uv version --bump patch|minor|major`. | Each skill sets `disable-model-invocation: true` so it is included when you **explicitly** invoke it, not on every chat. See [Agent Skills](https://cursor.com/docs/context/skills) in the Cursor docs. @@ -29,10 +41,33 @@ Each skill sets `disable-model-invocation: true` so it is included when you **ex Two recurring pain points the commands actively help with: -- **Stale local branches that look "several commits ahead of main" after a squash-merged PR.** `/issue-close` detects merge status via `gh pr view`, and once the PR is merged it offers (with one consolidated confirm) to switch back to the default branch, `git pull --ff-only`, `git fetch --prune`, and run `git branch -d` on every local branch whose commits are already in the default branch (including squash-merged ones). Destructive flags like `-D` are never used automatically. +- **Stale local branches that look "several commits ahead of main" after a squash-merged PR.** `/issue-cleanup` detects merge status via `gh pr view`, and once the PR is merged it offers (with one consolidated confirm) to switch back to the default branch, `git pull --ff-only`, `git fetch --prune`, and run `git branch -d` on every local branch whose commits are already in the default branch (including squash-merged ones). Destructive flags like `-D` are never used automatically. `/issue-close` no longer performs this step itself. - **Left-overs in `{{ issueflows_dir }}/{{ current_issues_folder }}/`.** Both `/issue-init` (when a new issue is captured) and `/issue-start` (before implementation begins) sweep that folder: every `issue<n>_*` group **other than the focus issue** is moved automatically to `{{ issueflows_dir }}/{{ solved_folder }}/` if a status file contains `- [x] Done`, otherwise to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. -All three commands also run a short **branch-status preflight**: `git fetch --prune`, current branch, ahead/behind vs the default branch, and a warning when the current branch's leading digits refer to an issue already archived in `02-`/`03-`. +All the commands that touch git also run a short **branch-status preflight**: `git fetch --prune`, current branch, ahead/behind vs the default branch, and a warning when the current branch's leading digits refer to an issue already archived in `02-`/`03-`. + +--- + +## 0. `/iflow` — smart dispatcher (quick start) + +**When:** Any time you want the next right step without remembering which specific command applies. + +**What you pass:** Nothing, or the same arguments the target command would take (e.g. `/iflow 42` on a fresh branch, `/iflow bump minor` when the issue is done). `/iflow` forwards the trailing text verbatim. + +**How it decides:** + +| State of the focus issue | Dispatches to | +|--------------------------|---------------| +| No `issue<N>_original.md` (or no focus issue yet) | `/issue-init` | +| `original` exists, no `issue<N>_plan.md` | `/issue-plan` | +| Plan exists, status file missing or `- [ ] Done` | `/issue-start` | +| Status file contains `- [x] Done` | `/issue-close` | + +**Focus-issue resolution:** prefer the leading digits of the current branch when it matches `^<N>-.+`; else the single group in `{{ issueflows_dir }}/{{ current_issues_folder }}/`; else ask. + +**Not auto-dispatched:** `/issue-pause`, `/issue-cleanup`, and `/issue-yolo`. `/iflow` will mention them in its output when relevant (e.g. "after the PR merges, run `/issue-cleanup`") but never picks them for you. + +**Result:** One of the four linear commands runs, with its own normal checkpoints intact. --- @@ -49,32 +84,66 @@ All three commands also run a short **branch-status preflight**: `git fetch --pr - **Archive:** Other files already in `{{ issueflows_dir }}/{{ current_issues_folder }}/` (grouped by issue number, e.g. `issue121_*`) may be **moved** to `{{ issueflows_dir }}/{{ partly_solved_folder }}/` or `{{ issueflows_dir }}/{{ solved_folder }}/`, based on whether a status file for that issue contains a checked **Done** line (`- [x] Done`). The new issue's files are never moved as part of this step. - If the target `issue<number>_original.md` already exists, the assistant should not overwrite it without asking. -**Result:** One canonical "original issue" file under `{{ issueflows_dir }}/{{ current_issues_folder }}/` plus optional archive moves. You can add or edit a separate `issue<number>_status.md` (or similar) by hand or with the assistant as work progresses. +**Result:** One canonical "original issue" file under `{{ issueflows_dir }}/{{ current_issues_folder }}/` plus optional archive moves. --- -## 2. `/issue-start` — plan and implement +## 2. `/issue-plan` — design the approach -**When:** The issue is represented in `{{ issueflows_dir }}/{{ current_issues_folder }}/` (at minimum the `*_original.md` file) and you are ready to code. +**When:** The issue is captured (`*_original.md` exists) and you want a confirmed plan **before** any code changes. -**What you pass:** Optional extra instructions (scope, constraints, design preferences). +**What you pass:** Optional free-form hints (constraints, design preferences). + +**What the assistant does:** + +1. Finds the focus issue in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. +2. Runs the branch-status preflight (non-destructive). +3. Reads the original issue and any prior status. +4. Writes **`issue<N>_plan.md`** with sections: **Goal**, **Constraints**, **Approach**, **Files to touch**, **Test strategy**, **Open questions**. +5. Runs a scope check — if the change is broad, proposes splitting into smaller issues or phases. +6. **Stops and asks for explicit confirmation**: accept, revise, or abort. `/issue-plan` never implements code itself. + +**Result:** A confirmed `issue<N>_plan.md` ready for `/issue-start` to execute. + +--- + +## 3. `/issue-start` — implement the plan + +**When:** The issue has a confirmed `issue<N>_plan.md` (from `/issue-plan`) and you are ready to code. + +**What you pass:** Optional implementation hints. **What the assistant does:** 1. Confirms **which** issue file applies if several exist or things are ambiguous. -2. **Branch status preflight** — `git fetch --prune`, report current branch and ahead/behind vs the default branch, warn if the current branch looks stale (leading digits point at an issue already in `02-`/`03-`) or if you are still on the default branch. -3. **Sweeps stale current issues** — moves every `issue<n>_*` group **other than the focus issue** to `{{ issueflows_dir }}/{{ solved_folder }}/` (if a status file contains `- [x] Done`) or `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. -4. **Plans** the work. If you are not in plan mode, it should stop and ask you to confirm before large changes. -5. Checks the plan is **not too broad**; may suggest splitting into smaller chunks. -6. **Implements** the plan (code, tests, and updates to issue status docs as appropriate for the task). +2. **Branch status preflight** — `git fetch --prune`, report current branch and ahead/behind vs the default branch, warn if the current branch looks stale or if you are still on the default branch. +3. **Sweeps stale current issues** — moves every `issue<n>_*` group **other than the focus issue** to `{{ issueflows_dir }}/{{ solved_folder }}/` (done) or `{{ issueflows_dir }}/{{ partly_solved_folder }}/` (not done). +4. **Plan precondition** — reads `issue<N>_plan.md`. If missing, asks the user to choose: run `/issue-plan` now, proceed without a plan (note in status file), or abort. Does **not** hard-stop. +5. **Implements** the plan. Updates the status markdown as work progresses. + +**Result:** Implementation aligned with the confirmed plan and project rules (tests with `uv run`, dependency management with `uv`, etc.). + +--- + +## 4. `/issue-pause` — park work safely + +**When:** You need to stop partway through an issue (context switch, blocked on input) without closing it. -**Result:** Implementation aligned with the markdown in `{{ issueflows_dir }}/{{ current_issues_folder }}/` and project rules (tests with `uv run`, dependency management with `uv`, etc.). +**What you pass:** Optional short note that becomes the **Remaining work** text. + +**What the assistant does:** + +1. Updates `issue<N>_status.md` with **Done so far**, **Remaining work**, and **Paused on** sections. The `- [ ] Done` checkbox stays unchecked. +2. Moves the whole `issue<N>_*` group from `{{ issueflows_dir }}/{{ current_issues_folder }}/` to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. +3. Offers, as **one** consolidated prompt, a WIP commit and/or `git switch <default>`. Never deletes branches, never force-pushes, never runs tests. + +**Result:** The issue is safely archived under `{{ partly_solved_folder }}/` with clear resume notes. Re-open via `/issue-init <N>` (which will ask for the archived-issue confirmation). --- -## 3. `/issue-close` — land the work +## 5. `/issue-close` — land the work -**When:** Implementation is done and you want to ship (commit, push, PR). +**When:** Implementation is done and you want to ship (commit, push, PR). Post-merge branch cleanup is a **separate** step — see `/issue-cleanup` below. **What you pass:** Optional notes (branch name, PR title, draft PR, or "skip issue doc update"). You can also ask for a **semver bump** in the same line, for example: @@ -89,32 +158,78 @@ The bump runs **after** tests and **before** issue-folder moves and **before** c 1. **Sanity check** — e.g. `uv run pytest`, review the diff. 2. **Optional version bump** — if requested, follow `{{ agent_dir }}/skills/issueflow-version-bump/SKILL.md` and run `uv version --bump …` from the project root. -3. **Issue folders** — update status markdown; use `- [x] Done` only when fully resolved. Move completed issue files from `{{ issueflows_dir }}/{{ current_issues_folder }}/` to `{{ issueflows_dir }}/{{ solved_folder }}/`, or partly done work to `{{ issueflows_dir }}/{{ partly_solved_folder }}/` (see project rules). -4. **Commit** — focused staging and a clear message (include `pyproject.toml` / `uv.lock` if the bump changed them). Sync with the default branch using `git pull --ff-only` so unrelated history never sneaks in silently. +3. **Issue folders** — update status markdown; use `- [x] Done` only when fully resolved. Move completed issue files from `{{ issueflows_dir }}/{{ current_issues_folder }}/` to `{{ issueflows_dir }}/{{ solved_folder }}/`, or partly done work to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. +4. **Commit** — focused staging and a clear message (include `pyproject.toml` / `uv.lock` if the bump changed them). Sync with the default branch using `git pull --ff-only`. 5. **Push** — to your usual remote (e.g. `origin`). 6. **Pull request** — open against the default branch; link the GitHub issue (`Closes #n` / `Refs #n`). -7. **Post-merge branch cleanup** — once the PR is merged, re-run `/issue-close` (or simply ask the assistant to do the cleanup). It will detect the merge, offer to `git switch <default> && git pull --ff-only && git fetch --prune`, and ask once before running `git branch -d` on every local branch already reachable from the default (including squash-merged ones). Nothing destructive happens without your yes. -8. **After review** — address comments, merge when ready; then come back for step 7. +7. **After review** — remind you the working copy is still on the issue branch; once the PR merges, run `/issue-cleanup` for the post-merge tidy-up. + +**Result:** Commit, push, PR link. No branches are deleted from `/issue-close` itself. + +--- + +## 6. `/issue-cleanup` — post-merge branch hygiene + +**When:** The PR opened by `/issue-close` has merged on GitHub. + +**What you pass:** Nothing (acts on the current branch) or an explicit branch name. + +**What the assistant does:** + +1. Detects the default branch. +2. Detects merge state via `gh pr view` (falls back to `git cherry origin/<default> <branch>` to catch squash-merges). +3. If **not merged**: reminds you to stay off the default for unrelated work and re-run after merge. +4. If **merged**: one consolidated yes/no prompt covering `git switch <default>`, `git pull --ff-only`, `git fetch --prune`, and `git branch -d` on every local branch whose tip is already reachable from the default (including squash-merged ones). Never `-D`; if `-d` refuses, reports and moves on. +5. Optional safe folder sweep: moves any `issue<N>_*` group whose status file says `- [x] Done` to `{{ issueflows_dir }}/{{ solved_folder }}/`. -**Result:** Short summary of commit, push, PR link, and (after merge) which local branches were deleted — or what is blocked. +**Result:** Working tree on the default, merged local branches deleted (with consent), folders tidy. + +--- + +## 7. `/issue-yolo` — all-in-one for small issues + +**When:** The change is genuinely small and low-risk (typo, one-line fix, doc tweak) and you want to skip the usual checkpoints. For anything bigger, use the individual commands. + +**Preflight (any failure aborts before the chain starts):** + +- Refuses to run on `main` / `master`. +- Refuses if `git status --porcelain` shows unrelated uncommitted changes. +- Runs `uv run pytest` up front; refuses if anything fails. +- **Single consolidated confirmation** listing the full planned chain (issue, branch, repo, downstream flags). + +**Chain:** `/issue-init` → `/issue-plan` (auto-confirmed short plan; aborts if the scope check reveals the change isn't actually small) → `/issue-start` → `uv run pytest` again → `/issue-close` (with any forwarded `bump`/`patch`/`minor`/`major`/`draft`). Does **not** run `/issue-cleanup` — the PR hasn't merged yet. + +**Result:** A commit, push, and PR ready for review — or an abort at the first ambiguity. --- ## End-to-end flow +Tip: at any point in the linear flow below, you can just run `/iflow` and it will dispatch to the right step based on current state. + ```text GitHub issue - │ /issue-init + │ /issue-init (or /iflow) ▼ -{{ issueflows_dir }}/{{ current_issues_folder }}/issueN_original.md (+ optional status files) +{{ issueflows_dir }}/{{ current_issues_folder }}/issueN_original.md + │ /issue-plan + ▼ +issueN_plan.md (user confirmed) │ /issue-start ▼ Code + tests (+ status updates during work) - │ /issue-close + │ /issue-close [optional: bump patch/minor/major] ▼ -Optional `uv version --bump …` → Commit → push → PR → merge +Commit → push → PR │ - └── issue docs in {{ issueflows_dir }}/{{ solved_folder }}/ or {{ issueflows_dir }}/{{ partly_solved_folder }}/ when appropriate + │ (PR merges on GitHub) + │ /issue-cleanup + ▼ +Default branch, stale local branches deleted (with single confirm) + +Detours: + /issue-pause — park mid-stream; moves issueN_* to {{ partly_solved_folder }}/ + /issue-yolo — chain init → plan → start → close for tiny fixes (safeguarded) ``` -The command definitions are the source of truth: `{{ agent_dir }}/commands/issue-init.md`, `issue-start.md`, and `issue-close.md`. The skill packages under `{{ agent_dir }}/skills/` repeat the same workflows for explicit invocation. This document is a readable overview only. +The command definitions under `{{ agent_dir }}/commands/` are the source of truth. The skill packages under `{{ agent_dir }}/skills/` repeat the same workflows for explicit invocation. This document is a readable overview only. diff --git a/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 b/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 index da93772..c6037d3 100644 --- a/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 +++ b/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 @@ -72,14 +72,29 @@ uv run script.py ### Working on issues -After each iteration, update the documents in `{{ issueflows_dir }}/{{ current_issues_folder }}` (should contain one file labelled `_original` that consists of the original issue description, and supplementary status files describing what has been done, current status, and remaining work). +After each iteration, update the documents in `{{ issueflows_dir }}/{{ current_issues_folder }}` (should contain one file labelled `_original` with the original issue description, a `_plan` file with the confirmed approach, and supplementary status files describing what has been done, current status, and remaining work). Use an explicit status checkbox in the status file: - `- [x] Done` when fully resolved - `- [ ] Done` when not fully resolved +### Command lifecycle + +If you just want the next right step, run **`/iflow`** — it detects state (by file presence under `{{ issueflows_dir }}/{{ current_issues_folder }}/` and the status-file `- [x] Done` marker) and dispatches to `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close`. It never auto-dispatches to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo` — those stay explicit. + +The full slash-command lifecycle is: + +1. **`/issue-init`** — capture the GitHub issue as `issue<N>_original.md`. +2. **`/issue-plan`** — design the approach in `issue<N>_plan.md` and get explicit confirmation before any code changes. +3. **`/issue-start`** — implement the confirmed plan. Asks to run `/issue-plan` first if the plan file is missing. +4. **`/issue-pause`** *(optional)* — park work mid-stream: update status, move the issue group to `{{ partly_solved_folder }}`, optional WIP commit. +5. **`/issue-close`** — tests, optional `uv version --bump`, status update, commit, push, PR. Does not delete branches. +6. **`/issue-cleanup`** — post-merge: switch to default, `git pull --ff-only`, `git fetch --prune`, `git branch -d` on merged local branches under a single consolidated confirm. Never `-D`. + +`/issue-yolo` chains `init → plan → start → close` for small, low-risk issues with up-front safeguards (clean tree, passing tests, single consolidated confirm). + ### When finishing an issue -If the issue is fully resolved (no additional subtasks present), move the original and status markdown files to `{{ issueflows_dir }}/{{ solved_folder }}`. Else, move them to `{{ issueflows_dir }}/{{ partly_solved_folder }}`. +If the issue is fully resolved (no additional subtasks present), move the original, plan, and status markdown files to `{{ issueflows_dir }}/{{ solved_folder }}`. Else, move them to `{{ issueflows_dir }}/{{ partly_solved_folder }}`. ### Scripts that can help us when working on issues @@ -90,7 +105,7 @@ If you want, you can put small scripts etc. that you have made and think could b - Do issue work on an **issue branch** named like `<N>-<short-slug>`, not on the default branch. - Before starting or continuing work on an issue branch, run `git fetch --prune` and check where the branch sits relative to `origin/<default>` (ahead/behind). A branch that is "several commits ahead" after a merged PR usually means the PR was squash-merged and the local branch is stale. -- **Assume squash-merges on GitHub.** After a PR merges: switch to the default branch, `git pull --ff-only`, `git fetch --prune`, and delete the local issue branch with `git branch -d <branch>` (never `-D` automatically). `/issue-close` does this with a single consolidated confirm. +- **Assume squash-merges on GitHub.** After a PR merges: run **`/issue-cleanup`** — it switches to the default branch, runs `git pull --ff-only`, `git fetch --prune`, and deletes merged local branches with `git branch -d <branch>` under a single consolidated confirm (never `-D` automatically). `/issue-close` no longer does this step itself. - If an issue is already archived under `{{ issueflows_dir }}/{{ partly_solved_folder }}` or `{{ issueflows_dir }}/{{ solved_folder }}`, the matching local branch is stale; don't resume work on it silently — switch back to the default branch and, if the issue really needs re-opening, do it deliberately through `/issue-init` (which will ask for a second confirmation). diff --git a/src/issue_flow/templates/skills/issueflow_iflow/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_iflow/SKILL.md.j2 new file mode 100644 index 0000000..e15f98f --- /dev/null +++ b/src/issue_flow/templates/skills/issueflow_iflow/SKILL.md.j2 @@ -0,0 +1,55 @@ +--- +name: issueflow-iflow +description: >- + Run the /iflow smart dispatcher: detect where the focus issue stands in the + lifecycle (via files in {{ issueflows_dir }}/{{ current_issues_folder }}/ and + status markers) and dispatch to /issue-init, /issue-plan, /issue-start, or + /issue-close. Forwards trailing args verbatim. Never auto-dispatches to + /issue-pause, /issue-cleanup, or /issue-yolo. +disable-model-invocation: true +--- + +# issue-flow — iflow smart dispatcher (`/iflow`) + +Follow this skill when the user wants to run **the right next step** in the issue-flow lifecycle without remembering which specific command applies. Matches `{{ agent_dir }}/commands/iflow.md`. + +## When to use + +- The user runs `/iflow`, mentions **iflow**, or asks "what's the next step?" during an issue-flow lifecycle. +- You want a single entry point that routes to `/issue-init`, `/issue-plan`, `/issue-start`, or `/issue-close` based on current state. + +Do **not** use this skill for `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. Those are explicit-only commands. + +## Instructions + +1. **Resolve the focus issue number `N`.** + - `git branch --show-current`. If it matches `^(\d+)-.+`, the leading digits are the **authoritative** `N`. + - List `issue<n>_*` groups in `{{ issueflows_dir }}/{{ current_issues_folder }}/`, and also check `{{ issueflows_dir }}/{{ partly_solved_folder }}/` and `{{ issueflows_dir }}/{{ solved_folder }}/` for archived groups matching `N`. + - Pick `N` by precedence: + 1. **Branch-derived `N` wins**, regardless of whether a group for `N` exists in `{{ current_issues_folder }}/`. State **A** will apply when no `issue<N>_*` files are present yet. If `issue<N>_*` is archived under `{{ partly_solved_folder }}/` or `{{ solved_folder }}/`, warn the user that `/issue-init`'s archived-issue guard will ask for an explicit confirmation before re-opening. + 2. No branch-derived `N`, exactly one group exists in `{{ current_issues_folder }}/` → use it. + 3. No branch-derived `N`, no groups at all → state **A** (dispatch `/issue-init`; it will ask for a number). + 4. No branch-derived `N`, multiple groups → **stop and ask**. + +2. **Detect state and choose the dispatch target** (first match wins): + + - **A** — no `issue<N>_original.md` (or no focus issue) → dispatch to **`/issueflow-issue-init`**. Reason: "no `*_original.md` yet". + - **B** — original exists, no `issue<N>_plan.md` → dispatch to **`/issueflow-issue-plan`**. Reason: "no plan file yet". + - **C** — plan exists, and status file is missing or its `- [x] Done` is unchecked → dispatch to **`/issueflow-issue-start`**. Reason: "plan is confirmed but status is not `- [x] Done`". + - **D** — status file contains `- [x] Done` (case-insensitive on `done`) → dispatch to **`/issueflow-issue-close`**. Reason: "status marks the issue `- [x] Done`". + +3. **Announce and dispatch.** Print one line like `/iflow -> /issue-plan (issue #N: no plan file yet)` and then follow the chosen command's playbook. Forward the user's trailing text verbatim. + +4. **Respect downstream checkpoints.** Never suppress the downstream command's own prompts (plan confirmation, unrelated-changes prompt, etc.). `/iflow` adds no new confirmation layer of its own. + +5. **Report.** Summarize: focus issue `N` and how it was resolved, which command was dispatched and why, the downstream output, and a one-line hint when an off-path command is the natural next step: + - state **D** + PR likely merged → "after the PR merges, run `/issue-cleanup`" + - mid-stream context switch needed → "to park this work, run `/issue-pause`" + - tiny fix that would benefit from a single-shot chain → "consider `/issue-yolo` next time" + +## Constraints + +- Never auto-dispatch to `/issue-pause`, `/issue-cleanup`, or `/issue-yolo`. +- If the focus issue cannot be resolved (multiple groups, branch ambiguous), stop and ask. +- Do not modify files beyond what the downstream command would normally modify. `/iflow` itself writes nothing — all file changes come from the dispatched command. +- Dispatch to at most one command per `/iflow` invocation. diff --git a/src/issue_flow/templates/skills/issueflow_issue_cleanup/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_issue_cleanup/SKILL.md.j2 new file mode 100644 index 0000000..20b25e1 --- /dev/null +++ b/src/issue_flow/templates/skills/issueflow_issue_cleanup/SKILL.md.j2 @@ -0,0 +1,44 @@ +--- +name: issueflow-issue-cleanup +description: >- + Run the /issue-cleanup workflow: detect merge status, switch to the default + branch, and — with a single consolidated confirm — delete every local branch + already reachable from origin/<default> (including squash-merges). Never -D. +disable-model-invocation: true +--- + +# issue-flow — issue cleanup (`/issue-cleanup`) + +Follow this skill when the user wants to **run post-merge branch hygiene** after a PR has been merged, matching `{{ agent_dir }}/commands/issue-cleanup.md`. + +## When to use + +- The user runs `/issue-cleanup`, mentions **issue-cleanup**, or asks you to delete local branches whose PRs have merged. +- The PR opened by `/issue-close` just merged and the user wants the standard post-merge tidy-up. + +## Instructions + +1. **Detect the default branch.** Prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`. + +2. **Identify the target branch.** If the user named a branch after `/issue-cleanup`, use it. Else use the current branch (`git branch --show-current`). If the current branch **is** the default, skip to step 4 (folder sweep only). + +3. **Check PR / merge state.** Prefer `gh pr view <branch> --json state,mergedAt,mergeCommit,headRefName`. If `gh` is unavailable, approximate with `git fetch --prune` then `git cherry origin/<default> <branch>` (all commits marked `-` means squash-merged). + - **If not merged:** remind the user that the working copy is still on the issue branch; suggest `git switch <default>` before unrelated work and re-run `/issue-cleanup` after the PR merges. **Stop.** Do not delete anything. + - **If merged:** continue. + +4. **Consolidated confirm** — one yes/no prompt listing every action: + - `git switch <default>` + - `git pull --ff-only` + - `git fetch --prune` + - Every local branch whose tip is already reachable from `origin/<default>` (include squash-merges via `git cherry`). List them explicitly before running `git branch -d <branch>` for each. Never use `-D`; if `-d` refuses, report the branch and move on. + +5. **Optional folder sweep** (safe; no destructive git). In `{{ issueflows_dir }}/{{ current_issues_folder }}/`, for each `issue<N>_*` group whose status file contains `- [x] Done` (case-insensitive on `done`), move the group to `{{ issueflows_dir }}/{{ solved_folder }}/`. Leave groups without a checked `Done` in place — routing them to `{{ issueflows_dir }}/{{ partly_solved_folder }}/` is `/issue-pause`'s job. + +6. **Report.** Summarize: default branch, PR/merge status, commands run, branches deleted, branches skipped (with reason), folder sweep result. + +## Constraints + +- Never use `git branch -D` or `git push --force`. +- Never delete the default branch. +- If anything is ambiguous (detached HEAD, multiple remotes, missing tracking info), report and stop rather than guess. +- Do not open or update PRs. Do not bump versions. Those belong to `/issue-close`. diff --git a/src/issue_flow/templates/skills/issueflow_issue_close/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_issue_close/SKILL.md.j2 index 70a9c79..d46f1c2 100644 --- a/src/issue_flow/templates/skills/issueflow_issue_close/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/issueflow_issue_close/SKILL.md.j2 @@ -2,7 +2,8 @@ name: issueflow-issue-close description: >- Run the /issue-close workflow: verify tests, optional uv semver bump, update - issue status and folder locations, commit, push, and open a PR with a clear summary. + issue status and folder locations, commit, push, and open a PR with a clear + summary. Post-merge branch cleanup now lives in /issue-cleanup. disable-model-invocation: true --- @@ -10,6 +11,8 @@ disable-model-invocation: true Follow this skill when the user wants to **finish and land** work: tests, optional version bump, issue-folder updates, git, and PR. Match `{{ agent_dir }}/commands/issue-close.md`. +Post-merge branch hygiene now lives in `/issue-cleanup` — this skill no longer deletes branches. + ## When to use - The user runs `/issue-close`, mentions **issue-close**, or asks to commit, push, or open a PR after issue-flow work. @@ -31,7 +34,7 @@ When a bump applies: read `{{ agent_dir }}/skills/issueflow-version-bump/SKILL.m 2. **Optional version bump** — If the user asked for a bump (see above), follow `{{ agent_dir }}/skills/issueflow-version-bump/SKILL.md` and run `uv version --bump <patch|minor|major>`. If there is no bumpable `pyproject.toml`, skip and continue. -3. **Issue tracking** — Under `{{ issueflows_dir }}/{{ current_issues_folder }}/`, update the status file: remaining work, checklists, and **`- [x] Done`** only when the issue is fully resolved. If fully resolved, move that issue’s markdown files (`issue<n>_*`) to `{{ issueflows_dir }}/{{ solved_folder }}/`. If partially resolved, move to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. Follow any stricter rules in `{{ agent_dir }}/rules/issueflow-rules.mdc` if present. +3. **Issue tracking** — Under `{{ issueflows_dir }}/{{ current_issues_folder }}/`, update the status file: remaining work, checklists, and **`- [x] Done`** only when the issue is fully resolved. If fully resolved, move that issue's markdown files (`issue<n>_*`) to `{{ issueflows_dir }}/{{ solved_folder }}/`. If partially resolved, move to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. Follow any stricter rules in `{{ agent_dir }}/rules/issueflow-rules.mdc` if present. 4. **Commit** — First check `git status`; if there are unrelated uncommitted changes, surface them and ask the user whether to include them — do not auto-include or drop silently. Then stage intentionally (include `pyproject.toml` and `uv.lock` if changed after a bump); write a commit message in full sentences describing what changed and why. @@ -41,13 +44,12 @@ When a bump applies: read `{{ agent_dir }}/skills/issueflow-version-bump/SKILL.m 7. **Pull request** — Open (or update) a PR against the default branch. Body should explain the change, how to test, and link the GitHub issue (`Closes #n` / `Refs #n`). -8. **Post-merge branch cleanup** — Detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`; fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`). Detect merge status with `gh pr view <branch> --json state,mergedAt,mergeCommit,headRefName`. If `gh` is unavailable, approximate with `git fetch --prune` + `git cherry origin/<default> <branch>` (all commits marked `-` means squash-merged). - - **If merged:** ask once whether to run the standard cleanup. On yes: `git switch <default> && git pull --ff-only && git fetch --prune`. Then list every local branch whose tip is already reachable from `origin/<default>` (including squash-merged ones) and ask **once** (one consolidated yes/no listing every branch) before running `git branch -d <branch>` for each. Never use `-D` automatically; if `-d` refuses, report the branch and leave it alone. - - **If not yet merged:** remind the user the working copy is still on the issue branch (not the default). Suggest `git switch <default>` before starting unrelated work, and tell them to re-run `/issue-close` after the PR merges so the post-merge cleanup runs. +8. **After review** — Remind the user the working copy is still on the issue branch (not the default). Suggest `git switch <default>` before starting unrelated work. Tell them to run **`/issue-cleanup`** once the PR is merged so the standard post-merge cleanup runs (switch to default, `git pull --ff-only`, `git fetch --prune`, `git branch -d` on merged local branches under a single consolidated confirm). -9. **Output** — Summarize commit, push result, PR URL, and (when applicable) which local branches were deleted during post-merge cleanup. If blocked, report the next step. +9. **Output** — Summarize commit, push result, PR URL, and next step (`/issue-cleanup` after merge, or "blocked on …" if stuck). ## Constraints -- Do not skip failing tests without the user’s explicit agreement. +- Do not skip failing tests without the user's explicit agreement. - Prefer focused commits; do not rewrite unrelated history unless asked. +- Never delete branches from `/issue-close`. Branch deletion belongs to `/issue-cleanup`. diff --git a/src/issue_flow/templates/skills/issueflow_issue_pause/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_issue_pause/SKILL.md.j2 new file mode 100644 index 0000000..e4b1dca --- /dev/null +++ b/src/issue_flow/templates/skills/issueflow_issue_pause/SKILL.md.j2 @@ -0,0 +1,44 @@ +--- +name: issueflow-issue-pause +description: >- + Run the /issue-pause workflow: update the status file with remaining work, + move the issue group to {{ issueflows_dir }}/{{ partly_solved_folder }}/, + and optionally make a WIP commit and switch to the default branch. +disable-model-invocation: true +--- + +# issue-flow — issue pause (`/issue-pause`) + +Follow this skill when the user wants to **park work on the current issue** without closing it, matching `{{ agent_dir }}/commands/issue-pause.md`. + +## When to use + +- The user runs `/issue-pause`, mentions **issue-pause**, or asks to park / stash / shelve work on an issue to switch context. +- The issue is **not** done — this is not `/issue-close`. + +## Instructions + +1. **Find the focus issue.** In `{{ issueflows_dir }}/{{ current_issues_folder }}/`, identify the `issue<N>_*` group. If multiple groups exist and the focus is ambiguous, ask. If none exist, **stop** and say there is nothing to pause. + +2. **Update `issue<N>_status.md`.** Create or update it under `{{ issueflows_dir }}/{{ current_issues_folder }}/` with: + - `- [ ] Done` (must remain **unchecked** — a pause is not a close). + - **Done so far** — short bullets of what has landed or been tried. + - **Remaining work** — explicit next steps so work can resume later. + - **Paused on** — date, branch name, any blockers. + + Preserve earlier user-written content; only add or update these sections. If the user passed a short note after `/issue-pause`, use it verbatim as the **Remaining work** text. + +3. **Move the issue group.** Move every `issue<N>_*` file from `{{ issueflows_dir }}/{{ current_issues_folder }}/` to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. Report each move. + +4. **Working-tree guard.** Run `git status --porcelain`. Report what is dirty, then offer — as **one** consolidated prompt — any combination of: + - **WIP commit** — stage tracked changes and commit `WIP: pause issue #<N> — <short note>`. List untracked files separately and ask before including them. + - **Switch to default branch** — detect default (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`) and run `git switch <default>`. Only after the WIP commit if the tree is dirty. + - **Stay put** — leave branch and working tree untouched. + +5. **Report.** Summarize the status update, the issue-group moves, working-tree actions taken, and remind the user how to resume (running `/issue-init <N>` re-opens the archived issue after its archived-issue guard, or they can simply switch back to the issue branch). + +## Constraints + +- The focus issue's `- [ ] Done` checkbox **must** stay unchecked. `/issue-pause` is not `/issue-close`. +- Do not delete branches. Do not `git reset`, `git stash drop`, or force-push. +- Do not open a PR. Do not bump versions. Do not run tests. diff --git a/src/issue_flow/templates/skills/issueflow_issue_plan/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_issue_plan/SKILL.md.j2 new file mode 100644 index 0000000..d44b5ea --- /dev/null +++ b/src/issue_flow/templates/skills/issueflow_issue_plan/SKILL.md.j2 @@ -0,0 +1,49 @@ +--- +name: issueflow-issue-plan +description: >- + Run the /issue-plan workflow: read the focus issue in + {{ issueflows_dir }}/{{ current_issues_folder }}/, draft a structured plan + in issue<N>_plan.md, and get explicit user confirmation before any + implementation starts. +disable-model-invocation: true +--- + +# issue-flow — issue plan (`/issue-plan`) + +Follow this skill when the user wants to **design the approach** for an issue before touching code, matching `{{ agent_dir }}/commands/issue-plan.md`. + +## When to use + +- The user runs `/issue-plan`, mentions **issue-plan**, or asks you to design the approach / write a plan for the current issue. +- You want a clear, confirmed plan before `/issue-start` begins editing code. + +## Instructions + +1. **Find the focus issue.** Look in `{{ issueflows_dir }}/{{ current_issues_folder }}/` for `issue<N>_original.md`. If it is missing or multiple groups are ambiguous, **stop** and ask. Suggest `/issue-init` first. + +2. **Branch status preflight** (non-destructive). Detect the default branch (prefer `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, else `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`). Run `git fetch --prune`. Report current branch, clean/dirty working tree, and ahead/behind vs `origin/<default>`. If on the default branch, suggest creating an issue branch (`git switch -c <N>-<short-slug>`) but do **not** auto-run it — planning itself does not require a branch switch. + +3. **Read context.** Load `issue<N>_original.md` and any existing `issue<N>_status.md`. Explore read-only: search code, read the files most likely to change, check existing tests. + +4. **Write `issue<N>_plan.md`** under `{{ issueflows_dir }}/{{ current_issues_folder }}/` with these sections: + - **Goal** — one or two sentences. + - **Constraints** — project rules, back-compat, scope limits. + - **Approach** — concrete design, data flow, ordering. + - **Files to touch** — path + what changes for each. + - **Test strategy** — `uv run pytest` (or equivalents) and any new tests. + - **Open questions** — anything that needs the user's call before coding. + + Keep it terse but specific. Use markdown links to files when useful. + +5. **Scope check.** If the plan is broad (many unrelated files, mixes refactors with feature work, multiple independent deliverables), propose splitting into smaller issues or phased PRs before finalizing the plan. + +6. **Confirm with the user.** Present the plan and **stop**. Accept one of: **Accept** (ready for `/issue-start`), **Revise** (update `issue<N>_plan.md` in place and re-confirm), or **Abort**. + +7. **Conflict on existing `issue<N>_plan.md`.** Do not overwrite silently. Offer: update in place (after review), keep both (`issue<N>_plan.v2.md`), or leave as is. + +## Constraints + +- `/issue-plan` is **read-only on source code**. The only file it writes is `{{ issueflows_dir }}/{{ current_issues_folder }}/issue<N>_plan.md`. +- Do not move files between `01-` / `02-` / `03-` folders from `/issue-plan`. +- Do not run tests or package managers; that belongs to `/issue-start` and `/issue-close`. +- Do not proceed to implementation from this skill. Hand off to `/issue-start` once the user confirms. diff --git a/src/issue_flow/templates/skills/issueflow_issue_start/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_issue_start/SKILL.md.j2 index 11cd003..707ce75 100644 --- a/src/issue_flow/templates/skills/issueflow_issue_start/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/issueflow_issue_start/SKILL.md.j2 @@ -1,15 +1,15 @@ --- name: issueflow-issue-start description: >- - Run the /issue-start workflow: pick the current issue markdown, plan (with - confirmation when not in plan mode), guard scope, then implement with project - conventions (e.g. uv run). + Run the /issue-start workflow: pick the current issue, read issue<N>_plan.md + (offer to run /issue-plan if missing), then implement with project conventions + (e.g. uv run). disable-model-invocation: true --- # issue-flow — issue start (`/issue-start`) -Follow this skill when the user wants to **begin implementation** from issue notes, matching `{{ agent_dir }}/commands/issue-start.md` and project rules. +Follow this skill when the user wants to **begin implementation** from issue notes, matching `{{ agent_dir }}/commands/issue-start.md` and project rules. Planning itself lives in `/issue-plan`; this skill is now implementation-only. ## When to use @@ -24,21 +24,27 @@ Follow this skill when the user wants to **begin implementation** from issue not 3. **Sweep stale current issues** (auto-safe) — Group files in `{{ issueflows_dir }}/{{ current_issues_folder }}/` by `issueNN_` prefix. For every group **other than the focus issue**, move the whole group to `{{ issueflows_dir }}/{{ solved_folder }}/` if any of its status files contains `- [x] Done` (case-insensitive on `done`), otherwise move it to `{{ issueflows_dir }}/{{ partly_solved_folder }}/`. Never move the focus issue's files. Report every move. -4. **Plan first** — Produce a concrete plan (steps, files touched, tests). If you are **not** in plan mode, **stop and ask for explicit confirmation** before implementing, per the command definition. +4. **Plan precondition** — Look for `issue<N>_plan.md` in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. + - **Plan present:** read it and treat it as the source of truth for scope and approach. + - **Plan missing:** do **not** hard-stop. Ask the user to choose one of: + - **Run `/issue-plan` now**, then continue into implementation after they confirm the plan. + - **Proceed without a plan** — add a short `- Skipped /issue-plan on <date>` note to `issue<N>_status.md` and continue. + - **Abort.** -5. **Scope check** — If the plan is broad, propose splitting into phases and ask whether to narrow scope before coding. +5. **Implement** — Execute the plan (or the explicitly-acknowledged plan-less path). Prefer minimal, focused diffs. Match existing code style and tooling. -6. **Implement** — Execute the confirmed plan. Prefer minimal, focused diffs. Match existing code style and tooling. - -7. **Project conventions** +6. **Project conventions** - Run Python via **`uv run`** (scripts, pytest, tools), not bare `python`, unless the user overrides. - Manage dependencies with **`uv add` / `uv remove` / `uv sync`** only. - - After meaningful progress, update or create a status markdown file under `{{ issueflows_dir }}/{{ current_issues_folder }}/` (e.g. `issue<number>_status.md`) with an explicit **Done** checkbox: `- [ ] Done` until fully resolved, then `- [x] Done`. + - After meaningful progress, update or create `issue<N>_status.md` under `{{ issueflows_dir }}/{{ current_issues_folder }}/` with an explicit `- [ ] Done` checkbox that stays unchecked until fully resolved. Record what has landed and what remains. + +7. **Hand off** — When the implementation is ready to ship, tell the user to run `/issue-close` (optionally with `bump`/`patch`/`minor`/`major`). Parking work mid-stream goes through `/issue-pause`. -8. **Reporting** — Summarize what changed, what remains, and where the issue docs live. Include any branch warnings from step 2 and any issue-group moves from step 3. +8. **Reporting** — Summarize what changed, what remains, and where the issue docs live. Include any branch warnings from step 2, any group moves from step 3, and whether the plan was followed or explicitly skipped. ## Constraints -- Do not invent issue text; treat `*_original.md` as read-only source of requirements unless the user asks to edit it. -- The stale sweep in step 3 is the **only** automatic move `/issue-start` performs, and it never touches the focus issue's own files. Do not move the focus issue's files between `01-` / `02-` / `03-` folders during `/issue-start`. +- Do not invent issue text; treat `*_original.md` as a read-only source of requirements unless the user asks to edit it. +- The stale sweep in step 3 is the **only** automatic folder move `/issue-start` performs, and it never touches the focus issue's own files. - Never delete or force-update git branches from `/issue-start`. +- Do not write or modify `issue<N>_plan.md` from here — changes to the plan go through `/issue-plan`. diff --git a/src/issue_flow/templates/skills/issueflow_issue_yolo/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_issue_yolo/SKILL.md.j2 new file mode 100644 index 0000000..87cd88e --- /dev/null +++ b/src/issue_flow/templates/skills/issueflow_issue_yolo/SKILL.md.j2 @@ -0,0 +1,49 @@ +--- +name: issueflow-issue-yolo +description: >- + Run the /issue-yolo workflow: preflight (no default branch, clean tree, + passing tests), single consolidated confirm, then chain init → plan → start + → close for small, low-risk issues. Stops on any ambiguity. +disable-model-invocation: true +--- + +# issue-flow — issue yolo (`/issue-yolo`) + +Follow this skill when the user wants to **blast through a small, low-risk issue** in one shot, matching `{{ agent_dir }}/commands/issue-yolo.md`. + +Use only for minor fixes, doc tweaks, and similar low-risk changes. Anything non-trivial should go through the individual commands. + +## When to use + +- The user runs `/issue-yolo`, `/issue-fast`, mentions **issue-yolo**, or asks to "just do it" for a small issue. +- The task is obviously small and the user has accepted that there will be no mid-run confirmation checkpoints. + +## Preflight (abort on any failure) + +1. **Refuse on default branch.** If the current branch is `main` / `master` / the detected default, **stop** and tell the user to create or switch to an issue branch first. Do not silently create one from yolo. + +2. **Refuse with dirty unrelated changes.** Run `git status --porcelain`. If anything uncommitted is not clearly part of the target issue, ask once; if still unclear, **stop**. Suggest committing or stashing first. + +3. **Tests must pass up front.** Run `uv run pytest` (or the repo's documented test command). On any failure, **stop** before the chain starts. + +4. **Single consolidated confirm.** Present the full planned chain explicitly (issue reference, target branch, repo, downstream commands including any `bump` / `patch` / `draft` flags). Require an explicit yes; any other input aborts. + +## Chain + +Once preflight has passed and the user confirmed: + +1. **`/issueflow-issue-init`** — capture the issue (or skip if `*_original.md` already exists for the focus issue). +2. **`/issueflow-issue-plan`** — write a **short** `issue<N>_plan.md` (Goal + Approach + Files to touch + Test strategy). Auto-confirm — the consolidated confirm above covered it. If the scope check reveals the change is not actually small, **abort the yolo chain** and tell the user to run the commands individually. +3. **`/issueflow-issue-start`** — implement the plan without an additional plan-mode prompt. +4. **Re-run tests.** `uv run pytest` again. On failure, **stop** before commit / push / PR. +5. **`/issueflow-issue-close`** — run the full close flow (optional version bump if the user passed `bump` / `patch` / `minor` / `major`, issue-folder update, commit, push, PR). Do **not** chain `/issueflow-issue-cleanup` automatically — the PR has not merged yet. + +## Post-run + +Leave the user on the issue branch with the PR URL. Remind them to re-run `/issue-cleanup` once the PR merges. + +## Constraints + +- Do not override downstream commands' own constraints (no `-D`, no force-push, etc.). `/issue-yolo` is a chain, not a free pass. +- If **any** downstream step requires a human decision (unrelated changes in `git status`, ambiguous version bump, merge conflict, failed test), **stop** and hand back to the user. +- Never run `/issue-cleanup` from this skill. Branch deletion always needs the user to see the merged PR first. diff --git a/src/issue_flow/templating.py b/src/issue_flow/templating.py index 553e629..08dd0b7 100644 --- a/src/issue_flow/templating.py +++ b/src/issue_flow/templating.py @@ -70,23 +70,48 @@ def render_template(template_name: str, context: dict[str, str]) -> str: # Each entry: (template_file, output_path_template) # The output_path_template uses simple str.format with the context dict. TEMPLATE_MANIFEST: list[tuple[str, str]] = [ + ("commands/iflow.md.j2", "{agent_dir}/commands/iflow.md"), ("commands/issue-init.md.j2", "{agent_dir}/commands/issue-init.md"), + ("commands/issue-plan.md.j2", "{agent_dir}/commands/issue-plan.md"), ("commands/issue-start.md.j2", "{agent_dir}/commands/issue-start.md"), + ("commands/issue-pause.md.j2", "{agent_dir}/commands/issue-pause.md"), ("commands/issue-close.md.j2", "{agent_dir}/commands/issue-close.md"), + ("commands/issue-cleanup.md.j2", "{agent_dir}/commands/issue-cleanup.md"), + ("commands/issue-yolo.md.j2", "{agent_dir}/commands/issue-yolo.md"), ("rules/issueflow-rules.mdc.j2", "{agent_dir}/rules/issueflow-rules.mdc"), ("docs/cursor-issue-workflow.md.j2", "{docs_dir}/cursor-issue-workflow.md"), + ( + "skills/issueflow_iflow/SKILL.md.j2", + "{agent_dir}/skills/issueflow-iflow/SKILL.md", + ), ( "skills/issueflow_issue_init/SKILL.md.j2", "{agent_dir}/skills/issueflow-issue-init/SKILL.md", ), + ( + "skills/issueflow_issue_plan/SKILL.md.j2", + "{agent_dir}/skills/issueflow-issue-plan/SKILL.md", + ), ( "skills/issueflow_issue_start/SKILL.md.j2", "{agent_dir}/skills/issueflow-issue-start/SKILL.md", ), + ( + "skills/issueflow_issue_pause/SKILL.md.j2", + "{agent_dir}/skills/issueflow-issue-pause/SKILL.md", + ), ( "skills/issueflow_issue_close/SKILL.md.j2", "{agent_dir}/skills/issueflow-issue-close/SKILL.md", ), + ( + "skills/issueflow_issue_cleanup/SKILL.md.j2", + "{agent_dir}/skills/issueflow-issue-cleanup/SKILL.md", + ), + ( + "skills/issueflow_issue_yolo/SKILL.md.j2", + "{agent_dir}/skills/issueflow-issue-yolo/SKILL.md", + ), ( "skills/issueflow_version_bump/SKILL.md.j2", "{agent_dir}/skills/issueflow-version-bump/SKILL.md", diff --git a/tests/test_templating.py b/tests/test_templating.py index c2ff7c3..d7ee297 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -53,7 +53,36 @@ def test_resolve_output_path() -> None: def test_manifest_entry_count() -> None: - assert len(TEMPLATE_MANIFEST) == 9 + # 8 commands + 1 rule + 1 doc + 9 skills = 19 + assert len(TEMPLATE_MANIFEST) == 19 + + +def test_manifest_has_expected_commands_and_skills() -> None: + """Every expected slash command and skill has a manifest entry.""" + template_names = {name for name, _ in TEMPLATE_MANIFEST} + for command in ( + "iflow", + "issue-init", + "issue-plan", + "issue-start", + "issue-pause", + "issue-close", + "issue-cleanup", + "issue-yolo", + ): + assert f"commands/{command}.md.j2" in template_names + for skill in ( + "issueflow_iflow", + "issueflow_issue_init", + "issueflow_issue_plan", + "issueflow_issue_start", + "issueflow_issue_pause", + "issueflow_issue_close", + "issueflow_issue_cleanup", + "issueflow_issue_yolo", + "issueflow_version_bump", + ): + assert f"skills/{skill}/SKILL.md.j2" in template_names def _default_context() -> dict[str, str]: @@ -77,13 +106,93 @@ def test_issue_start_mentions_branch_and_sweep_preflight() -> None: assert "git fetch --prune" in rendered -def test_issue_close_mentions_post_merge_cleanup() -> None: - """The /issue-close command must describe post-merge branch cleanup.""" +def test_issue_close_delegates_post_merge_cleanup_to_issue_cleanup() -> None: + """/issue-close no longer deletes branches; it points at /issue-cleanup instead.""" rendered = render_template("commands/issue-close.md.j2", _default_context()) - assert "Post-merge branch cleanup" in rendered + assert "/issue-cleanup" in rendered + assert "git pull --ff-only" in rendered + # The destructive branch delete lives in /issue-cleanup now, not /issue-close. + assert "git branch -d" not in rendered + + +def test_issue_cleanup_describes_post_merge_branch_cleanup() -> None: + """The /issue-cleanup command owns the post-merge branch cleanup logic.""" + rendered = render_template("commands/issue-cleanup.md.j2", _default_context()) assert "git branch -d" in rendered assert "git pull --ff-only" in rendered assert "gh pr view" in rendered + # Never -D automatically. + assert "-D" not in rendered or "Never use `-D`" in rendered or "Never `-D`" in rendered + + +def test_issue_start_requires_or_offers_plan() -> None: + """/issue-start should read the plan file and offer to run /issue-plan when missing.""" + rendered = render_template("commands/issue-start.md.j2", _default_context()) + assert "issue<N>_plan.md" in rendered + assert "/issue-plan" in rendered + + +def test_issue_plan_writes_plan_file_and_stops_for_confirmation() -> None: + """/issue-plan must produce a plan file and require confirmation.""" + rendered = render_template("commands/issue-plan.md.j2", _default_context()) + assert "issue<N>_plan.md" in rendered + assert "Goal" in rendered + assert "Approach" in rendered + assert "Confirm" in rendered or "confirmation" in rendered.lower() + + +def test_issue_pause_moves_to_partly_solved() -> None: + """/issue-pause moves the issue group to the partly-solved folder.""" + rendered = render_template("commands/issue-pause.md.j2", _default_context()) + assert "02-partly-solved-issues" in rendered + assert "Remaining work" in rendered + assert "- [ ] Done" in rendered + + +def test_issue_yolo_has_safeguards() -> None: + """/issue-yolo must advertise the up-front safeguards before chaining.""" + rendered = render_template("commands/issue-yolo.md.j2", _default_context()) + assert "uv run pytest" in rendered + assert "default branch" in rendered.lower() + # Must not chain cleanup automatically. + assert "/issue-cleanup" in rendered + + +def test_iflow_describes_state_machine() -> None: + """/iflow must describe the four-state dispatch and name its downstream targets.""" + rendered = render_template("commands/iflow.md.j2", _default_context()) + # Dispatches into all four linear-flow commands. + for target in ( + "/issue-init", + "/issue-plan", + "/issue-start", + "/issue-close", + ): + assert target in rendered, f"/iflow must mention {target}" + # State keywords from the dispatch table. + assert "_original.md" in rendered + assert "_plan.md" in rendered + assert "- [x] Done" in rendered + # Off-path commands are explicitly not auto-dispatched. + assert "/issue-pause" in rendered + assert "/issue-cleanup" in rendered + assert "/issue-yolo" in rendered + + +def test_iflow_treats_branch_derived_n_as_authoritative() -> None: + """/iflow's step 0 must treat a branch-derived N as authoritative. + + The branch-derived N should win even when `issue<N>_*` files don't exist + yet, or when unrelated groups sit in 01-current-issues. It should also + trigger the archived-issue guard warning when the group lives in + 02-partly-solved or 03-solved. + """ + rendered = render_template("commands/iflow.md.j2", _default_context()) + assert "authoritative" in rendered.lower() + # Both archive folders are consulted for the archived-issue guard warning. + assert "02-partly-solved-issues" in rendered + assert "03-solved-issues" in rendered + assert "archived-issue guard" in rendered.lower() def test_issue_init_mentions_branch_preflight_and_archive_guard() -> None: From b91cddaa4503d4c4a68942a8dad4e5ab3e5b0b2f Mon Sep 17 00:00:00 2001 From: jepegit <jepe@ife.no> Date: Sun, 19 Apr 2026 20:01:24 +0200 Subject: [PATCH 2/3] Update README.md to reflect new issue-flow commands and usage instructions - Revised the documentation to include new commands: `/issue-plan`, `/issue-pause`, `/issue-cleanup`, and `/issue-yolo`. - Updated the command sequence for issue management to enhance clarity and usability. - Added descriptions for the new commands and their functionalities, including the smart dispatcher `/iflow`. - Improved overall structure and readability of the README to guide users through the issue-flow lifecycle more effectively. --- README.md | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 899bed4..1330ef7 100644 --- a/README.md +++ b/README.md @@ -11,35 +11,51 @@ Running `issue-flow init` in your project root creates: ```text your-project/ .issueflows/ - 00-tools/ # Helper scripts for agents - 01-current-issues/ # Active issue markdown files + 00-tools/ # Helper scripts for agents + 01-current-issues/ # Active issue markdown files 02-partly-solved-issues/ # Parked / in-progress issues - 03-solved-issues/ # Completed issues archive + 03-solved-issues/ # Completed issues archive .cursor/ commands/ - issue-init.md # /issue-init — fetch a GitHub issue locally - issue-start.md # /issue-start — plan and implement - issue-close.md # /issue-close — test, commit, push, PR - skills/ # Optional Agent Skills (explicit / @ invoke) + iflow.md # /iflow — smart dispatcher (quick start) + issue-init.md # /issue-init — fetch a GitHub issue locally + issue-plan.md # /issue-plan — write issue<N>_plan.md and confirm + issue-start.md # /issue-start — implement the plan + issue-pause.md # /issue-pause — park work in 02-partly-solved-issues/ + issue-close.md # /issue-close — test, commit, push, PR + issue-cleanup.md # /issue-cleanup — post-merge branch hygiene + issue-yolo.md # /issue-yolo — all-in-one for small, low-risk issues + skills/ # Optional Agent Skills (explicit / @ invoke) + issueflow-iflow/SKILL.md issueflow-issue-init/SKILL.md + issueflow-issue-plan/SKILL.md issueflow-issue-start/SKILL.md + issueflow-issue-pause/SKILL.md issueflow-issue-close/SKILL.md + issueflow-issue-cleanup/SKILL.md + issueflow-issue-yolo/SKILL.md issueflow-version-bump/SKILL.md rules/ - issueflow-rules.mdc # Always-on Cursor rule for the workflow + issueflow-rules.mdc # Always-on Cursor rule for the workflow docs/ cursor-issue-workflow.md # Human-readable overview of the workflow ``` -The three Cursor slash commands give agents a repeatable flow: +The Cursor slash commands give agents a repeatable flow. The linear path is: 1. `/issue-init 42` — pulls GitHub issue #42 into `.issueflows/01-current-issues/` and archives older issues. -2. `/issue-start` — reads the issue file, plans, and implements. -3. `/issue-close` — runs tests, optionally bumps version with `uv version --bump`, updates status files, commits, pushes, and opens a PR. +2. `/issue-plan` — drafts `issue<N>_plan.md` (Goal / Constraints / Approach / Files to touch / Test strategy / Open questions) and stops for your confirmation. +3. `/issue-start` — reads the confirmed plan and implements it. If no plan file exists, it offers to run `/issue-plan` first, proceed without a plan, or abort. +4. `/issue-close` — runs tests, optionally bumps version with `uv version --bump`, updates status files, commits, pushes, and opens a PR. +5. `/issue-cleanup` — after the PR merges, switches to the default branch, fast-forwards, prunes, and deletes the merged local branch. -The matching **Agent Skills** (under `.cursor/skills/`) carry the same workflows for on-demand use with `/issueflow-issue-init`, `/issueflow-issue-start`, `/issueflow-issue-close`, or `@issueflow-version-bump` when you need only the bump steps (see [Cursor Agent Skills](https://cursor.com/docs/context/skills)). +Plus a few off-path commands: -The matching **Agent Skills** (under `.cursor/skills/`) carry the same workflows for on-demand use with `/issueflow-issue-init`, `/issueflow-issue-start`, or `/issueflow-issue-close` (see [Cursor Agent Skills](https://cursor.com/docs/context/skills)). +- `/iflow` — **quick start**: inspects the current issue's state and dispatches to the right linear step automatically. A branch-derived number (`42-fix-login` → `N=42`) is authoritative, so `/iflow` works from a fresh branch too. +- `/issue-pause` — park the current issue in `02-partly-solved-issues/` with a **Remaining work** note; optional WIP commit + switch back to the default branch. +- `/issue-yolo` — all-in-one chain (`init → plan → start → close`) for small, low-risk issues, with up-front safeguards (refuses on the default branch, refuses with dirty unrelated changes, requires passing tests, single consolidated confirm). + +The matching **Agent Skills** (under `.cursor/skills/`) carry the same workflows for on-demand use with `/issueflow-iflow`, `/issueflow-issue-init`, `/issueflow-issue-plan`, `/issueflow-issue-start`, `/issueflow-issue-pause`, `/issueflow-issue-close`, `/issueflow-issue-cleanup`, `/issueflow-issue-yolo`, or `@issueflow-version-bump` when you need only the bump steps (see [Cursor Agent Skills](https://cursor.com/docs/context/skills)). ## Installation @@ -62,7 +78,7 @@ cd your-project issue-flow init ``` -That's it. Open the project in Cursor and use `/issue-init`, `/issue-start`, `/issue-close`. +That's it. Open the project in Cursor and start with `/iflow` (or step through `/issue-init`, `/issue-plan`, `/issue-start`, `/issue-close`, `/issue-cleanup` explicitly). ## Usage From 7c1a006fcbeab375d361a18e3801afa4ce34a047 Mon Sep 17 00:00:00 2001 From: jepegit <jepe@ife.no> Date: Sun, 19 Apr 2026 20:02:57 +0200 Subject: [PATCH 3/3] Archive issue #39 to 03-solved-issues Issue #39 is fully resolved. Move issue39_original.md and issue39_status.md from .issueflows/01-current-issues to .issueflows/03-solved-issues per the workflow rule that only the active focus issue lives in 01-. Made-with: Cursor --- .../{01-current-issues => 03-solved-issues}/issue39_original.md | 0 .../{01-current-issues => 03-solved-issues}/issue39_status.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .issueflows/{01-current-issues => 03-solved-issues}/issue39_original.md (100%) rename .issueflows/{01-current-issues => 03-solved-issues}/issue39_status.md (100%) diff --git a/.issueflows/01-current-issues/issue39_original.md b/.issueflows/03-solved-issues/issue39_original.md similarity index 100% rename from .issueflows/01-current-issues/issue39_original.md rename to .issueflows/03-solved-issues/issue39_original.md diff --git a/.issueflows/01-current-issues/issue39_status.md b/.issueflows/03-solved-issues/issue39_status.md similarity index 100% rename from .issueflows/01-current-issues/issue39_status.md rename to .issueflows/03-solved-issues/issue39_status.md