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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [Unreleased]

### Added
- **New skill `/ghcp-handoff`.** Bounded delegation to the GitHub Copilot CLI (`copilot`). Generates structured prompts with hard boundaries, NOT-in-scope lists, stream-scoped deliverables, and PR contracts, then invokes `copilot -p` non-interactively and verifies the resulting PR stayed within bounds. Three modes: guided (interactive question flow), `--from-plan <path>` (extract a `## The GHCP prompt` section from a plan file and wrap it with worktree + metadata), and `verify <pr>` (diff-vs-NOT-in-scope glob check + dependency deviation check + PR body contract check). Sibling to `/codex`: where `/codex` pulls a second opinion in, `/ghcp-handoff` pushes bounded mechanical work out. Verification writes a `gstack-review-log` JSONL entry so results surface in the Plan Status Footer. Adds `yaml` and `minimatch` as dependencies.

## [0.18.3.0] - 2026-04-17

### Added
Expand Down
10 changes: 10 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

915 changes: 915 additions & 0 deletions ghcp-handoff/SKILL.md

Large diffs are not rendered by default.

334 changes: 334 additions & 0 deletions ghcp-handoff/SKILL.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
---
name: ghcp-handoff
preamble-tier: 3
version: 0.1.0
description: |
Bounded delegation to GitHub Copilot CLI. Generates structured prompts with
hard boundaries, NOT-in-scope lists, stream-scoped deliverables, and PR
contracts. Invokes copilot, then verifies the PR stayed within bounds.
Use when asked to "hand off to copilot", "delegate to ghcp", "ghcp-handoff",
"scaffold handoff", "mechanical refactor handoff", or "copilot prompt". (gstack)
voice-triggers:
- "hand off"
- "copilot handoff"
- "delegate this"
triggers:
- hand off to copilot
- delegate to ghcp
- ghcp-handoff
- scaffold handoff
- mechanical refactor handoff
- copilot prompt
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- AskUserQuestion
---

{{PREAMBLE}}

{{BASE_BRANCH_DETECT}}

# /ghcp-handoff — Bounded delegation to GitHub Copilot CLI

You are running the `/ghcp-handoff` skill. It delegates bounded, mechanical work
(scaffolds, CI wiring, mechanical refactors, test generation) to GitHub Copilot
CLI while you hold the judgment thread. The skill's value is the *structure* of
the handoff — boundaries, contracts, verification — not raw CLI wrapping.

Sibling to `/codex`: both wrap an external agentic CLI. `/codex` pulls a second
opinion in; `/ghcp-handoff` pushes bounded work out.

---

## Step 0 — Detect copilot

```bash
COPILOT_VERSION=$(bash "${CLAUDE_SKILL_DIR}/bin/ghcp-detect.sh" 2>/dev/null) || true

if [[ -z "$COPILOT_VERSION" ]]; then
COPILOT_AVAILABLE=false
else
COPILOT_AVAILABLE=true
fi
```

The base branch was already resolved above via the BASE_BRANCH_DETECT block — use
the resulting branch name as `$BASE_BRANCH` in later steps. copilot is a
GitHub-only product, so gitlab paths are never taken here.

---

## Step 1 — Mode detection

Parse the user's invocation to determine mode:

- **Guided mode**: default, no flags → interactive question flow (Step 2B)
- **From-plan mode**: `--from-plan <path>` → extract from plan file (Step 2A)
- **Verify mode**: `verify <pr-number>` → verify an existing PR (Step 3)

Optional flags (passthrough to copilot):
- `--model <model>` → override copilot model
- `--effort <level>` → reasoning effort (low/medium/high/xhigh)

---

## Step 2A — From-plan mode

1. Read the plan file at the given path.
2. Run extraction:
```bash
EXTRACT_RESULT=$(bun run "${CLAUDE_SKILL_DIR}/bin/ghcp-extract-plan.ts" "<path>" 2>&1)
```
3. If extraction returned an error (heading not found):
- Print which headings were searched for.
- AskUserQuestion with choices:
- A) Switch to guided mode (plan file loaded as context)
- B) Paste the handoff section manually
- C) Abort
- If B: accept pasted content, resume from step 4 below.

4. Show the extracted prompt in a fenced block for review.

5. **Metadata follow-up round** — 3 quick AskUserQuestion prompts:
- NOT-in-scope paths/globs (suggest from plan context if visible)
- Forbidden deps (optional, can skip)
- Target branch: extract from prompt text if present, otherwise ask.
Validate against forbidden branch list: `main`, `master`, `dev`, `develop`,
`production`, `staging`. Hard-reject if forbidden.

6. Build the final artifact:
- Prepend worktree instruction block:
```
IMPORTANT: Work in a clean git worktree on the branch `<target_branch>`.
Create the branch from the repo's default branch if it doesn't exist.
Do not work directly in the main worktree — other agents may be active there.
```
- Body: extracted prompt content verbatim
- Append: `<!-- ghcp-handoff:metadata -->` block populated from follow-up answers,
always including `required_pr_sections: [Summary, Manual followups, Deviations from plan, Flagged uncertainties]`

7. Write to `.gstack/ghcp-handoff/<slug>.md` + sibling `.meta.json`.
Slug format: `<target-branch-slug>_<YYYY-MM-DD-HHMM>.md`

8. Proceed to **Step 2-exec** (invocation).

---

## Step 2B — Guided mode

Interactive AskUserQuestion flow. Collect all inputs, then render the template
at `${CLAUDE_SKILL_DIR}/templates/prompt-template.md`.

### Question 1: Task summary
AskUserQuestion: "What is GHCP building? (brief task summary)"
Free text. Example: "project scaffold + CI wiring for a TypeScript service"

### Question 2: Target branch
Derive default from task summary:
- Lowercase, replace spaces/punctuation with hyphens, collapse consecutive hyphens
- Truncate to 40 characters
- Prefix with `ghcp/`

AskUserQuestion: "Target branch? Default: `ghcp/<derived-slug>`"
Validate against forbidden list: `main`, `master`, `dev`, `develop`, `production`, `staging`.
If forbidden: hard error, re-prompt.
Inform: "GHCP will use a clean worktree on this branch."

### Question 3: Context files
Auto-detect in CWD:
- `CLAUDE.md` — if exists, include
- `TODOS.md` — if exists, include
- `docs/design.md` — if exists, include

Pre-populate detected files. Warn for missing standard files.
AskUserQuestion: "Context files for GHCP to read first: [detected list]. Add/remove?"

### Question 4: Phase note + stub policy
AskUserQuestion: "Phase note (what phase is this?)" — e.g. "Phase 1a-alpha scaffolding only"
AskUserQuestion: "Stub policy (what should GHCP NOT implement beyond?)" — e.g. "empty interface stubs"

### Question 5: Streams
Loop:
- AskUserQuestion: "Stream name?" (e.g. "Project scaffold")
- AskUserQuestion: "Deliverables for this stream? (bullet list)"
- AskUserQuestion: "Add another stream? (y/n)"

Each stream rendered as:
```
STREAM N — <stream_name>
<deliverables_bullets>
```

### Question 6: Hard boundaries
AskUserQuestion: "List hard boundaries — things that will cause the PR to be rejected.
(Numbered list, one per line)"

### Question 7: NOT-in-scope
AskUserQuestion: "NOT-in-scope paths/globs + reasons. Format: `path: reason`
Example: `src/domain/*: judgment-heavy, stays with Claude`"

### Question 8: Forbidden deps
AskUserQuestion: "Forbidden dependencies? (comma-separated, or 'none')"

### Question 9: PR sections
Default sections: Summary, Manual followups, Deviations from plan, Flagged uncertainties.
AskUserQuestion: "PR description sections — accept defaults or customize?"

### Render
After all answers:
1. Read `${CLAUDE_SKILL_DIR}/templates/prompt-template.md`.
2. Fill all double-brace-style template variables (e.g. `task_summary`, `target_branch`, `stream_blocks`) from collected answers.
3. Write to `.gstack/ghcp-handoff/<slug>.md` + `.meta.json`.
4. Show the rendered prompt in a fenced block.
5. Proceed to **Step 2-exec**.

---

## Step 2-exec — Invoke copilot

### .gstack/ directory management
Before writing the prompt file, ensure `.gstack/ghcp-handoff/` exists in the
**target repo's working tree** (not the skill's own folder). If `.gstack/` is
not in that repo's `.gitignore`, add it:
```bash
if ! grep -qxF '.gstack/' .gitignore 2>/dev/null; then
echo '.gstack/' >> .gitignore
echo "Added .gstack/ to .gitignore."
fi
mkdir -p .gstack/ghcp-handoff
```

### Invocation
If `$COPILOT_AVAILABLE` is true:
```bash
copilot -p "$(cat .gstack/ghcp-handoff/<slug>.md)" \
--allow-all-tools \
--output-format json \
--no-ask-user
```
With optional passthrough flags:
- `--model <x>` if user specified `--model`
- `--effort <level>` if user specified `--effort`

**Timeout: 30 minutes** (`timeout: 1800000` ms in Bash tool).

On completion, save to `.meta.json`: timestamp, exit code, elapsed time.

### Error handling

| Error | Detection | Message |
|-------|-----------|---------|
| Binary not found | `$COPILOT_AVAILABLE` is false | "copilot not found. Prompt saved at `<path>`. Install: https://docs.github.com/copilot/how-tos/copilot-cli" |
| Auth failure | stderr contains "auth", "login", or "unauthorized" | "copilot auth failed. Run: `copilot login`" |
| Timeout (30 min) | Bash tool timeout exceeded | "Timed out after 30 minutes. Prompt saved at `<path>`. Re-run manually or try a smaller scope." |
| Empty response | stdout empty after exit | "copilot returned no output. Check logs at `~/.copilot/logs/`." |

In all cases, the prompt file survives for retry.

### After success
Print:
```
PR should be open on `<target_branch>`.
Run `/ghcp-handoff verify <pr-number>` to check boundaries.
```

---

## Step 3 — Verify mode (`/ghcp-handoff verify <pr>`)

### 3.1 Resolve PR to branch name
```bash
BRANCH=$(gh pr view <pr> --json headRefName -q .headRefName 2>/dev/null) || true
```
If `gh` fails or is not installed: AskUserQuestion "Could not resolve PR branch. What is the branch name?"

### 3.2 Find the prompt file
Look in `.gstack/ghcp-handoff/` (in the target repo's working tree) for files
whose slug matches the resolved branch name.
- If multiple matches: list candidates with timestamps, ask user to pick.
- If no match: ask user for the prompt file path.

### 3.3 Parse metadata
Parsing happens inside the verify scripts (Steps 3.4 and 3.6). They extract the
`<!-- ghcp-handoff:metadata -->` block and parse the YAML for:
- `not_in_scope.paths` — glob patterns
- `not_in_scope.reasons` — human-readable reasons
- `forbidden_deps` — list of forbidden dependency names
- `required_pr_sections` — list of required PR body sections

Both scripts exit with code **2** if the metadata block is missing or malformed.
Handle that case per **Step 3.9** below before proceeding.

### 3.4 Boundary check
```bash
bun run "${CLAUDE_SKILL_DIR}/bin/ghcp-verify-boundaries.ts" "<prompt-file>" "$BASE_BRANCH" "$BRANCH"
```
- Gets `git diff --name-only <base>...<pr-branch>`
- Matches each changed file against `not_in_scope.paths` globs
- Matches → `boundary_violations` with file + pattern + reason

Exit codes: `0` clean, `1` violations found, `2` metadata parse failure.

### 3.5 Dependency check (same script)
- Diffs each supported manifest: package.json, pyproject.toml, go.mod, Cargo.toml, Gemfile
- Any new dependency → `deviation` flag
- Any dependency in `forbidden_deps` → `violation` flag

### 3.6 PR body check
```bash
bun run "${CLAUDE_SKILL_DIR}/bin/ghcp-verify-pr-body.ts" "<prompt-file>" "<pr-number>"
```
- Fetches PR body via `gh pr view`
- Case-insensitive search for each `required_pr_sections` entry
- Missing → `missing_sections` count + list

Exit codes: `0` all present, `1` sections missing, `2` metadata parse failure.

### 3.7 Report
Print verification table:
```
GHCP HANDOFF VERIFICATION — PR #<pr>
═══════════════════════════════════════
Boundary violations: N (list if >0)
Dependency deviations: N (list if >0)
Dependency violations: N (list if >0)
Missing PR sections: N (list if >0)
═══════════════════════════════════════
VERDICT: CLEAN / ISSUES FOUND

Next: run /review <pr> for full code review.
```

### 3.8 Persist
Write gstack-review-log JSONL entry (if available):
```json
{
"skill": "ghcp-handoff-verify",
"timestamp": "<ISO 8601>",
"status": "clean|issues_found",
"boundary_violations": N,
"dep_deviations": N,
"dep_violations": N,
"missing_sections": N,
"commit": "<short SHA>"
}
```
If gstack-review-log is unavailable, skip silently.

### 3.9 Error handling

| Exit | Meaning | What to tell the user |
|------|---------|-----------------------|
| `0` | Clean | Present the verdict table (Step 3.7) with no violations. |
| `1` | Violations found | Present the verdict table with the violations listed, plus the `/review` suggestion. |
| `2` | **Metadata parse failure** | The prompt file at `<prompt-file>` is missing a valid `<!-- ghcp-handoff:metadata -->` block, or its YAML is malformed. Most common causes: (a) the prompt was edited manually and the metadata block was removed, (b) an older prompt file predates the metadata contract, (c) YAML syntax error in `not_in_scope` / `forbidden_deps` / `required_pr_sections`. **Do not** persist a review-log entry — the verification did not run. Tell the user: "Can't verify PR #\<pr\> — metadata block missing from `<prompt-file>`. Regenerate via `/ghcp-handoff` (guided) or `/ghcp-handoff --from-plan <plan>` and re-run `verify`." Stop. |
| Other | Unexpected error (e.g. `gh` not installed, `git diff` failed, bun not found) | Surface the stderr from the failing script verbatim. Suggest the most likely remediation (install `gh`, authenticate with `gh auth login`, confirm the PR branch exists locally via `git fetch`). Do not persist a review-log entry. |

When the boundary-check script (Step 3.4) and the PR-body-check script (Step 3.6)
disagree (e.g. boundary clean but PR body missing sections), aggregate both in
the Step 3.7 report. Persist a single review-log entry with the combined counts.
Loading