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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions .claude/skills/feature-pipeline-preview/03-adr-draft/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
---
name: 03-adr-draft
description: Drafts the Architecture Decision Record for the feature in Status Proposed.
description: Drafts the ADR (Status Proposed) and opens a draft PR so the maintainer can review the ADR on GitHub.
kind: leaf
executor: opus
model: claude-opus-4-7
---

# ADR draft

Produces `docs/decisions/NNNN-<slug>.md` matching the NeoHaskell ADR template, with Status: Proposed. The ADR is the input to every later phase (security, perf, devex, architecture, test spec).
Produces `docs/decisions/NNNN-<slug>.md` matching the NeoHaskell ADR template, with Status: Proposed, then commits it and opens a **draft PR** so the maintainer reviews the ADR on GitHub. The ADR is the input to every later phase (security, perf, devex, architecture, test spec); the draft PR is converted to ready for review at the PR gate (phase 16) — this phase never opens a second PR.

## Inputs

- `adr_number` — 4-digit string. The orchestrator derives the next number by parsing the 4-digit numeric prefix of every `docs/decisions/NNNN-*.md` file, taking the max, and adding one (zero-padded to 4 digits). Empty directory yields `0001`. Non-numeric or short prefixes are ignored.
- `feature_name` — string.
- `issue_number` — string.
- `module_path` — string.
- `branch_name` — string. The feature branch created in phase 1; the draft PR is opened from it.
- `slug` — string. The kebab-case slug used in the ADR filename.

## Plan

1. Read the existing `docs/decisions/` directory → verify: the chosen ADR number is unused.
2. Read the `neohaskell-adr-template` skill if needed → verify: section list matches the template (Context, Decision drivers, Considered options, Decision outcome, Public API, Consequences).
3. Draft the ADR with every required section, every code example following the NeoHaskell style guide → verify: file written and contains `Status: Proposed`.
4. Mark phase 3 complete → verify: `pipeline.py status` shows phase 3 awaiting approval.
4. Commit the ADR and open a draft PR → verify: `pipeline.py get pr_url` returns a URL.
5. Mark phase 3 complete → verify: `pipeline.py status` shows phase 3 awaiting approval.

Assumptions:
- `docs/decisions/` exists.
- The maintainer will review the ADR before approving phase 3.
- The feature branch (`branch_name`) was created in phase 1; at this point the working tree contains only the ADR.
- `gh` is on PATH and the repository supports draft PRs.
- The maintainer will review the ADR in the draft PR before approving phase 3.

If any assumption fails (style guide unclear, ADR template missing), refuse and ask.
If any assumption fails (style guide unclear, ADR template missing, `gh` absent), refuse and ask.

## Steps

Expand All @@ -37,15 +42,27 @@ If any assumption fails (style guide unclear, ADR template missing), refuse and
3. Apply the NeoHaskell style: pipes, do-blocks, `case ... of`, `Task`/`Result`, no `let..in`, no `where`, no `$`, no single-letter type params. Refuse if the chosen API forces any of these.
4. Apply the Jess persona check (`../references/jess-persona.md`): every public function reachable from the API block should be usable in 15 minutes from autocomplete alone. Refuse the design if it is not.
5. Write the file with `Status: Proposed`.
6. Run `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py complete 3`.
6. Open the draft PR so the maintainer can review the ADR on GitHub:
1. `BRANCH=$(git branch --show-current)`. If `$BRANCH = main`, refuse: "cannot open a PR from main".
2. `command -v gh >/dev/null 2>&1` — if non-zero, refuse: "gh not found".
3. Stage and commit the ADR (plus any `docs/decisions/README.md` index update): `git add docs/decisions/` then `git commit -m "docs(adr): add ADR-<adr_number> <slug> (#<issue_number>)"`.
4. Push the branch: `git push -u origin "$BRANCH"`.
5. Write `.pipeline/draft-pr-body.md`: a short body stating the ADR `docs/decisions/<adr_number>-<slug>.md` is open for review, that implementation lands in later commits, that the PR is marked Ready for review at the pipeline's PR gate (phase 16), and a `Closes #<issue_number>` line.
6. Open the draft PR (provisional title; phase 16 finalizes it): `URL=$(gh pr create --draft --title "<feature_name> (#<issue_number>)" --body-file .pipeline/draft-pr-body.md)`.
7. `NUM=$(gh pr view --json number -q .number)`.
8. Store both: `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py set pr_url "$URL"` and `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py set pr_number "$NUM"`.
7. Run `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py complete 3`.

## Output

- `docs/decisions/<adr_number>-<slug>.md` exists, Status: Proposed, all template sections present.
- Phase 3 marked complete; pipeline now in `waiting_for_approval` for phase 3.
- `docs/decisions/<adr_number>-<slug>.md` exists, Status: Proposed, all template sections present, committed.
- A **draft PR** is open against the feature branch; `pr_url` and `pr_number` are stored in pipeline state.
- Phase 3 marked complete; pipeline now in `waiting_for_approval` for phase 3 — the maintainer reviews the ADR in the draft PR.

## Refusals

- ADR path already exists → stop, do not overwrite.
- Current branch is `main` → refuse: "cannot open a PR from main".
- `gh` not on PATH → refuse: "gh not found".
- Design forces NeoHaskell-style violations → surface the conflict; do not silently rewrite the API.
- Feature fails the Jess 15-minute test → surface the failure and request a redesign before drafting.
Original file line number Diff line number Diff line change
@@ -1,53 +1,54 @@
---
name: 02-submit
description: Stages, commits, pushes the branch, opens the PR, and records the PR URL and number.
description: Commits and pushes the implementation, updates the phase-3 draft PR's title/body, and marks it ready for review.
kind: leaf
executor: script
model: claude-haiku-4-5-20251001
---

# Submit PR
# Submit PR (mark draft ready)

Stages the changes, commits, pushes the branch, opens the PR via `gh`, and stores the resulting URL and number.
Commits and pushes the implementation accumulated since phase 3, updates the existing **draft PR** (opened in phase 3) with the final title and body, and marks it ready for review. It does not create a new PR.

## Inputs

- `.pipeline/pr-title.txt`
- `.pipeline/pr-body.md`
- Pipeline state `pr_number` and `pr_url` (set in phase 3 when the draft PR was opened).

## Plan

1. Confirm the current branch is not `main` → verify: `git branch --show-current` differs from `main`.
2. Stage and commit changed files → verify: `git status` is clean afterwards.
3. Push the branch with `-u` → verify: remote tracking branch set.
4. Run `gh pr create` and capture the URL → verify: URL stored via `pipeline.py set pr_url`.
2. Confirm the phase-3 draft PR exists → verify: `pipeline.py get pr_number` is non-empty.
3. Commit and push the implementation → verify: branch pushed, `git status` clean.
4. Update the PR title/body and mark it ready → verify: `gh pr view --json isDraft` is `false`.

Assumptions:
- Phase 3 opened the draft PR and stored `pr_number`/`pr_url`.
- The maintainer has not opted into `--no-verify` or signing bypasses; do not pass those flags.
- The branch hook will reject a commit on `main`.

If any assumption fails, refuse — do not guess.

## Steps

1. Run: `BRANCH=$(git branch --show-current)`. If `$BRANCH = main`, refuse: "cannot PR from main".
1. Run: `BRANCH=$(git branch --show-current)`. If `$BRANCH = main`, refuse: "cannot finalize PR from main".
2. Run: `command -v gh >/dev/null 2>&1` — if non-zero, refuse: "gh not found" and exit non-zero.
3. Stage changed files: `git add -u` plus any new files explicitly listed in the architecture doc.
4. Read title from `.pipeline/pr-title.txt`.
5. Commit: `git commit -m "$(cat .pipeline/pr-title.txt)"`.
3. Read the draft PR number: `NUM=$(python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py get pr_number)`. If empty, refuse: "no draft PR from phase 3 — phase 3 must open the draft PR first".
4. Stage changed files: `git add -u` plus any new files explicitly listed in the architecture doc.
5. Commit the implementation: `git commit -m "$(cat .pipeline/pr-title.txt)"`. If there is nothing to commit, continue (the implementation may already be committed).
6. Push: `git push -u origin "$BRANCH"`.
7. Run: `URL=$(gh pr create --title "$(cat .pipeline/pr-title.txt)" --body-file .pipeline/pr-body.md)`.
8. Extract the PR number: `NUM=$(gh pr view --json number -q .number)`.
9. Run `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py set pr_url "$URL"`.
10. Run `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py set pr_number "$NUM"`.
11. Run `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py complete 16`.
7. Update the existing PR's title and body: `gh pr edit "$NUM" --title "$(cat .pipeline/pr-title.txt)" --body-file .pipeline/pr-body.md`.
8. Convert the draft to ready for review: `gh pr ready "$NUM"`.
9. Run `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py complete 16`.

## Output

PR opened; `pr_url` and `pr_number` stored in pipeline state; phase 16 marked complete.
Implementation committed and pushed; the phase-3 draft PR updated to its final title/body and marked ready for review; `pr_url`/`pr_number` unchanged; phase 16 marked complete.

## Refusals

- Current branch is `main` → refuse: "cannot PR from main".
- Current branch is `main` → refuse: "cannot finalize PR from main".
- `gh` not on PATH → refuse: "gh not found".
- `pr_number` unset → refuse: "no draft PR from phase 3 — open it in phase 3 first".
- Either input file missing → refuse: "PR body or title file missing".
9 changes: 5 additions & 4 deletions .claude/skills/feature-pipeline-preview/16-create-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
---
name: 16-create-pr
description: Drafts the PR body and submits the pull request, then pauses for maintainer approval.
description: Finalizes the PR — writes the final body/title, commits and pushes the implementation, and marks the phase-3 draft PR ready for review.
kind: process
executor: haiku
model: claude-haiku-4-5-20251001
---

# Create PR
# Finalize PR (mark ready)

Drafts a PR body and submits the pull request.
Generates the final PR body and title, commits and pushes the implementation, then converts the **draft PR opened in phase 3** to ready for review. Phase 3 already opened the draft PR (for the ADR review); this phase updates its title/body and marks it ready — it never creates a second PR.

## Steps

1. **PR body** — spawn an Agent (model: sonnet) and instruct it to read `./01-pr-body/SKILL.md` and follow it. Verify: both `.pipeline/pr-body.md` and `.pipeline/pr-title.txt` exist — submit needs the title file.
2. **Submit** — read `./02-submit/SKILL.md` and follow it. Verify: PR URL captured in pipeline state; phase 16 marked complete.
2. **Submit** — read `./02-submit/SKILL.md` and follow it. Verify: `pipeline.py get pr_url` returns the phase-3 draft PR URL, the PR is no longer a draft (`gh pr view --json isDraft -q .isDraft` is `false`), and phase 16 marked complete.

Walk these steps in order. After step 2, PAUSE — wait for explicit maintainer approval before phase 17 runs (Opus PR review).

## Shared invariants

- The draft PR already exists (opened in phase 3); this phase updates its title/body and marks it ready — it never opens a second PR. Refuse if `pr_number`/`pr_url` is unset.
- PR body targets Jess as the reader (community-writer voice).
- Title follows conventional-commit format.
- Closes the originating issue with `Closes #N`.
Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/feature-pipeline-preview/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The pipeline has two distinguishing characteristics:

- All state lives in `.pipeline/state.json` and `.pipeline/findings-*.json`. Drive via `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py <cmd>`.
- The pipeline must be initialised before any phase runs. Phase 1 calls `pipeline.py init`.
- PAUSE-gated phases (marked 🔒 below) stop the orchestrator until the maintainer runs `pipeline.py approve <N>`. The PAUSE remains only on phases 3 (ADR draft), 16 (PR creation), 17 (Opus PR review), and 18 (CI cycle / merge) — every other phase auto-gates on its rubric or findings.
- PAUSE-gated phases (marked 🔒 below) stop the orchestrator until the maintainer runs `pipeline.py approve <N>`. The PAUSE remains only on phases 3 (ADR draft — opens a draft PR for review), 16 (PR finalize — marks the draft ready), 17 (Opus PR review), and 18 (CI cycle / merge) — every other phase auto-gates on its rubric or findings.
- Every step reads [`references/jess-persona.md`](./references/jess-persona.md) and [`references/nhcore-context.md`](./references/nhcore-context.md) when delegating to a sonnet child.
- The grounding pass is mandatory — it cannot be skipped to "save time", and it cannot be merged with the deep-audit step.
- The review-quality steps on phases 6/7/8 are mandatory and must be run by a different agent invocation from the producer step (independence is what makes the gate trustworthy).
Expand All @@ -30,7 +30,7 @@ The pipeline has two distinguishing characteristics:

1. **Init pipeline** — read `./01-init-pipeline/SKILL.md` and follow it with `{feature_name, issue_number, module_path, test_path, branch_name, adr_number}`. Verify: `.pipeline/state.json` exists and `pipeline.py status` lists phase 1 as completed.
2. **Classify feature** — spawn an Agent (model: haiku) and instruct it to read `./02-classify-feature/SKILL.md` and follow it. Verify: `.pipeline/classification.json` exists with `tier ∈ {trivial, simple, moderate, complex, security-critical}`.
3. **ADR draft 🔒** — spawn an Agent (model: opus) and instruct it to read `./03-adr-draft/SKILL.md` and follow it. Verify: `docs/decisions/NNNN-slug.md` exists with Status: Proposed. Then `pipeline.py complete 3` and stop until `pipeline.py approve 3`.
3. **ADR draft 🔒** — spawn an Agent (model: opus) and instruct it to read `./03-adr-draft/SKILL.md` and follow it. Verify: `docs/decisions/NNNN-slug.md` exists with Status: Proposed, the ADR is committed, and a **draft PR** is open (`pipeline.py get pr_url` returns a URL). Then `pipeline.py complete 3` and stop until `pipeline.py approve 3` — the maintainer reviews the ADR in the draft PR.
4. **Security review (ADR)** — spawn an Agent (model: haiku) and instruct it to read `./04-security-adr/SKILL.md` and follow it. Verify: `.pipeline/findings-04.json` exists with `blockers >= 0`. If `blockers > 0`, stop and surface to maintainer.
5. **Performance review (ADR)** — spawn an Agent (model: haiku) and instruct it to read `./05-performance-adr/SKILL.md` and follow it. Verify: `.pipeline/findings-05.json` exists. May run in parallel with step 4.
6. **DevEx review** — spawn an Agent (model: haiku) and instruct it to read `./06-devex-review/SKILL.md` and follow it. Verify: `.pipeline/devex-review-rubric.json` exists with `"verdict": "pass"`. On `fail`, the rubric record names the failing checks and the pipeline halts here.
Expand All @@ -43,11 +43,11 @@ The pipeline has two distinguishing characteristics:
13. **Performance review (impl)** — spawn an Agent (model: haiku) and instruct it to read `./13-performance-impl/SKILL.md` and follow it. Verify: `.pipeline/findings-13.json` exists. May run in parallel with step 12.
14. **Fix findings** — spawn an Agent (model: sonnet) and instruct it to read `./14-fix-findings/SKILL.md` and follow it. Verify: `blockers == 0` across findings-12 and findings-13 after fixes; `cabal test` still green.
15. **Final verify** — spawn an Agent (model: haiku) and instruct it to read `./15-final-verify/SKILL.md` and follow it. Verify: clean build, all tests pass; `.pipeline/hlint.log` refreshed (hlint is captured for the PR body, not gated).
16. **Create PR 🔒** — spawn an Agent (model: haiku) and instruct it to read `./16-create-pr/SKILL.md` and follow it. Verify: `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py get pr_url` returns a URL. The submit leaf calls `pipeline.py complete 16` itself; then stop until the maintainer runs `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py approve 16`.
16. **Finalize PR (mark ready) 🔒** — spawn an Agent (model: haiku) and instruct it to read `./16-create-pr/SKILL.md` and follow it. It commits/pushes the implementation, sets the final title/body, and converts the **draft PR opened in phase 3** to ready for review (`gh pr ready`) — it does NOT create a second PR. Verify: `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py get pr_url` returns the draft PR URL and `gh pr view --json isDraft -q .isDraft` is `false`. The submit leaf calls `pipeline.py complete 16` itself; then stop until the maintainer runs `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py approve 16`.
17. **Opus PR review 🔒** — spawn an Agent (model: opus) and instruct it to read `./17-opus-pr-review/SKILL.md` and follow it. Verify: `.pipeline/findings-17.json` exists. Then PAUSE until `pipeline.py approve 17`.
18. **CI cycle** — spawn an Agent (model: haiku) and instruct it to read `./18-ci-cycle/SKILL.md` and follow it. Verify: CI green and PR merged.

Phase completion is owned by each phase's own skill — the orchestrator never calls `pipeline.py complete <N>` unconditionally. Rubric-gated phases (6, 7, 8) and the script-style record leaves (2, 4, 5, 12, 13) call `complete <N>` only when the gate passes; the test-writing, implementation, build-loop, fix-findings, final-verify, and create-PR phases call it from their last step on success. PAUSE-gated phases (3, 16, 17, 18) then stop and wait for the maintainer's `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py approve <N>` before continuing.
Phase completion is owned by each phase's own skill — the orchestrator never calls `pipeline.py complete <N>` unconditionally. Rubric-gated phases (6, 7, 8) and the script-style record leaves (2, 4, 5, 12, 13) call `complete <N>` only when the gate passes; the test-writing, implementation, build-loop, fix-findings, final-verify, and PR-finalize phases call it from their last step on success. PAUSE-gated phases (3, 16, 17, 18) then stop and wait for the maintainer's `python3 .claude/skills/feature-pipeline-preview/scripts/pipeline.py approve <N>` before continuing.

## Refusals

Expand Down
Loading