Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions .claude/skills/issue-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
name: issue-workflow
description: Drive a single GitHub issue from "open on main" through branch → commits → PR → CI green → squash-merge → issue closed → main synced. Replaces the old scripts/issue-workflow.sh. Use when the user says "work on issue #N", "implement issue #N", "finish issue #N", "next issue", or asks to start a feature/fix branch tied to a specific issue number.
---

# issue-workflow

Drive one GitHub issue through the full branch → PR → merge → cleanup
loop. This skill is the AI-equivalent of the old `scripts/issue-workflow.sh`
shell script: instead of imperative bash, it produces a tracked list of
tasks the assistant walks through one at a time.

The skill encodes the project conventions in [`CONTRIBUTING.md`](../../CONTRIBUTING.md):
- branch off `main`, keep `main` clean
- one logical change per PR
- Conventional Commits (`type(scope): subject`)
- squash-merge, source branch deleted on merge
- PR title = first commit subject
- allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
- allowed scopes: `detector`, `manager`, `packages`, `node`, `config`, `ui`, `platform`, `cli`, `deps`, `release`, `ci`, `docs`, `lint`
- every PR body must reference its source issue via `Closes #N` or `Refs #N`

## When to invoke

The user explicitly asks to work on an issue by number, or says
"next issue" / "finish the open issue" / "implement #N". Do not invoke
this skill for ad-hoc exploratory work or for changes that don't have
a tracking issue — open an issue first.

## Inputs to collect up front

Before doing anything, ask the user (use `AskUserQuestion` if more than
one is ambiguous):

1. **Issue number** — the only required input. If the user says "next
issue", find the lowest-numbered open issue via `gh issue list
--state open --limit 1 --json number,title`.
Comment on lines +35 to +37
2. **Type / scope / slug** — for branching. Try to derive these from
the issue title (it should follow `type(scope): subject` per
CONTRIBUTING.md). If the title doesn't match, ask the user to
rename it OR to pass the three explicitly.

Slug rules: lowercase, non-alnum → `-`, trim leading/trailing `-`.
Branch name is `<type>/<scope>/<slug>`.

## Workflow (in order)

Create one `TaskCreate` per step below, mark `in_progress` when you
start it, `completed` when done. Don't skip steps. If a step fails,
stop and surface the failure to the user — do not paper over it.

### 1. Inspect the issue

`gh issue view <N> --json title,state,labels,body,number`

Confirm `state == OPEN`. If closed, tell the user and stop. Skim the
body for scope, linked issues, and any "Out of scope" notes.

### 2. Sync local main

```
git fetch origin main --quiet
git checkout main
git pull --ff-only origin main
```

If `git pull` refuses (non-fast-forward), stop and tell the user — do
not force.

### 3. Create the feature branch

`git checkout -b <type>/<scope>/<slug> origin/main`

Verify with `git rev-parse --abbrev-ref HEAD` that you're on the new
branch.

### 4. Plan the work

Before writing any code, read enough of the codebase to know:
- which files this issue touches (use `Glob`/`Grep`)
- what the change shape looks like (one paragraph mental summary)
- what tests need to be added or updated
- what docs/CHANGELOG need updating

If the change is larger than ~200 lines or spans 3+ packages, surface
that to the user — `CONTRIBUTING.md` says "one logical change per PR"
and a too-large PR may need to be split into multiple issues.

### 5. Implement + test + lint loop

For each logical chunk of work:
- write the change
- `go build ./...`
- `go vet ./...`
- `go test ./...` (or scoped to the package)
- `gofmt -l .` (no diffs)
- commit with a Conventional Commits subject that matches the PR
title the user wants

Subject must match `^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z]+\))?!?: .{1,100}$`.
Body lines ≤100 cols. Footer for `Refs #N` / `BREAKING CHANGE:` etc.

Commit author should be the assistant's Co-Authored-By line per the
project's commit style.

### 6. Run `make ci`

`make ci` runs `tidy` + `vet` + `test` + `golangci-lint`. Must be green
before opening the PR. If `golangci-lint` complains, fix the lint
issues — don't suppress.

### 7. Generate the PR body

Use the template at `.github/PULL_REQUEST_TEMPLATE.md`. Fill in:
- `## Summary` — one or two sentences, end with `Closes #N`
- `## Linked issues` — `Closes #N`
- `## Type of change` — check the matching box
- `## Checklist` — leave unchecked only the ones the user hasn't done
- `## Scope notes` — anything intentionally left out
- `## Screenshots / output` — if user-facing output changed

### 8. Push and open the PR

```
git push -u origin <branch>
gh pr create --base main --head <branch> \
--title "<first commit subject>" \
--body-file /tmp/pr-body-<N>.md
```

Capture the PR number from the output.

### 9. Watch CI

`gh pr checks <N> --watch --fail-fast --interval 30`

Don't poll in a loop. Don't run multiple `gh pr checks` in parallel.
If a check fails, read the failure log, fix it, push an amendment,
re-run.

### 10. Address review comments

If a reviewer (human or bot like Copilot) leaves comments on the PR,
read each one, decide whether to apply the fix or push back with a
rationale. Don't blindly accept suggestions — apply the ones that
improve the code, push back on the ones that don't.

### 11. Squash-merge

```
gh pr merge <N> --squash --delete-branch
```

If the repo's branch policy refuses, retry with `--admin` and tell
the user you used admin override.

### 12. Verify the issue closed

`gh issue view <N> --json state,stateReason`

If `stateReason == COMPLETED` and the linked PR shows in
`closedByPullRequestReferences`, you're done. If the issue is still
open, the PR body is missing `Closes #N` — fix the PR description
Comment on lines +159 to +163
with `gh pr edit <N> --body-file ...` and re-check.

### 13. Sync local main and clean up

```
git checkout main
git pull --ff-only origin main
git branch -d <branch> # local cleanup
```

If `git branch -d` refuses (branch not fully merged), the squash-merge
didn't actually merge — go back to step 11.

### 14. Recurse to the next open issue

Find the next open issue (lowest number that is still OPEN and not
blocked). Tell the user what the next issue is and ask whether to
continue.

## Failure modes to handle

| Symptom | What to do |
|---|---|
| Issue title doesn't match `type(scope): …` | Ask user to rename it, OR pass `--type/--scope/--slug` explicitly |
| `git pull --ff-only` fails | Stop — `main` has diverged. Tell user. |
| `golangci-lint` reports a finding | Fix the code, don't suppress |
| `gh pr create` errors about base branch policy | The repo requires admin merge — use `--admin` on step 11 |
| Reviewer comment is wrong or out-of-scope | Push back politely with rationale in a reply |
| Squash-merge silently fails | Check `gh pr view <N> --json state` — likely blocked by CI |
| Issue didn't auto-close | PR body missing `Closes #N`. Edit with `gh pr edit`. |
| `git branch -d` says branch not fully merged | Squash-merge didn't actually merge — investigate |

## Output expectations

When the skill completes successfully, summarize in one short message:

```
Issue #N closed via PR #M.
Branch: <branch>
Squash-merge: <short-sha>
Next open issue: #<next> — <title>
Want me to start on it?
```

## Why this is a skill and not a bash script

The old `scripts/issue-workflow.sh` was 265 lines of bash that:
- parsed Conventional Commits titles with bash regex (fragile)
- called `gh` directly (coupled contributor machines to GitHub auth)
- embedded `gh pr checks --watch` in a `sleep 600; kill $watch_pid`
pattern that crashed the IDE when paged output interleaved with
other terminal tabs
- made every contributor's shell the orchestration layer

This skill moves orchestration into the AI: the assistant reads the
issue, plans the change, edits files, runs validation, opens the PR,
reads review comments, and merges. No contributor shell needed.
The shell is just the place commands execute, not the place workflows
are encoded.
12 changes: 9 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ node_modules/
package-lock.json

# Tooling session metadata (not part of the project source tree).
.claude/

.conversation-history
# `.claude/skills/` is intentionally NOT ignored — it holds
# project-local AI skills (e.g. issue-workflow) that should be
# committed so any contributor/clone picks them up. Only the rest
# of `.claude/` (e.g. session settings) is ignored.
.claude/*
!.claude/skills/

.conversation-history
.puku-cli
16 changes: 14 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,16 @@ Enforced by commitlint (`wagoid/commitlint-github-action@v5`). Violations block
When the user says **"start the next issue"** (or any equivalent — "work on the next one", "what's next", "pick up the next issue"), treat the following as the **default, no-clarification-needed workflow**:

1. **Identify the next issue.** Run `gh issue list --state open --limit 30`. Pick the lowest-numbered open issue that is not a meta-tracking issue, unless the user says otherwise. If only meta-issues remain (e.g. Phase 6 cross-platform polish — issue #16), split the meta-issue into focused sub-issues first (one per concern, one PR each), then start the first sub-issue.
2. **Always pass the issue number through `issue-workflow.sh`.** Use `./scripts/issue-workflow.sh start <issue#>` to branch, and `./scripts/issue-workflow.sh pr-body <issue#>` to render the PR body. The body includes `Closes #<issue#>` — **do not edit this out**. The `Closes` keyword is what auto-closes the issue when the PR is merged.
2. **Use the `issue-workflow` skill.** Invoke
[`.claude/skills/issue-workflow/SKILL.md`](./.claude/skills/issue-workflow/SKILL.md)
to drive the full branch → PR → merge → cleanup loop. The skill
creates one tracked `TaskCreate` per step (sync main, branch, plan,
implement+test, PR body, push, watch CI, address review, merge,
verify close, recurse). Don't re-implement the workflow inline —
the skill exists so the orchestration is consistent and reviewable.
The PR body the skill generates includes `Closes #<issue#>` —
**do not edit this out**. The `Closes` keyword is what auto-closes
the issue when the PR is merged.
3. **Verify the linkage before merging.** After `gh pr create`, run `gh pr view <PR#> --json body | grep -E 'Closes|Fixes' #<issue#>'` to confirm the PR body references the issue. If it does not, edit the PR body via `gh pr edit <PR#> --body-file <new-body.md>` before merging. **Do not squash-merge a PR that is not linked to its source issue.**
4. **Merge with admin override when needed.** This repo's `enforce_admins: false` means solo-author merges need `--admin`. The conventional sequence is:
```bash
Expand All @@ -94,7 +103,10 @@ When the user says **"start the next issue"** (or any equivalent — "work on th
5. **Verify the issue auto-closed.** After the merge, run `gh issue view <issue#> --json state --jq .state` and confirm `CLOSED`. If still `OPEN`, add the issue close manually with `gh issue close <issue#> -c "Closed by <PR#>"`.
6. **Then recurse.** Re-run `gh issue list --state open --limit 30`. If another issue is queued, ask the user to confirm continuation or proceed (if the user already said "keep going", just proceed).

The `make next-issue` target wraps steps 1 and 2 for humans. AI sessions should drive the same script directly.
The `make next-issue` target still works for human contributors who
want a quick branch-off-main shortcut, but it now just prints the
reminder to use the skill — it does not shell out to a bash
orchestrator.

## Phase status

Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ config.manager=<name> explicitly, or omit the key for auto-detect.
fine (use `--force-with-lease`).
7. **Every PR must reference its source issue in the body** — use
`Closes #N` (closing the issue on merge) or `Refs #N` (linked but
not auto-closed). The `scripts/issue-workflow.sh pr-body` template
fills this in automatically from the issue number; do not strip it.
A PR without an issue reference will be sent back for revision.
not auto-closed). The
[`.claude/skills/issue-workflow`](./.claude/skills/issue-workflow/SKILL.md)
skill fills this in automatically from the issue number when the
AI generates the PR body; do not strip it. A PR without an issue
reference will be sent back for revision.

## Local development

Expand Down
26 changes: 18 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,30 @@ verify: ci build ## Full pre-commit verification (CI + build)

# Drive a single GitHub issue from branch → PR → merge.
# Usage: make next-issue ISSUE=16
# Requires: gh authed for dipto0321/nodeup, conventional-commits commit on HEAD,
# clean working tree. See scripts/issue-workflow.sh and [redacted].md
# ("AI workflow — standing orders") for the full sequence.
# For human contributors, this is a thin wrapper that prints the
# standing-orders reminder. The actual orchestration is in the
# `.claude/skills/issue-workflow/SKILL.md` skill for AI sessions,
# or driven manually by following CONTRIBUTING.md for humans.
.PHONY: next-issue
next-issue: ## Drive the next open issue end-to-end (branch → PR → squash-merge)
next-issue: ## Print the standing-orders reminder for ISSUE; humans drive the workflow from CONTRIBUTING.md
@if [ -z "$(ISSUE)" ]; then \
echo "usage: make next-issue ISSUE=<issue#>"; \
exit 1; \
fi
./scripts/issue-workflow.sh start $(ISSUE)
@echo "Issue #$(ISSUE) — drive the workflow per CONTRIBUTING.md:"
@echo " 1. Sync main and create the branch:"
@echo " git fetch origin main && git checkout main && git pull --ff-only origin main"
@echo " git checkout -b <type>/<scope>/<slug> origin/main"
@echo " 2. Implement + test + lint, commit with a Conventional Commits subject"
@echo " make ci"
@echo " 3. Push and open the PR:"
@echo " git push -u origin <branch>"
@echo " gh pr create --base main --head <branch> --title '<subject>' --body-file /tmp/pr-body-$(ISSUE).md"
@echo " 4. Watch CI and merge:"
@echo " gh pr checks <PR#> --watch --fail-fast"
@echo " make finish-pr ISSUE=$(ISSUE) PR=<PR#>"
@echo ""
@echo "Now: edit code, then commit with a conventional-commits subject."
@echo "When CI is green and you're ready to merge, run:"
@echo " make finish-pr ISSUE=$(ISSUE)"
@echo "AI sessions should invoke .claude/skills/issue-workflow/SKILL.md instead."

# Squash-merge the PR linked to ISSUE. Use after the branch is pushed
# and CI is green. Uses --admin because branch protection requires 1
Expand Down
47 changes: 30 additions & 17 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
# scripts/

Project-local automation. Currently:
Project-local automation. Currently empty — the `issue-workflow.sh`
shell script that used to live here was replaced by the
[`.claude/skills/issue-workflow/SKILL.md`](../.claude/skills/issue-workflow/SKILL.md)
skill, which drives a GitHub issue through branch → PR → merge →
cleanup using AI-orchestrated tasks instead of bash.

- `issue-workflow.sh` — drive a single GitHub issue through the full
branch → PR → merge → cleanup loop. Three subcommands:
- `start <issue#>` — sync `main` and create `<type>/<scope>/<slug>` off `origin/main`
- `pr-body <issue#>` — print a `.github/PULL_REQUEST_TEMPLATE.md`-shaped body
- `finish` — push, open PR, watch CI, squash-merge, delete branch, sync `main`
## Why a skill (and not a Makefile target or a shell script)

Enforces the conventions in `CONTRIBUTING.md` (Conventional Commits,
allowed types/scopes, one logical change per PR, squash-merge,
source-branch deletion). See `./issue-workflow.sh --help` for details.
The Makefile is for **build/test/lint/release** commands — anything
that produces artifacts you can ship. A shell script for the
branch → PR → merge loop is a poor fit because:

## Why a shell script (and not a Makefile target)
- It needs to parse Conventional Commits titles (fragile in bash ERE).
- It needs to call `gh` (couples the contributor's machine to GitHub
auth).
- It needs to watch CI and react to review comments — bash can't do
that without complex polling that paged output will eventually
break.
- The puku editor crashes when interleaved `gh` output is paged in
multiple terminal tabs (see `.conversation-history/.conversation-history-chunk4.md`
section 17 for the full diagnosis).

The Makefile is for **build/test/lint/release** commands — anything
that produces artifacts you can ship. `issue-workflow.sh` is a
**git/GitHub plumbing** helper: it talks to `gh`, reads PR titles,
force-pushes with `--force-with-lease`, and reshapes your local
checkout. Mixing those into `make` would couple the build to GitHub
auth and to `gh` being installed, which isn't true for every
contributor's machine.
A skill moves the orchestration into the AI: the assistant reads the
issue, edits files, runs validation, opens the PR, reads review
comments, and merges. The shell just executes commands, it doesn't
encode the workflow.

## When new scripts are appropriate

Add a new `scripts/<name>.sh` only for **build/test/release plumbing**
that needs to run on machines without an AI in the loop — e.g. CI
helpers, release packaging steps, local bootstrap. For anything
involving GitHub interaction or sequential decision-making, write a
skill instead.
Loading
Loading