| name | implement-issue |
| description | Phase 1 of the PLEXI ship pipeline. Finds an issue, sets up a worktree, implements the code changes, pushes the feature branch, then invokes /open-pr inline in the same pane. Entry points: /implement-issue (auto-find), /implement-issue <n> (specific), /implement-issue P1 (by priority), /implement-issue <n> <m> [...] (bundle). |
| risk | medium |
| source | local |
| date_added | 2026-05-20 |
Implement Issue
Phase 1 of the ship pipeline. Output: a pushed feature branch and an immediate inline handoff to /open-pr.
| Invocation | Behavior |
|---|
/implement-issue | Auto-find next unblocked issue (P0→P4) |
/implement-issue <n> | Specific issue |
/implement-issue P1 | First unblocked at that priority |
/implement-issue <n> <m> [...] | Bundle — implement multiple issues in one branch |
On completion, append a Ship Log entry to the issue body, set pipeline labels (see Pipeline Labels below), and output:
[IMPLEMENTED] Issue #<n>
Branch: feature/<n>-short-description
Files changed: <N>
Pipeline: pipeline:open-pr + ready set — invoking /open-pr inline
This skill is not complete when the branch is merely pushed. Completion includes invoking /open-pr inline in the same pane and letting the pipeline continue to validation.
Labels are live state — never the Ship Log. Pane: plexi${PLEXI_CHANNEL:+-$PLEXI_CHANNEL} pane name "#<n> · <state>" — no digit in status word (PM census uses grep -oE '[0-9]+'). States: impl, pushed, noop, blocked. Source .agents/skills/_lib/pipeline-slots.sh; call pipeline_slots_set implement <n> "" <status> "" "" at each boundary.
Stint timing. stint claim <task-id> in this skill; stint done runs in /merge-pr after merge. The installed CLI does not have stint start.
Phase 0 — Find the Issue
Skip this phase entirely when a specific issue number is provided as the argument. Go directly to Phase 1 with that number — no git log, no in-progress scan.
Run in parallel:
git log --oneline -10
gh issue list --label "in progress" --json number,title
Surface the in-progress list as context.
Determine search scope from argument:
- No arg → try P0, P1, then P2→P4 (no
ready filter at any level)
- Priority arg → search only that level
gh issue list --label "<priority>" --state open --json number,title,labels,body --limit 50
Sort ascending by issue number. For each:
- Skip if labeled
in progress
- If labeled
blocked: parse depends_on from front matter; skip if any dependency is OPEN. If all CLOSED: strip blocked, add ready
- Skip if body contains "Do not implement here" (epic tracker)
- Skip if body has unlabeled "Depends on" section with open issues — add
blocked label
- Otherwise: this is the target → proceed to Phase 1
Advance to next priority level if no unblocked issues found (no-arg mode only).
Front matter convention — every issue body opens with:
---
depends_on: []
---
Phase 1 — Pre-flight
Run in parallel:
git fetch origin && git status --porcelain && git log origin/alpha..HEAD --oneline
gh issue view <number> --json state,title,body,labels
Output immediately:
ISSUE #<n>: <title>
Hold the full issue body in context — Phase 3 uses it directly. Do not re-fetch.
Hard stops — check all before proceeding. Any failure exits immediately, no conditional paths:
- Dirty working tree → allowed and ignored. Worktrees are commit-isolated — dirty files in the main tree cannot bleed into a worktree checkout. Do NOT stop, do NOT stash. The only hard stop is an in-progress merge or rebase (check
test -f .git/MERGE_HEAD || test -f .git/REBASE_HEAD) which puts HEAD itself in an indeterminate state.
- In-progress merge or rebase → STOP. "Cannot create worktree: HEAD is mid-merge/rebase. Resolve first."
- Issue is
CLOSED → set pane noop, stop. "Issue # is already closed."
- Issue is labeled
in progress → STOP. "ERROR: issue # is already in progress." Surface existing worktree + any open PR. Do not proceed.
Then run:
git ls-remote origin "refs/heads/feature/<issue-number>-*"
Skip git pull --rebase entirely — the worktree always branches from local HEAD (which includes any unpushed commits), so pulling is unnecessary and risks reconciling unrelated work.
If ls-remote is non-empty: another agent owns this branch. Surface it and any existing PR, then stop.
Label + pane setup + worktree creation — run in sequence:
gh issue edit <number> --add-label "in progress" --add-label "pipeline:implement"
plexi${PLEXI_CHANNEL:+-$PLEXI_CHANNEL} pane name "#<number> · impl"
pipeline_slots_set implement <number> "" working "" ""
IMPL_PANE=$PLEXI_PANE_ID
wtp add -b feature/<issue-number>-short-description HEAD
Start linked stint tasks immediately after the issue enters implementation:
- Find tasks whose frontmatter links the issue:
rg -l 'gh_issue: .*"<number>"|gh_issue: .*\\[<number>\\]' .stint/tasks
- For each linked task that will be materially worked, run:
stint claim <task-id>
- Do not use
--restart unless deliberately replacing bad timing data; normal resumed work keeps the original started_at.
- For historical backfill only, use
stint claim <task-id> --started-at <UTC-RFC3339>.
- If no linked task exists, continue but note the missing stint linkage in the issue Ship Log; do not invent timing only in GitHub.
If "branch already exists": check git worktree list. If no worktree, wtp add without -b. Check for prior commits.
Verify base immediately:
git -C worktrees/<branch> log --oneline -1
git log --oneline -1
If mismatch: delete worktree and branch, redo.
Phase 2 — Formulate
Before writing code, produce a tight implementation spec.
Issue body is already in context from Phase 1. Do not re-fetch.
For bundles: fetch the additional issue bodies in parallel now:
gh issue view <n2> --json title,body,labels > /tmp/issue_n2.json &
wait && cat /tmp/issue_n*.json && rm /tmp/issue_n*.json
Implementation Map — verify, then trust. If the issue body has an ## Implementation Map section, it lists the exact files to touch (written by /create-stint during its codebase research). Do NOT re-run the broad grep/discovery sweep. Instead:
- Cheap existence check — one batched pass confirming each mapped path still exists and each named symbol is still present:
git -C worktrees/<branch> ls-files <path1> <path2> ...
grep -rn "<fn1>\|<fn2>" worktrees/<branch>/<mapped-files>
- Every path/symbol resolves → read ONLY the mapped files. Skip GOTCHAS grep. Common case; skips discovery entirely.
- A path is missing, a symbol moved, or the map is absent → the issue drifted since filing. Fall back to full discovery (grep affected modules, then read) for the unresolved parts only, note "Map stale: " in the spec, and run the GOTCHAS grep below.
Grep GOTCHAS.md — only when doing full discovery (step 3 above):
grep -in "<term1>\|<term2>\|<term3>" GOTCHAS.md
Surface every match under Gotchas found:. Each requires disposition: NOTED or N/A.
If any changed file is a Python app under apps/ or apps/dev/: invoke create-plexi-app skill before writing code.
If issue involves a third-party library: invoke coding-conventions skill.
Write implementation spec (in context only, not to disk):
Files to change:
- src/foo.rs:42 — <exactly what>
Files NOT to touch:
- <adjacent but out of scope>
CLI rename check:
- grep -rn "<old-command>" .claude/skills/ if renaming a subcommand
Cheapest verification before commit:
- <specific command, e.g. cargo test --bin plexi <test_name>, cargo build, or diff-only review>
- Prefer a targeted test when one already exists or the change adds logic with a clear test seam.
- For tiny, one-file UI wiring or copy changes with no practical harness coverage, use `cargo build` or a diff review instead of starting a broad cargo test run.
Invariants to preserve:
- <constraints from GOTCHAS.md or CLAUDE.md>
Assumptions to validate:
- <what must be true> — validated by: <cheapest check>
Logging plan (required):
- Every new AppRequest/HostEffect/DrawCommand handler → log::info! at entry
- Every user-visible state change → log::info! with what changed
- Every early-return bail-out → log::warn! naming app/command/reason
- Every unrecoverable failure → log::error! with full context
Phase 3 — Implement
Worktree path rule: All Read/Edit/Write tool calls must target the WORKTREE absolute path. Never the repo root.
Scope gate:
- ≤ 3 files: implement inline. Add/update a focused test when the change has host logic, a new branch, a bug fix with a reproducible condition, or an existing nearby test seam. Run the cheapest verification from the spec, not a reflexive broad test.
-
3 files or multiple subsystems: dispatch Sonnet subagent.
Subagent dispatch
Prompt must include: full implementation spec, contents of every file it will touch, worktree path, and the rule "stage changes but do NOT commit."
Report back: DONE | DONE_WITH_CONCERNS <details> | NEEDS_CONTEXT <what> | BLOCKED <reason>
DONE: review staged diff, run the cheapest verification from the spec, commit.
DONE_WITH_CONCERNS: read concerns first.
NEEDS_CONTEXT: provide + redispatch.
BLOCKED: provide context or escalate.
Self-validation — /testing
After cargo build passes and before pushing, run the /testing skill (.agents/skills/testing/SKILL.md): classify the diff, run the matching harness tests, generate headless render screenshots for visual changes, and produce the **Test evidence:** block. That block goes into the Ship Log entry below and is what lets validate-pr skip binary install for fully-covered PRs.
Orchestrator owns the commit:
git -C worktrees/<branch> add <files>
git -C worktrees/<branch> commit -m "<message>"
git -C worktrees/<branch> push -u origin HEAD
Bundle mode: branch name feature/bundle-<n1>-<n2>, implement and commit issues sequentially so each commit is independently bisectable:
- For each issue (N1, N2, ...), implement changes and commit individually:
git -C worktrees/<branch> add <files>
git -C worktrees/<branch> commit -m "fix/feat: <issue description> (#<issue_number>)"
- Push all commits:
git -C worktrees/<branch> push -u origin HEAD
- After pushing, write a Ship Log entry to each issue body (see Ship Log Format).
- Set Pipeline Labels on ALL N issues (see Pipeline Labels).
Ship Log Format
After pushing, append this section to the issue body. In bundle mode, write a Ship Log entry to each issue body. If a ## Ship Log section already exists (prior attempt), append a new entry under it. If not, add the section.
Do not run stint done in this skill. A pushed implementation is not task completion; validation and merge can still fail. /merge-pr closes linked stint tasks after the PR merges and alpha is verified.
## Ship Log
### Attempt <N> — <YYYY-MM-DD>
**Branch:** feature/<n>-short-description
**Stint:** started <task-id> at <UTC ISO-8601 timestamp>, or missing linked task
**Files changed:** <list key files>
**Spec summary:** <one-line description of approach>
**Test evidence (attempt <N>):**
- cargo test: <passed> passed, <failed> failed — filters: <module list or "full bin suite">
- PlexiUiHarness render: /tmp/plexi-render-<issue>-<name>.png — <what it shows>
- Conclusion: install skippable — full coverage | binary install required — <why>
Append with:
CURRENT_BODY=$(gh issue view <number> --json body --jq '.body')
ATTEMPT_N=$(printf '%s' "$CURRENT_BODY" | grep -c '^### Attempt ' || true)
ATTEMPT_N=$((ATTEMPT_N + 1))
ATTEMPT_BLOCK=$(printf '### Attempt %s — %s\n**Branch:** feature/<branch>\n**Stint:** started <task-id> at <started_at>, or missing linked task\n**Files changed:** <files>\n**Spec summary:** <summary>' "$ATTEMPT_N" "$(date +%Y-%m-%d)")
if printf '%s' "$CURRENT_BODY" | grep -q '^## Ship Log$'; then
NEW_BODY=$(printf '%s\n\n%s' "$CURRENT_BODY" "$ATTEMPT_BLOCK")
else
NEW_BODY=$(printf '%s\n\n## Ship Log\n\n%s' "$CURRENT_BODY" "$ATTEMPT_BLOCK")
fi
gh issue edit <number> --body "$NEW_BODY"
Pipeline Labels
After pushing and writing the Ship Log, set pipeline state on every issue in the bundle (or the single issue in single-issue mode):
gh issue edit <N> \
--add-label "pipeline:open-pr" \
--add-label "ready" \
--remove-label "pipeline:implement" \
--remove-label "in progress"
Set the pane status to pushed, then invoke /open-pr inline in the same pane — do not spawn a new pane or wait for PM to dispatch:
plexi${PLEXI_CHANNEL:+-$PLEXI_CHANNEL} pane name "#<n> · pushed"
pipeline_slots_set implement <n> "" pushed "" ""
/open-pr <branch-or-issue>
Rules
- When a specific issue number is given, skip Phase 0 entirely
- If the issue body contains a complete
## Action Plan with named files, trust it. Do not re-read and re-grep those files to re-derive the plan.
- Never branch from main — always from repo root (alpha)
- Never skip base verification after
wtp add
- No
todo!() or unimplemented!() outside #[cfg(test)]
- No
#[allow(dead_code)] or #[allow(unused)]
cargo build must pass after all changes
/testing is mandatory before pushing when any src/ host logic or apps/ Python app code was touched. For src/ changes, this means cargo test --bin plexi; for apps/ changes, this means the PGAP-layer evidence (real-process headless screenshot). The **Test evidence:** block must be in the Ship Log before /open-pr is invoked. For docs-only, skills-only, or config-only changes, write "docs-only — no test evidence required" in place of the block.
- Subagents stage only — orchestrator owns the commit
- Never dispatch subagent without the Phase 3 spec
- Every implementation needs a logging plan
- CLI changes must update
~/.claude/skills/plexi-cli/SKILL.md in same PR
cargo build --manifest-path <worktree>/Cargo.toml — never rely on CWD
- Start linked stint tasks here; never mark them done here.
/merge-pr owns completion timing.
- After pushing, always invoke
/open-pr inline before considering this skill complete.
- On unrecoverable failure: set
plexi${PLEXI_CHANNEL:+-$PLEXI_CHANNEL} pane name "#<n> · blocked", run pipeline_slots_set implement <n> "" blocked "" "<error summary>", close PR if open, comment on issue under ## Prior Attempts, remove in progress, add ready, exit