Skip to content

Commit 55a8670

Browse files
authored
Merge pull request #25 from stranma/feat/add-landed-skill
Add /landed skill for post-merge lifecycle
2 parents 0a9f904 + bf5bd65 commit 55a8670

File tree

10 files changed

+178
-67
lines changed

10 files changed

+178
-67
lines changed

.claude/commands/catchup.md

Lines changed: 0 additions & 48 deletions
This file was deleted.

.claude/deploy.json.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"__comment": "Copy to .claude/deploy.json and customize. Used by /landed.",
3+
"environments": [
4+
{"name": "dev", "workflow": "deploy-dev.yml"},
5+
{"name": "staging", "workflow": "deploy-staging.yml", "health_check": "https://staging.example.com/health"}
6+
]
7+
}

.claude/skills/landed/SKILL.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
name: landed
3+
description: Post-merge lifecycle. Verifies merge CI, optional deployment checks, cleans up branches, and prepares next phase.
4+
allowed-tools: Bash, Read, Grep, Glob
5+
disable-model-invocation: true
6+
---
7+
8+
# Landed
9+
10+
Post-merge lifecycle command. Run this after a PR is merged to verify CI, check deployments, clean up branches, and identify next steps.
11+
12+
## Step 1: Detect Merged PR
13+
14+
Identify the PR that was just merged.
15+
16+
1. Run `git branch --show-current` to get the current branch
17+
2. If already on master:
18+
- Check `git reflog --oneline -20` for the previous branch name
19+
- If no branch found, ask the user for the PR number or branch name
20+
3. Look up the merged PR:
21+
22+
```bash
23+
gh pr list --state merged --head <branch> --json number,title,mergeCommit -L 1
24+
```
25+
26+
4. If no PR found: ask the user for the PR number directly
27+
5. Display: PR number, title, merge commit SHA
28+
29+
**Pre-check**: Run `gh auth status` early. If not authenticated, stop and instruct the user to run `gh auth login`.
30+
31+
## Step 2: Verify Merge CI
32+
33+
Check that CI passed on the merge commit.
34+
35+
1. List recent runs on master:
36+
37+
```bash
38+
gh run list --branch master -L 20 --json status,conclusion,databaseId,name,headSha
39+
```
40+
41+
2. Filter to runs whose `headSha` matches the merge commit SHA
42+
3. Evaluate all matched runs:
43+
- **in_progress**: watch still-running run(s) with `gh run watch <id>`
44+
- **success**: all matched runs must be `completed` with `conclusion=success` to proceed
45+
- **failure**: show details via `gh run view <id> --log-failed` for each failing run
46+
- Ask: "Is this a recurring issue or specific to this PR?"
47+
- If recurring: suggest adding to `/done` validation or pre-merge CI
48+
- If specific: diagnose inline from the failed log output
49+
50+
## Step 3: Deployment Verification (Configurable)
51+
52+
Check for deployment status if configured.
53+
54+
1. Check if `.claude/deploy.json` exists
55+
2. If it exists:
56+
- Read the file and iterate over configured environments
57+
- For each environment:
58+
- Watch the deployment workflow: `gh run list --workflow <workflow> --commit <merge-commit-sha> --json status,conclusion,databaseId`
59+
- If `health_check` URL is configured, fetch it and verify a 200 response
60+
- Report per-environment status (success/failure/in_progress)
61+
3. If no config file:
62+
- Ask the user: "Is there a deployment to verify? (skip if none)"
63+
- If user says no or skips: mark as "skipped"
64+
65+
## Step 4: Branch Cleanup
66+
67+
Switch to master and clean up the feature branch.
68+
69+
1. `git checkout master && git pull --rebase`
70+
2. Delete local branch: `git branch -d <branch>` (safe delete, will fail if unmerged)
71+
3. Check if remote branch still exists: `git ls-remote --heads origin <branch>`
72+
4. If remote branch exists:
73+
- Ask the user before deleting: "Delete remote branch origin/<branch>?"
74+
- If approved: `git push origin --delete <branch>`
75+
- If denied: note "kept" in summary
76+
5. If remote branch already deleted (e.g., GitHub auto-delete): note "already deleted by GitHub" in summary
77+
78+
**Edge case**: If already on master and the branch was already deleted locally, skip local deletion gracefully.
79+
80+
## Step 5: Next Phase (P-scope Only)
81+
82+
Check if there is more planned work.
83+
84+
1. Read `docs/IMPLEMENTATION_PLAN.md`
85+
2. If the file exists, check the "Quick Status Summary" table near the top for any phase whose status is not "Complete":
86+
- Identify the next incomplete phase
87+
- Summarize what it covers and any noted dependencies
88+
3. If all phases show "Complete" or no plan file exists: skip this step
89+
90+
## Step 6: Summary Report
91+
92+
Output a summary of everything that happened:
93+
94+
```text
95+
# Landed
96+
97+
PR: #N "<title>" merged into master
98+
CI: PASS (run #ID) | FAIL (run #ID) | WATCHING
99+
Deploy: verified / skipped / failed
100+
101+
## Cleanup
102+
- Deleted local branch: <branch>
103+
- Deleted remote branch: <branch> [or "kept" or "already deleted by GitHub"]
104+
- Now on: master (up to date)
105+
106+
## Next Steps
107+
- [next phase summary / "Ready for new work" / "Project complete"]
108+
```
109+
110+
## Edge Cases
111+
112+
- **Already on master**: check `git reflog` for previous branch, or ask the user
113+
- **PR not found via branch name**: ask the user for the PR number
114+
- **Remote branch already deleted**: GitHub auto-delete is common; handle gracefully
115+
- **gh not authenticated**: check `gh auth status` early and stop with instructions
116+
- **No CI runs found**: report "no CI runs found for merge commit" and proceed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Thumbs.db
4545

4646
# Claude Code local overrides
4747
.claude/settings.local.json
48+
.claude/deploy.json
4849
.claude/hooks/*.log
4950
CLAUDE.local.md
5051

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Development Process
44

5-
Use `/sync` before starting work, `/design` to formalize a plan, and `/done` when finished. `/design` estimates scope (Q/S/P) during planning; `/done` auto-detects actual scope at completion based on workspace signals. Before creating any plan, read `docs/DEVELOPMENT_PROCESS.md` first.
5+
Use `/sync` before starting work, `/design` to formalize a plan, `/done` when finished, and `/landed` after the PR merges. `/design` estimates scope (Q/S/P) during planning; `/done` auto-detects actual scope at completion based on workspace signals. Before creating any plan, read `docs/DEVELOPMENT_PROCESS.md` first.
66

77
## Security
88

docs/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- `/landed` skill for post-merge lifecycle -- verifies merge CI, optionally checks deployments (via `.claude/deploy.json`), cleans up feature branches, and identifies the next phase for P-scope work
12+
- `.claude/deploy.json.example` template for configuring deployment verification in `/landed`
1113
- Chain-of-Verification (CoVe) commands (`/cove`, `/cove-isolated`) for high-stakes accuracy -- 4-step self-verification process based on Meta's CoVe paper, with an isolated variant that runs verification in a separate agent to prevent confirmation bias
1214
- Template sync workflow (`.github/workflows/template-sync.yml`) for downstream projects to auto-sync upstream template improvements -- runs weekly or on manual trigger, creates PRs with changed template-managed files while preserving project-specific code
1315
- Python-specific SOLID checklist in `refactoring-specialist` agent -- checks for mutable default arguments, ABC/Protocol misuse, missing dependency injection, god classes, `@property` overuse, and circular imports
@@ -18,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1820
- Workflow skill `/done` auto-detects scope (Q/S/P) and runs the full validate-ship-document pipeline, including the former `/ship` checklist
1921
- Three graduated permission tiers (Assisted, Autonomous, Full Trust) for devcontainer environments -- container isolation (firewall, non-root, hooks) enables safely expanding Claude Code permissions, reducing unnecessary prompts from dozens per session to zero in Tier 2/3 while blocking tool installation, package publishing, and container escape vectors via curated deny lists and a policy-enforcement hook
2022
- 5 hook scripts in `.claude/hooks/` run automatically during Claude Code sessions -- 3 security hooks block destructive commands, secret leaks, and invisible Unicode attacks in real time; 2 productivity hooks auto-format Python files and auto-run associated tests after every edit
21-
- 4 slash commands (`/catchup`, `/cove`, `/cove-isolated`, `/security-audit`) provide context restoration, chain-of-verification for accuracy, and a 6-phase security posture scan with A-F grading
23+
- 3 slash commands (`/cove`, `/cove-isolated`, `/security-audit`) provide chain-of-verification for accuracy and a 6-phase security posture scan with A-F grading
2224
- 3 new specialized agents: `security-auditor` (OWASP-based vulnerability analysis, read-only), `refactoring-specialist` (SOLID/code smell detection, read-only), `output-evaluator` (LLM-as-Judge quality scoring for automated pipelines)
2325
- 4 review rules in `.claude/rules/` auto-loaded as project context -- cover architecture, code quality, performance, and test quality concerns that linters cannot catch
2426
- AI-powered PR review via GitHub Actions (`claude-code-review.yml`) using `anthropics/claude-code-action@v1` -- automatically reviews PRs with read-only tools on open/sync/ready_for_review
@@ -36,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3638

3739
### Changed
3840

41+
- Development workflow expanded from sync-design-done to sync-design-done-landed, closing the post-merge gap
3942
- QSP scope classification is now auto-detected by `/done` based on branch, diff size, and IMPLEMENTATION_PLAN.md state -- users no longer classify manually before starting work
4043
- PCC shorthand now triggers `/done` instead of manually executing S.5-S.7
4144
- Setup script now makes `.claude/hooks/*.sh` files executable after placeholder substitution -- hook scripts work immediately after project setup without manual `chmod`
@@ -50,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5053

5154
### Removed
5255

56+
- `/catchup` command -- its context restoration role overlaps with `/sync`, which already covers pre-flight workspace state
5357
- `/ship` slash command -- its 3-tier validation checklist (Blockers, High Priority, Recommended) is preserved in `/done` Phase 2
5458
- Shell Command Style and Allowed Operations sections from CLAUDE.md -- absolute path preferences and read-only command lists are now handled by settings.json permission rules rather than prose instructions
5559

docs/DECISIONS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ When a decision is superseded or obsolete, delete it (git history preserves the
113113
- `/sync` and `/done` have `disable-model-invocation: true` (side effects: git fetch, git commit/push, PR creation); `/design` is intentionally model-invocable so Claude can suggest it during brainstorming
114114
- QSP paths (Q/S/P) and their step descriptions preserved in DEVELOPMENT_PROCESS.md -- skills orchestrate the paths, they don't replace them
115115

116+
## 2026-03-10: Post-merge /landed Skill
117+
118+
**Request**: Close the post-merge gap in the sync-design-done workflow. After `/done` creates a PR and it merges, nothing verifies merge CI, checks deployments, cleans up branches, or identifies the next phase.
119+
120+
**Decisions**:
121+
- New `/landed` skill (not command) -- follows same pattern as `/sync` and `/done` with `disable-model-invocation: true`
122+
- `/catchup` removed -- its context restoration overlaps with `/sync` which already covers pre-flight state
123+
- Optional deployment verification via `.claude/deploy.json` (gitignored) -- not all projects have deployments, so it's opt-in with an example file
124+
- Phase detection uses "Quick Status Summary" table in IMPLEMENTATION_PLAN.md, not `- [ ]` checkboxes -- matches actual file structure
125+
116126
## 2026-03-10: Template Integration CI Pipeline
117127

118128
**Request**: Create a CI pipeline that applies the template in various settings to catch template bugs before merge.

docs/DEVELOPMENT_PROCESS.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ All agents use `subagent_type: "general-purpose"`. Do NOT use `feature-dev:code-
101101

102102
---
103103

104+
## Post-merge
105+
106+
Run `/landed` after a PR is merged. It verifies merge CI, optionally checks
107+
deployments (via `.claude/deploy.json`), cleans up branches, and identifies the
108+
next phase for P-scope work.
109+
110+
---
111+
104112
## P. Project Path
105113

106114
**P.1 Analyze**
@@ -161,11 +169,10 @@ All hooks require `jq` for JSON parsing and degrade gracefully if jq is missing.
161169

162170
## Commands
163171

164-
4 slash commands in `.claude/commands/`:
172+
3 slash commands in `.claude/commands/`:
165173

166174
| Command | Purpose |
167175
|---------|---------|
168-
| `/catchup` | Context restoration after `/clear`. Reads IMPLEMENTATION_PLAN.md, CHANGELOG.md, git history; recommends next steps. |
169176
| `/cove` | Chain-of-Verification (CoVe) for high-stakes accuracy. 4-step process: generate baseline, plan verifications, verify from source, produce corrected response. |
170177
| `/cove-isolated` | Isolated CoVe variant. Verification step runs in a separate agent that cannot see the baseline response, preventing confirmation bias. |
171178
| `/security-audit` | 6-phase Python security scan (deps, secrets, code patterns, input validation, config, scoring). Outputs A-F grade. |
@@ -174,13 +181,14 @@ All hooks require `jq` for JSON parsing and degrade gracefully if jq is missing.
174181

175182
## Skills
176183

177-
4 skills in `.claude/skills/`:
184+
5 skills in `.claude/skills/`:
178185

179186
| Skill | Purpose |
180187
|-------|---------|
181188
| `/sync` | Pre-flight workspace sync. Fetches remote, reports branch state, dirty files, ahead/behind, recent commits. |
182189
| `/design` | Crystallize brainstorming into a structured plan. Reads DECISIONS.md for conflicts, auto-classifies scope, outputs actionable plan. |
183190
| `/done` | Universal completion. Auto-detects scope (Q/S/P), validates (3-tier checklist), ships/lands/delivers, updates docs. Absorbs former `/ship`. |
191+
| `/landed` | Post-merge lifecycle. Verifies merge CI, optional deployment checks, cleans up branches, prepares next phase. |
184192
| `/edit-permissions` | Manage Claude Code permission rules in settings.json. Pattern syntax reference and safety guardrails. |
185193

186194
---

tests/test_commands.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
COMMANDS_DIR = Path(__file__).parent.parent / ".claude" / "commands"
88

99
ALL_COMMANDS = [
10-
"catchup.md",
1110
"cove.md",
1211
"cove-isolated.md",
1312
"security-audit.md",
@@ -71,18 +70,6 @@ def test_command_has_markdown_heading(self, command_name: str) -> None:
7170
class TestCommandContent:
7271
"""Verify specific command content."""
7372

74-
def test_catchup_reads_implementation_plan(self) -> None:
75-
content = (COMMANDS_DIR / "catchup.md").read_text(encoding="utf-8")
76-
assert "IMPLEMENTATION_PLAN" in content, "catchup should reference IMPLEMENTATION_PLAN.md"
77-
78-
def test_catchup_reads_changelog(self) -> None:
79-
content = (COMMANDS_DIR / "catchup.md").read_text(encoding="utf-8")
80-
assert "CHANGELOG" in content, "catchup should reference CHANGELOG.md"
81-
82-
def test_catchup_checks_git(self) -> None:
83-
content = (COMMANDS_DIR / "catchup.md").read_text(encoding="utf-8")
84-
assert "git log" in content, "catchup should analyze git history"
85-
8673
def test_security_audit_has_scoring(self) -> None:
8774
content = (COMMANDS_DIR / "security-audit.md").read_text(encoding="utf-8")
8875
assert "Grade" in content or "grade" in content, "security-audit should include grading"

tests/test_skills.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"sync",
1212
"design",
1313
"done",
14+
"landed",
1415
]
1516

1617

@@ -80,7 +81,7 @@ def test_skill_has_markdown_heading(self, skill_name: str) -> None:
8081
class TestSkillSideEffects:
8182
"""Verify side-effect declarations are correct."""
8283

83-
@pytest.mark.parametrize("skill_name", ["sync", "done"])
84+
@pytest.mark.parametrize("skill_name", ["sync", "done", "landed"])
8485
def test_side_effect_skills_disable_model_invocation(self, skill_name: str) -> None:
8586
content = (SKILLS_DIR / skill_name / "SKILL.md").read_text(encoding="utf-8")
8687
parts = content.split("---", 2)
@@ -181,3 +182,28 @@ def test_done_has_scope_detection(self) -> None:
181182
assert "ship" in content.lower(), "done should describe Q=ship"
182183
assert "land" in content.lower(), "done should describe S=land"
183184
assert "deliver" in content.lower(), "done should describe P=deliver"
185+
186+
# /landed
187+
def test_landed_detects_merged_pr(self) -> None:
188+
content = (SKILLS_DIR / "landed" / "SKILL.md").read_text(encoding="utf-8")
189+
assert "gh pr list" in content, "landed should detect merged PR"
190+
191+
def test_landed_verifies_ci(self) -> None:
192+
content = (SKILLS_DIR / "landed" / "SKILL.md").read_text(encoding="utf-8")
193+
assert "gh run" in content, "landed should verify CI runs"
194+
195+
def test_landed_cleans_branches(self) -> None:
196+
content = (SKILLS_DIR / "landed" / "SKILL.md").read_text(encoding="utf-8")
197+
assert "git branch -d" in content, "landed should clean up branches"
198+
199+
def test_landed_checks_deployment(self) -> None:
200+
content = (SKILLS_DIR / "landed" / "SKILL.md").read_text(encoding="utf-8")
201+
assert "deploy.json" in content, "landed should check deployment config"
202+
203+
def test_landed_checks_next_phase(self) -> None:
204+
content = (SKILLS_DIR / "landed" / "SKILL.md").read_text(encoding="utf-8")
205+
assert "IMPLEMENTATION_PLAN" in content, "landed should check for next phase"
206+
207+
def test_landed_produces_summary(self) -> None:
208+
content = (SKILLS_DIR / "landed" / "SKILL.md").read_text(encoding="utf-8")
209+
assert "# Landed" in content, "landed should produce a summary report"

0 commit comments

Comments
 (0)