mirror of
https://github.com/EveryInc/compound-engineering-plugin.git
synced 2026-07-07 18:06:34 +02:00
feat(ce-dogfood): promote from beta to stable (#1022)
This commit is contained in:
+1
-1
@@ -80,4 +80,4 @@ The classification of a review finding by how safely its proposed fix can be app
|
||||
An explicit opt-in mode that runs a Skill unattended, with no user prompts — it produces a written report as its deliverable and conservatively defers genuinely ambiguous decisions rather than guessing.
|
||||
|
||||
### Beta skill
|
||||
A parallel copy of a stable Skill, suffixed `-beta`, used to trial a new version alongside the stable one without disrupting users. Invoked manually (model auto-invocation is disabled); promoting it to stable is an orchestration change, not just a rename — every caller must move in the same change so none silently inherits stale defaults.
|
||||
A parallel copy of a stable Skill, suffixed `-beta`, used to trial a new version alongside the stable one without disrupting users. Invoked manually (model auto-invocation is disabled); promoting it to stable is more than a rename — every caller must move in the same change so none silently inherits stale defaults, and the retired beta name must be registered for stale-artifact cleanup so upgrading users don't keep a dead duplicate of the skill alongside the promoted one.
|
||||
|
||||
@@ -136,7 +136,7 @@ The `compound-engineering` plugin currently ships 26 skills and 0 standalone age
|
||||
| `/ce-simplify-code` | Simplify recent code changes |
|
||||
| `/ce-polish` | Start a dev server and iterate on UX polish |
|
||||
| `/ce-proof` | Create, edit, and share Proof documents |
|
||||
| `/ce-dogfood-beta` | Diff-scoped browser QA of the active branch |
|
||||
| `/ce-dogfood` | Hands-off diff-scoped browser QA of the active branch, with autonomous fixes |
|
||||
| `/lfg` | Full autonomous engineering workflow |
|
||||
|
||||
---
|
||||
|
||||
@@ -109,6 +109,7 @@ Invoked when a specific need arises — not part of any chain.
|
||||
|-------|-------------|
|
||||
| [`/ce-promote`](./ce-promote.md) | Draft user-facing announcement copy for a shipped feature (X, changelog, LinkedIn, email) — voice-matched via the optional Spiral CLI, a lite layer of editorial & social expertise without it, drafts only |
|
||||
| [`/ce-resolve-pr-feedback`](./ce-resolve-pr-feedback.md) | Evaluate, fix, and reply to PR review feedback in parallel — including nitpicks |
|
||||
| [`/ce-dogfood`](./ce-dogfood.md) | Hands-off diff-scoped browser QA of the active branch — maps flows, autonomously fixes small breakages with regression tests and commits, writes a durable report (manual invocation only) |
|
||||
| [`/ce-test-browser`](./ce-test-browser.md) | End-to-end browser tests on PR / branch-affected pages using `agent-browser` exclusively |
|
||||
| [`/ce-test-xcode`](./ce-test-xcode.md) | Build and test iOS apps on simulator using XcodeBuildMCP — screenshots, logs, human verification |
|
||||
| [`/ce-setup`](./ce-setup.md) | Diagnose optional tool capabilities and bootstrap safe project-local config |
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# `ce-dogfood`
|
||||
|
||||
> Hands-off, diff-scoped browser QA of the active branch — maps flows, drives a real browser, autonomously fixes small breakages with regression tests and commits, and writes a durable report. Manual invocation only.
|
||||
|
||||
`ce-dogfood` acts as a QA engineer who dogfoods the **active branch** end to end: it understands every change versus the trunk, maps the user flows the diff touches, exercises each flow in a real browser via `agent-browser`, judges both correctness and experience (including per-persona paper cuts), fixes what's safely fixable on its own — adding a regression test and committing each fix — escalates what isn't, and leaves a durable report under `docs/dogfood-reports/`.
|
||||
|
||||
It is **diff-scoped**, not whole-app exploration, and it is **hands-off**: once invoked it runs the full loop autonomously. Because it edits code and creates commits, it is **manual-invocation only** (`disable-model-invocation: true`).
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
| Question | Answer |
|
||||
|----------|--------|
|
||||
| What does it do? | Maps diff → flows → test matrix, drives each flow in a browser, autonomously fixes small breakages (with regression tests + commits), escalates big ones, writes a report |
|
||||
| When to use it | Before shipping a branch, when you want a real-browser pass that also *fixes* what it finds — not just reports |
|
||||
| What it produces | A durable report (`docs/dogfood-reports/<date>-<branch>-dogfood.md`): flows, test matrix, fixes, paper cuts, escalations, learnings, verdict |
|
||||
| How it differs from `ce-test-browser` | `ce-test-browser` tests and reports; `ce-dogfood` adds autonomous fixing, regression tests, fix commits, persona-level experiential judgment, and a durable report artifact |
|
||||
| Invocation | Manual only — type `/ce-dogfood` |
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
A branch can pass static review and unit tests and still be broken or rough in the browser — and finding that out usually means a manual click-through that:
|
||||
|
||||
- **Tests pages, not journeys** — the email "sends" but lands in the wrong thread; the form saves but the redirect drops you somewhere confusing
|
||||
- **Stops at "does it work"** — it never asks whether the change *feels* right for the people who actually use the product
|
||||
- **Finds bugs but doesn't fix them** — the QA pass produces a list, and the fixing is a separate, later task
|
||||
- **Lets fixed bugs regress** — nothing locks in the fix with a test
|
||||
- **Leaves no durable trace** — the next person re-derives the same flows from scratch
|
||||
|
||||
## The Solution
|
||||
|
||||
`ce-dogfood` runs the whole loop as one hands-off pass:
|
||||
|
||||
- **Flow-first** — it maps the journeys the diff touches as Mermaid flowcharts *before* building the test matrix, so it tests the journey, not isolated widgets
|
||||
- **Persona-grounded** — it grounds flows in the product's personas (from `STRATEGY.md` / `VISION.md` / persona docs, or an inferred persona) and walks each flow looking for **paper cuts**
|
||||
- **Autonomous fix loop** — small, low-risk, unambiguous breakages get fixed in place, each with a regression test that fails-before/passes-after and its own commit
|
||||
- **Knows when to stop** — large, ambiguous, or product-altering changes are escalated as "Decisions for a human," not forced
|
||||
- **Verifies before the verdict** — runs the project's existing test suite once before declaring the branch ready
|
||||
- **Durable artifact** — a report doc that doubles as a resume checkpoint
|
||||
|
||||
---
|
||||
|
||||
## What Makes It Novel
|
||||
|
||||
### 1. Flows before the matrix
|
||||
|
||||
The skill maps each user-visible change as a Mermaid `flowchart` — entry point, actions, branch points, side effects, true end state (including email click-through destinations) — and only then derives the test matrix from those diagrams. The flowcharts are the understanding; they become the spine of the matrix and ship in the report. The mapping scales to the diff: a one-route change gets one small flowchart, never a skipped step.
|
||||
|
||||
### 2. Functional *and* experiential judgment
|
||||
|
||||
Every scenario is judged twice: "does it work?" (right data, right destination, no console errors) and "does it feel right?" The skill walks each flow as each primary persona and records **paper cuts** — small frictions that pass a functional test but degrade the experience for that persona. A scenario can `Pass` functionally and still carry paper cuts.
|
||||
|
||||
### 3. Autonomous fix loop with a size gate
|
||||
|
||||
When a scenario fails — or a passing scenario carries a sharp paper cut worth fixing now — the skill first judges whether the fix is **its** to make. It auto-fixes only small, well-understood, low-risk changes; anything requiring an architecture/schema decision, changing product behavior, spanning many files, or with plausible competing solutions is escalated instead of forced. Each autonomous fix gets a regression test (or, for a pure copy/visual fix, a documented replay/screenshot check and a note on why no automated test was meaningful) and its own commit.
|
||||
|
||||
### 4. Escalation as a first-class outcome
|
||||
|
||||
"Too big to fix autonomously" is a normal result, not a failure. The skill records each one under **Decisions for a human** — what's broken, why it's not a safe autonomous fix, the options with trade-offs, and a recommendation — and marks the scenario `Blocked (human decision)`.
|
||||
|
||||
### 5. Resumable by design
|
||||
|
||||
The report doc is created as soon as the matrix exists and updated incrementally, so a run can be interrupted and resumed (or picked up by a teammate). On resume, done scenarios stay done and pending ones re-queue — but the two `Blocked` states are surfaced to the user rather than silently re-run, because they're waiting on a person.
|
||||
|
||||
### 6. A suite check before "ready"
|
||||
|
||||
A green browser matrix with a red test suite is not "ready." Before the verdict, the skill runs the project's existing automated tests (plus the new regression tests), discovering the command from the project's conventions rather than assuming a runner.
|
||||
|
||||
---
|
||||
|
||||
## `ce-dogfood` vs `ce-test-browser`
|
||||
|
||||
Both take a PR / branch and drive `agent-browser` over diff-affected pages. Pick by what you want at the end:
|
||||
|
||||
| | `ce-test-browser` | `ce-dogfood` |
|
||||
|---|---|---|
|
||||
| Output | A test summary | A durable report + committed fixes |
|
||||
| Fixes breakages? | No — reports them | Yes — small ones autonomously, with regression tests |
|
||||
| Experiential judgment | Functional focus | Functional + per-persona paper cuts |
|
||||
| Flow modeling | Route-oriented | Journey-first (Mermaid flowcharts) |
|
||||
| Autonomy | Asks how to proceed on failures | Hands-off: fixes, escalates, continues |
|
||||
| Invocation | Model- or user-invokable | Manual only |
|
||||
|
||||
Use `ce-test-browser` for a lighter "do the affected pages still work" pass; reach for `ce-dogfood` when you want the branch driven to genuinely-ready, with fixes applied.
|
||||
|
||||
---
|
||||
|
||||
## When to Reach For It
|
||||
|
||||
Reach for `ce-dogfood` when:
|
||||
|
||||
- You have a branch you want driven to genuinely-ready in a real browser, not just smoke-tested
|
||||
- You want breakages *fixed and locked in with tests*, not just listed
|
||||
- You care whether the change feels right for real users, not only whether it works
|
||||
- You want a durable QA artifact for the branch
|
||||
|
||||
Skip it when:
|
||||
|
||||
- The change is backend-only with no browser-visible behavior → use the project's test runner
|
||||
- You only want a quick "does it still render" check → use `/ce-test-browser`
|
||||
- `agent-browser` isn't installed → run `/ce-setup` first
|
||||
- The dev server can't be brought up locally → use a different approach
|
||||
|
||||
---
|
||||
|
||||
## Use Standalone
|
||||
|
||||
- **Current branch** — `/ce-dogfood`
|
||||
- **Specific PR** — `/ce-dogfood 847`
|
||||
- **Specific branch** — `/ce-dogfood feature/new-dashboard`
|
||||
- **Custom port** — `/ce-dogfood --port 5000`
|
||||
|
||||
The skill refuses to run on the trunk (there is no diff to dogfood) and offers to run in an isolated worktree so the main checkout stays untouched.
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
| Argument | Effect |
|
||||
|----------|--------|
|
||||
| _(empty)_ | Dogfoods the current branch |
|
||||
| `<PR number>` | Checks out and dogfoods that PR |
|
||||
| `<branch name>` | Checks out and dogfoods that branch |
|
||||
| `--port <number>` | Override port detection |
|
||||
|
||||
Required: `agent-browser` CLI installed (run `/ce-setup` if missing); a local dev server the skill can start.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [`ce-test-browser`](./ce-test-browser.md) — the lighter test-and-report sibling
|
||||
- [`ce-worktree`](./ce-worktree.md) — isolation offered in Phase 0
|
||||
- [`ce-debug`](./ce-debug.md) — root-cause analysis for non-obvious failures
|
||||
- [`ce-commit`](./ce-commit.md) — well-scoped commit messages for each fix
|
||||
- [`ce-compound`](./ce-compound.md) — capture reusable lessons surfaced during the pass
|
||||
- [`ce-setup`](./ce-setup.md) — reports whether `agent-browser` is available and prints the install command when missing
|
||||
@@ -126,5 +126,5 @@ Yes. When the bundled health script is not directly runnable, the skill falls ba
|
||||
## See Also
|
||||
|
||||
- [`/ce-test-browser`](./ce-test-browser.md) — uses `agent-browser` for browser testing
|
||||
- [`/ce-dogfood-beta`](./ce-dogfood-beta.md) — uses `agent-browser` for diff-scoped QA
|
||||
- [`/ce-dogfood`](./ce-dogfood.md) — uses `agent-browser` for diff-scoped QA
|
||||
- [`/ce-product-pulse`](./ce-product-pulse.md) — uses `.compound-engineering/config.local.yaml` for pulse settings
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: "Beta-to-stable skill promotion: wiring legacy cleanup so the stale beta dir is actually swept"
|
||||
category: skill-design
|
||||
date: 2026-06-28
|
||||
module: legacy-cleanup
|
||||
problem_type: convention
|
||||
component: tooling
|
||||
severity: medium
|
||||
applies_when:
|
||||
- "promoting a skill from beta (ce-X-beta) to stable (ce-X) and registering the old dir for legacy flat-install cleanup"
|
||||
tags:
|
||||
- skill-promotion
|
||||
- legacy-cleanup
|
||||
- beta-rename
|
||||
- stale-skill-dirs
|
||||
- flat-install
|
||||
related:
|
||||
- docs/solutions/skill-design/beta-skills-framework.md
|
||||
- docs/solutions/skill-design/beta-promotion-orchestration-contract.md
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
When a compound-engineering skill graduates from beta to stable, its directory is renamed `ce-X-beta` -> `ce-X`. Users who installed the plugin before the rename will have a stale `ce-X-beta/` directory sitting next to the new `ce-X/` in their flat-install layout. `src/utils/legacy-cleanup.ts` is supposed to sweep that stale dir on the next upgrade.
|
||||
|
||||
The trap: the two edits that *look* sufficient — adding the old name to `STALE_SKILL_DIRS` and adding a `LEGACY_SKILL_DESCRIPTION_ALIASES` entry — are a **silent no-op** on their own. The existing precedent (`ce-polish-beta`) shipped with exactly that incomplete wiring, so its cleanup never ran, and there was no error, log, or failing test to reveal it. The `ce-dogfood-beta -> ce-dogfood` promotion would have inherited the same dead code by copying the precedent.
|
||||
|
||||
## Guidance
|
||||
|
||||
A correct beta-to-stable rename requires **four edits across two files plus a regression test**. The load-bearing one is easy to miss.
|
||||
|
||||
### How the mechanism works
|
||||
|
||||
`cleanupStaleSkillDirs(skillsRoot)` iterates `STALE_SKILL_DIRS` and, for each name, calls:
|
||||
|
||||
```ts
|
||||
isLegacyPluginOwned(targetPath, skills.get(name), null)
|
||||
```
|
||||
|
||||
`isLegacyPluginOwned` guards early:
|
||||
|
||||
```ts
|
||||
if (!expectedDescription) return false
|
||||
```
|
||||
|
||||
Only *after* that guard does it read the on-disk `SKILL.md` description and compare it against `expectedDescription` **plus** any `LEGACY_SKILL_DESCRIPTION_ALIASES[basename]` entries. So if `expectedDescription` is `undefined`, the alias list is never consulted.
|
||||
|
||||
`expectedDescription` comes from the `skills` map seeded in `loadLegacyFingerprints()`. For each `STALE_SKILL_DIRS` name:
|
||||
|
||||
```ts
|
||||
const currentPath = skillIndex.get(currentSkillNameForLegacy(name))
|
||||
if (currentPath) {
|
||||
// seed = the currently-shipping skill's CURRENT description
|
||||
} else if (LEGACY_ONLY_SKILL_DESCRIPTIONS[name]) {
|
||||
// seed = hardcoded last-shipped description (for FULLY-RETIRED skills, no replacement)
|
||||
} else {
|
||||
// seed = undefined -> isLegacyPluginOwned bails immediately
|
||||
}
|
||||
```
|
||||
|
||||
`currentSkillNameForLegacy` has explicit `case`s for skills renamed to a *different* name, and a default that returns any `ce-`-prefixed name unchanged:
|
||||
|
||||
```ts
|
||||
default:
|
||||
return legacyName.startsWith("ce-") ? legacyName : `ce-${legacyName}`
|
||||
```
|
||||
|
||||
So `currentSkillNameForLegacy("ce-dogfood-beta")` returns `"ce-dogfood-beta"`. After the rename that name is no longer in the skill index, the seed stays `undefined`, the alias is never reached, and nothing is swept.
|
||||
|
||||
### The correct wiring
|
||||
|
||||
**1 — `STALE_SKILL_DIRS` (`src/utils/legacy-cleanup.ts`)** — register the old name:
|
||||
|
||||
```ts
|
||||
// ce-dogfood-beta -> ce-dogfood (promoted to stable)
|
||||
"ce-dogfood-beta",
|
||||
```
|
||||
|
||||
**2 (load-bearing) — `currentSkillNameForLegacy` (`src/utils/legacy-cleanup.ts`)** — map the beta name to the shipping stable name so the seed resolves to a real description:
|
||||
|
||||
```ts
|
||||
case "ce-polish-beta":
|
||||
return "ce-polish"
|
||||
case "ce-dogfood-beta":
|
||||
return "ce-dogfood"
|
||||
```
|
||||
|
||||
**3 — `LEGACY_SKILL_DESCRIPTION_ALIASES` (`src/utils/legacy-cleanup.ts`)** — the seed is now the *new* stable description, but the stale dir on disk still carries the *old* beta description. Add the **verbatim last-shipped beta `description:`** so the old-on-disk file matches:
|
||||
|
||||
```ts
|
||||
"ce-dogfood-beta": [
|
||||
"[BETA] Hands-off end-to-end branch dogfood pass with browser testing, auto-fixes, regression tests, and fix commits.",
|
||||
],
|
||||
```
|
||||
|
||||
**4 — `EXTRA_LEGACY_ARTIFACTS_BY_PLUGIN["compound-engineering"]` (`src/data/plugin-legacy-artifacts.ts`)** — register the name with the universal artifact sweeper:
|
||||
|
||||
```ts
|
||||
"ce-dogfood-beta",
|
||||
```
|
||||
|
||||
**5 — Regression test (`tests/legacy-cleanup.test.ts`)** — mirror the existing "removes ce-review and ce-document-review (renamed skills)" test, but create the stale dir with its **old beta description** (the realistic upgrade state), not the current stable one. A test seeded with the current description would pass for the wrong reason and would not catch a missing step 2:
|
||||
|
||||
```ts
|
||||
test("removes promoted-from-beta skill dirs via their last-shipped beta description (ce-dogfood-beta, ce-polish-beta)", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "cleanup-beta-promoted-"))
|
||||
await createDir(
|
||||
path.join(root, "ce-dogfood-beta"),
|
||||
skillContent(
|
||||
"ce-dogfood-beta",
|
||||
"[BETA] Hands-off end-to-end branch dogfood pass with browser testing, auto-fixes, regression tests, and fix commits.",
|
||||
),
|
||||
)
|
||||
await createDir(
|
||||
path.join(root, "ce-polish-beta"),
|
||||
skillContent(
|
||||
"ce-polish-beta",
|
||||
"Start the dev server, open the feature in a browser, and iterate on improvements together. Manual invocation only — type /ce-polish to run it.",
|
||||
),
|
||||
)
|
||||
|
||||
const removed = await cleanupStaleSkillDirs(root)
|
||||
|
||||
expect(removed).toBe(2)
|
||||
expect(await exists(path.join(root, "ce-dogfood-beta"))).toBe(false)
|
||||
expect(await exists(path.join(root, "ce-polish-beta"))).toBe(false)
|
||||
})
|
||||
```
|
||||
|
||||
`cleanupStaleSkillDirs(root)` takes a single argument; it loads the fingerprint map internally.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Cleanup is the only thing stopping stale beta dirs from accumulating in users' flat installs after an upgrade. A lingering `ce-dogfood-beta/` next to `ce-dogfood/` means two skill definitions for the same concept — listing conflicts and extra tokens loaded on every invocation. The failure mode is silent: no error, no log, the dir just stays.
|
||||
|
||||
The `ce-polish-beta` cleanup had been shipped live and broken. The existing suite never caught it because the only rename tests (`ce-review -> ce-code-review`, `ce-document-review -> ce-doc-review`) all use names with explicit `currentSkillNameForLegacy` cases — so they pass through step 2 by construction. Any beta promotion that copies the `ce-polish-beta` pattern as a template inherits the same no-op. This wiring was fixed for both `ce-dogfood-beta` and `ce-polish-beta` in the dogfood promotion.
|
||||
|
||||
## When to Apply
|
||||
|
||||
Apply this every time a skill is promoted from beta to stable (`ce-X-beta` renamed to `ce-X`). Land all four edits plus the test in the same PR that performs the rename — not a follow-up. It does **not** apply to fully-retired skills with no replacement; those seed their fingerprint via `LEGACY_ONLY_SKILL_DESCRIPTIONS` instead of the `currentSkillNameForLegacy` path.
|
||||
|
||||
## Examples
|
||||
|
||||
### Before — the incomplete `ce-polish-beta` pattern (silent no-op)
|
||||
|
||||
```ts
|
||||
// src/utils/legacy-cleanup.ts
|
||||
export const STALE_SKILL_DIRS = [
|
||||
"ce-polish-beta", // step 1: present
|
||||
]
|
||||
// step 2: MISSING — no currentSkillNameForLegacy case, so the default returns
|
||||
// "ce-polish-beta" (not a shipping skill) -> seed undefined -> isLegacyPluginOwned
|
||||
// bails before the alias is read -> nothing swept
|
||||
const LEGACY_SKILL_DESCRIPTION_ALIASES = {
|
||||
"ce-polish-beta": [ /* present, but unreachable */ ],
|
||||
}
|
||||
```
|
||||
|
||||
Steps 1, 3, 4 present; step 2 missing; step 5 absent. Stale `ce-polish-beta/` dirs survive upgrades forever. No error, no test failure.
|
||||
|
||||
### After — complete wiring
|
||||
|
||||
Add the `currentSkillNameForLegacy` case (step 2) so the seed resolves to the stable skill's current description, keep the alias (step 3) so the old-on-disk description still matches, and add the regression test (step 5) that proves the sweep fires. Result: `bun test` 1600 pass / 0 fail; `bun run release:validate` in sync; stale beta dirs swept on the first upgrade after the rename.
|
||||
@@ -73,10 +73,11 @@ When the beta version is validated:
|
||||
3. Remove `disable-model-invocation: true` so the model can auto-trigger it
|
||||
4. Update all internal references back to stable names
|
||||
5. Restore stable plan file naming (remove `-beta` from the convention)
|
||||
6. Delete the beta skill directory
|
||||
7. Update README.md: remove from Beta Skills section, verify counts
|
||||
8. Verify `lfg` works with the promoted skill
|
||||
9. Verify `ce-work` consumes plans from the promoted skill
|
||||
6. Rename the beta skill directory to the stable name (`ce-X-beta` -> `ce-X`)
|
||||
7. Register the retired `ce-X-beta` name for legacy flat-install cleanup so upgrading users don't keep a stale duplicate dir. This is more than adding the name to `STALE_SKILL_DIRS` — the sweep silently no-ops without the `currentSkillNameForLegacy` case that maps the beta name to the stable one. See [beta-promotion-cleanup-registry-wiring.md](./beta-promotion-cleanup-registry-wiring.md) for the complete four-edit-plus-test wiring.
|
||||
8. Update README.md: remove from Beta Skills section, verify counts
|
||||
9. Verify `lfg` works with the promoted skill
|
||||
10. Verify `ce-work` consumes plans from the promoted skill
|
||||
|
||||
If the beta skill changed its invocation contract, promotion must also update all orchestration callers in the same PR instead of relying on the stable default behavior. See [beta-promotion-orchestration-contract.md](./beta-promotion-orchestration-contract.md) for the concrete review-skill example.
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
name: ce-dogfood-beta
|
||||
description: "[BETA] Hands-off end-to-end branch dogfood pass with browser testing, auto-fixes, regression tests, and fix commits."
|
||||
name: ce-dogfood
|
||||
description: "Hands-off, diff-scoped browser QA of the active branch: maps user flows, drives a real browser, autonomously fixes small breakages with regression tests and commits, judges experience against product personas, and writes a durable dogfood report. Manual invocation only."
|
||||
disable-model-invocation: true
|
||||
argument-hint: "[PR number, branch name, or blank for current branch] [--port PORT]"
|
||||
---
|
||||
|
||||
# Dogfood (Beta)
|
||||
# Dogfood
|
||||
|
||||
Act as a QA engineer who dogfoods the **active branch** end-to-end: understand every change, test every change in a real browser as a user would, and fix what's broken — autonomously — until the branch is genuinely ready.
|
||||
|
||||
This is **diff-scoped**, not whole-app exploration. You test what *this branch* introduced or modified versus `main`. (For full-app exploratory QA, use the `dogfood` skill instead.)
|
||||
This is **diff-scoped**, not whole-app exploration. You test what *this branch* introduced or modified versus the trunk.
|
||||
|
||||
## Use `agent-browser` Only For Browser Automation
|
||||
|
||||
@@ -24,27 +24,26 @@ This workflow drives the browser exclusively through the `agent-browser` CLI. Do
|
||||
command -v agent-browser >/dev/null 2>&1 && echo "Ready" || echo "NOT INSTALLED"
|
||||
```
|
||||
|
||||
If not installed, run the `ce-setup` skill to get the current install command, install `agent-browser`, then resume. Do not continue without it.
|
||||
If not installed, stop and tell the user to install `agent-browser` (run `/ce-setup` to print the current install command), then re-run this skill — this workflow cannot function without it.
|
||||
|
||||
## Reusing Compound-Engineering Skills
|
||||
|
||||
`ce-dogfood-beta` is an orchestrator. Prefer delegating to existing CE skills over re-deriving their behavior:
|
||||
`ce-dogfood` is an orchestrator. Prefer delegating to existing CE skills over re-deriving their behavior:
|
||||
|
||||
| When | Skill | Why |
|
||||
|------|-------|-----|
|
||||
| Phase 0 isolation | `ce-worktree` | Run the dogfood in an isolated worktree so the main checkout stays clean. |
|
||||
| agent-browser missing | `ce-setup` | Reports the current `agent-browser` install command. |
|
||||
| A failure's root cause is non-obvious | `ce-debug` | Systematic root-cause analysis instead of guess-and-check. |
|
||||
| Committing each fix | `ce-commit` | Consistent, well-scoped commit messages. |
|
||||
| A bug reveals a reusable lesson | `ce-compound` | Capture the learning so the team compounds knowledge. |
|
||||
|
||||
Reuse `ce-test-browser`'s mechanics for port detection and dev-server startup (see Phase 3) rather than reinventing them.
|
||||
(If `agent-browser` is missing, direct the user to install it — `/ce-setup` prints the current command. See Prerequisites.)
|
||||
|
||||
## Workflow
|
||||
|
||||
```
|
||||
0. Scope Pick the branch, get onto it (offer worktree), never touch main
|
||||
1. Analyze Diff branch vs main, understand every change
|
||||
0. Scope Pick the branch, get onto it (offer worktree), never touch the trunk
|
||||
1. Analyze Diff branch vs trunk, understand every change
|
||||
2. Map+Matrix Map user flows as Mermaid flowcharts, then derive the test matrix as a task list
|
||||
3. Serve Detect port, start dev server, open agent-browser
|
||||
4. Execute Work the matrix one item at a time with agent-browser
|
||||
@@ -56,30 +55,52 @@ Reuse `ce-test-browser`'s mechanics for port detection and dev-server startup (s
|
||||
|
||||
Parse `$ARGUMENTS`: a PR number, a branch name, or blank (use current branch). Strip `--port PORT` if present.
|
||||
|
||||
1. Resolve the target branch:
|
||||
- **PR number:** `gh pr checkout <number>` (probe for an existing worktree first).
|
||||
- **Branch name:** check it out (probe for an existing worktree first).
|
||||
- **Blank:** use the current branch.
|
||||
2. **Refuse to run on `main`/`master`.** If the resolved branch is the trunk, stop and tell the user — there is no diff to dogfood.
|
||||
3. **Offer isolation.** Ask whether to run in a git worktree so the main checkout stays untouched (use the platform's blocking question tool). If yes, hand off to `ce-worktree`; if no, continue in place.
|
||||
4. **Resume if a prior run exists.** Look for an existing report at `docs/dogfood-reports/*-<branch-slug>-dogfood.md`. If one is found with unfinished scenarios, ask whether to resume it or start fresh. To resume, re-hydrate the task list from its matrix (Pass/Fixed/Skipped stay done; Pending/Blocked/in-progress become the remaining work) and continue from there.
|
||||
1. **Identify the target — keep PR identity; do not switch the working tree yet.**
|
||||
- **PR number:** the target *is the PR* — carry the number through every later step (trunk check, isolation, checkout). Read its head only for display (`gh pr view <number> --json headRefName,isCrossRepository`), but do **not** reduce it to a bare branch name: a fork PR's head can even be named `main`/`master`. Do not check out yet.
|
||||
- **Branch name:** the target is that branch.
|
||||
- **Blank:** the target is the current branch.
|
||||
2. **Refuse to run on the trunk — branch/blank targets only.** If a *branch-name or blank* target resolves to the trunk (`main`/`master`/the detected default), stop — there is no diff to dogfood. A **PR is always diffable** (it has a base), so this check never applies to a PR target; never refuse `/ce-dogfood <number>` just because the PR's head branch happens to be named `main`.
|
||||
3. **Decide isolation by what you're testing; let `ce-worktree` own the worktree mechanics.** Do not re-derive worktree detection or creation here — `ce-worktree` handles existing-isolation detection, the harness-native tool, attaching to a ref, and the "already checked out" constraint, and reports its decision back. The only call this skill makes is *whether to ask for isolation at all*:
|
||||
- **Blank / current-branch target:** do **not** isolate — dogfood in place. You are already on the branch under test, the fix-commits belong on it, and git cannot check the same branch out in a second worktree anyway. (If you happen to already be in a worktree, that is fine — you are simply dogfooding here.)
|
||||
- **A PR or a different named branch:** this is an existing ref to test without disturbing your current checkout. Offer isolation (platform's blocking question tool). On **yes**, invoke `ce-worktree` to isolate **that target ref** — it attaches a worktree to the ref (or, if already isolated, checks it out in place; or reports "already checked out at `<path>` — work there" when the ref is live elsewhere). Act on `ce-worktree`'s verdict; the primary checkout is never switched. On **no**, check the target out in place (`gh pr checkout <number>` for a PR, `git checkout <branch>` for a branch), confirming first if uncommitted changes would be disturbed.
|
||||
4. **Resume if a prior run exists.** Look for an existing report at `docs/dogfood-reports/*-<branch-slug>-dogfood.md` (see the branch-slug rule under Resumability). If one is found with unfinished scenarios, ask whether to resume it or start fresh. To resume, re-hydrate the task list from its matrix: `Pass`/`Fixed`/`Skipped` stay done; `Pending` and `in_progress` become the remaining auto-runnable work. The two `Blocked` states are **not** auto-runnable — `Blocked (needs human verify)` and `Blocked (human decision)` are waiting on a person, so surface them to the user and ask how to proceed rather than silently re-queuing them.
|
||||
|
||||
### Resumability (stop and return at any point)
|
||||
|
||||
This workflow is designed to be interrupted and resumed. Two pieces of state make that safe:
|
||||
|
||||
- **The task list** (`TaskCreate`/`TaskUpdate`) is the live to-do — one task per matrix scenario. Mark each `in_progress` when you start it and `completed` only when it genuinely passes.
|
||||
- **The report doc** at `docs/dogfood-reports/<YYYY-MM-DD>-<branch-slug>-dogfood.md` is the durable checkpoint that survives across sessions. **Create it as soon as the matrix exists (end of Phase 2)** with every scenario listed as `Pending`, and **update it incrementally** — after each scenario is judged and after each fix is committed — not only at the end.
|
||||
- **The task list** (the harness's task tool — `TaskCreate`/`TaskUpdate` on Claude Code, `update_plan` on Codex, or the equivalent elsewhere) is the live to-do — one task per matrix scenario. Mark each `in_progress` when you start it and `completed` only when it genuinely passes.
|
||||
- **The report doc** at `docs/dogfood-reports/<YYYY-MM-DD>-<branch-slug>-dogfood.md` is the durable checkpoint that survives across sessions. `<branch-slug>` is the branch name lowercased with every run of non-alphanumeric characters (slashes included) collapsed to a single `-` (e.g. `feature/Foo_Bar` -> `feature-foo-bar`). **Create it as soon as the matrix exists (end of Phase 2)** with every scenario listed as `Pending`, and **update it incrementally** — after each scenario is judged and after each fix is committed — not only at the end.
|
||||
|
||||
Because tasks are session-scoped but the report doc is on disk, the report is the source of truth for resuming. Always keep the two in sync so a later run (or a teammate) can pick up exactly where this one stopped.
|
||||
|
||||
### Phase 1: Analyze Changes
|
||||
|
||||
Pull the full diff against `main` and read it.
|
||||
Derive the trunk ref once, then pull the full diff against it and read it. Do not hard-code `main` — a repo whose default branch is `master` (or anything else) would fail with `fatal: ambiguous argument 'main...HEAD'`.
|
||||
|
||||
```bash
|
||||
git diff --name-only main...HEAD # what changed
|
||||
git diff main...HEAD # how it changed
|
||||
# Resolve the trunk to a ref that actually exists. Start from the detected
|
||||
# default name (origin/HEAD, then gh), then fall back to common names. For each
|
||||
# candidate prefer a local branch; else use the remote-tracking ref QUALIFIED as
|
||||
# origin/<branch> — an unqualified name resolves via refs/remotes/<name>, NOT
|
||||
# refs/remotes/origin/<name>, so a remote-only trunk would otherwise miss. This
|
||||
# qualification applies to the detected default too (PR/CI checkouts often have
|
||||
# only origin/main, no local main).
|
||||
DEFAULT=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@')
|
||||
DEFAULT=${DEFAULT:-$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null)}
|
||||
TRUNK=""
|
||||
for cand in "$DEFAULT" main master; do
|
||||
[ -n "$cand" ] || continue
|
||||
if git show-ref --verify --quiet "refs/heads/$cand"; then
|
||||
TRUNK=$cand; break
|
||||
elif git show-ref --verify --quiet "refs/remotes/origin/$cand"; then
|
||||
TRUNK="origin/$cand"; break
|
||||
fi
|
||||
done
|
||||
TRUNK=${TRUNK:-main}
|
||||
|
||||
git diff --name-only "$TRUNK...HEAD" # what changed
|
||||
git diff "$TRUNK...HEAD" # how it changed
|
||||
```
|
||||
|
||||
Build a mental model of every change: new features, modified behavior, new routes/views/components, touched data flows. Note anything that produces user-visible behavior — that is what the matrix must cover.
|
||||
@@ -108,7 +129,7 @@ flowchart TD
|
||||
H --> I{Lands on correct thread + scrolls to the reply?}
|
||||
```
|
||||
|
||||
Produce one flowchart per distinct journey. Cover the happy path **and** the branch points (error, empty, boundary, permission). These diagrams ARE the understanding — they become the spine of the matrix and belong in the final report.
|
||||
Produce one flowchart per distinct journey, scaled to the diff: a one-route or copy-only change gets a single small flowchart, a multi-step feature gets several. Cover the happy path **and** the branch points (error, empty, boundary, permission). Mapping the flows before the matrix is never skipped — these diagrams ARE the understanding; they become the spine of the matrix and belong in the final report.
|
||||
|
||||
#### 2b. Derive the matrix from the flows
|
||||
|
||||
@@ -116,11 +137,11 @@ Walk each flowchart and turn every node and branch into one or more test scenari
|
||||
|
||||
Map changed files to concrete routes (views -> their pages, components -> pages rendering them, layouts -> all pages, stylesheets -> visual regression on key pages) and attach those routes to the flows that exercise them.
|
||||
|
||||
**Load the matrix as a task list** (`TaskCreate`), one task per scenario, so progress is tracked and nothing is skipped. Order tasks by flow, following the flowcharts, not by file.
|
||||
**Load the matrix as a task list** (the harness's task tool, as above), one task per scenario, so progress is tracked and nothing is skipped. Order tasks by flow, following the flowcharts, not by file.
|
||||
|
||||
### Phase 3: Detect Port and Start the Dev Server
|
||||
|
||||
Determine the port (priority: explicit `--port` > a port explicitly stated in your in-context project instructions > `package.json` dev script > `.env*` `PORT=` > default `3000`). If a server is already listening, reuse it; otherwise start the project's dev command in the background and wait for the port to come up. This is the same mechanism `ce-test-browser` uses — follow its Phase 5–6 logic.
|
||||
Determine the port (priority: explicit `--port` > a port explicitly stated in your in-context project instructions > `package.json` dev script > `.env*` `PORT=` > default `3000`). If a server is already listening on it, reuse it. Otherwise start the project's dev command (`bin/dev`, `rails server`, `npm run dev`, etc.) in the background and poll the port until it accepts connections before opening the browser. This skill is hands-off, so start the server automatically without asking — do not block on a confirmation.
|
||||
|
||||
```bash
|
||||
agent-browser open "http://localhost:${PORT}"
|
||||
@@ -139,19 +160,21 @@ Work the task list **one item at a time**. For each scenario, mark the task `in_
|
||||
agent-browser snapshot -i
|
||||
agent-browser click @e1
|
||||
agent-browser fill @e2 "value"
|
||||
agent-browser screenshot <scenario>.png
|
||||
agent-browser screenshot "$(mktemp -d)/<scenario>.png" # scratch dir, not the repo root
|
||||
agent-browser errors # check console/page errors
|
||||
```
|
||||
|
||||
Write transient screenshots to OS temp (e.g. `mktemp -d`), never the repo root. Only copy a screenshot into the report's location if you intend to embed it in the final report.
|
||||
|
||||
3. **Judge** both correctness and experience: right data, right destination, sensible content, no console errors, and does it feel aligned with the product?
|
||||
4. **Walk it as each persona.** Re-run the journey in your head from each primary persona's perspective (from Phase 1) and ask where they'd feel a **paper cut** — a small friction that wouldn't fail a functional test but degrades the experience: a confusing label, an extra click, an unexpected jump, a slow-feeling step, missing feedback, copy that doesn't match how that persona thinks. A scenario can be functionally `Pass` yet still carry paper cuts. Note each paper cut, which persona feels it, and its severity.
|
||||
5. **Record** pass/fail plus any paper cuts, with specifics. Mark the task `completed` only when it genuinely passes (paper cuts are logged, not blockers — fix the sharp ones in Phase 5, surface the rest in the report).
|
||||
5. **Record** pass/fail plus any paper cuts, with specifics. Mark the task `completed` only when it genuinely passes. Paper cuts do not block a `Pass`, but a **sharp** paper cut (one severe enough to fix now) is routed into the Phase 5 fix loop just like a failure — apply the same auto-fix-vs-escalate judgment to it. Log the rest in the report.
|
||||
|
||||
**External-interaction flows** (OAuth, real email delivery, payments, SMS) can't be fully driven headlessly — pause and ask the user to verify that leg, then continue.
|
||||
**External-interaction flows** (OAuth, real email delivery, payments, SMS) can't be fully driven headlessly — pause, ask the user to verify that leg, and mark the scenario `Blocked (needs human verify)` until they confirm. Then continue.
|
||||
|
||||
### Phase 5: Fix Loop (Autonomous)
|
||||
|
||||
When a scenario fails, **fix it and prove it** — but first decide whether the fix is yours to make autonomously or a human's to decide.
|
||||
When a scenario fails — or a passing scenario carries a sharp paper cut worth fixing now — **fix it and prove it**, but first decide whether the fix is yours to make autonomously or a human's to decide.
|
||||
|
||||
**Judge the size of the fix before touching code.** Auto-fix when the change is small, well-understood, and low-risk: a clear bug with an obvious correct fix, contained to a few files, no schema/architecture/product trade-off. **Do not auto-fix** when the change is large or ambiguous — it requires an architectural or schema decision, changes product behavior or UX intent, spans many files, has plausible competing solutions, or you're not confident the "right" answer is unambiguous. Forcing a big judgment call autonomously is worse than escalating it.
|
||||
|
||||
@@ -159,14 +182,16 @@ When a scenario fails, **fix it and prove it** — but first decide whether the
|
||||
|
||||
1. Investigate the root cause. If it's non-obvious, use `ce-debug`.
|
||||
2. Apply the fix in the code.
|
||||
3. **Add an automated regression test** that fails before the fix and passes after, so the bug can't return.
|
||||
3. **Add an automated regression test** that fails before the fix and passes after, so the bug can't return. This is the default for behavioral and code bugs. When an automated test is genuinely impractical — a pure copy, spacing, or visual fix with no behavioral assertion to make — substitute a documented browser-replay or screenshot check and **state in the report why no automated test was meaningful**. Do not invent a hollow test just to satisfy the step.
|
||||
4. Commit the fix with a clear message (use `ce-commit`). One logical fix per commit.
|
||||
5. Re-run the failing scenario in the browser to confirm it now passes; then continue the matrix.
|
||||
6. If the bug carried a reusable lesson, capture it with `ce-compound`.
|
||||
|
||||
**For changes too big to make autonomously:** do not implement. Record it in the report's **Decisions for a human** section with: what's broken, why it's not a safe autonomous fix, the options you see (with trade-offs), and your recommendation. Mark the scenario `Blocked (human decision)` in the matrix, then continue with the rest. Never make a large, irreversible, or product-altering change just to clear a matrix item.
|
||||
|
||||
Keep iterating until every task is `completed` or explicitly `Blocked (human decision)`. Re-test anything a fix might have affected (watch for regressions in adjacent journeys).
|
||||
Keep iterating until every task is `completed` or in a terminal `Blocked` state — `Blocked (human decision)` (escalated here) or `Blocked (needs human verify)` (set in Phase 4 for external-interaction legs). Both are terminal for the loop: they wait on a person, so do not re-queue them. Re-test anything a fix might have affected (watch for regressions in adjacent journeys).
|
||||
|
||||
**Before declaring the branch ready, run the project's automated test suite once** (the new regression tests plus everything that already exists). Discover the test command from the project's active instructions and conventions already in your context — do not assume a specific runner. Record the result in the report; a green matrix with a red suite is not "ready."
|
||||
|
||||
### Phase 6: Write the Report Artifact
|
||||
|
||||
@@ -174,14 +199,16 @@ The report doc was created at the end of Phase 2 and updated incrementally throu
|
||||
|
||||
Use `references/dogfood-report-template.md` as the shape — the same way plans and brainstorms are captured from a template. The finalized artifact must include:
|
||||
|
||||
1. **Diff Summary** — what changed between the branch and `main`.
|
||||
1. **Diff Summary** — what changed between the branch and the trunk.
|
||||
2. **Personas** — the primary personas evaluated against (and their source: STRATEGY.md / VISION.md / inferred).
|
||||
3. **Flows tested** — the Mermaid flowcharts from Phase 2a, so the journeys are preserved.
|
||||
4. **Test Matrix & Results** — every scenario: what was tested, pass/fail, issue found, fix applied, commit SHA.
|
||||
5. **What was fixed** — each bug, its root cause, the fix, the regression test added, and the commit.
|
||||
5. **What was fixed** — each bug, its root cause, the fix, the regression test added (or why none was meaningful), and the commit.
|
||||
6. **Paper cuts (by persona)** — experiential friction found, which persona feels each, severity, and whether fixed or deferred.
|
||||
7. **Decisions for a human** — issues too big to fix autonomously: what's broken, why it was escalated, options with trade-offs, and a recommendation.
|
||||
8. **Learnings** — reusable lessons worth carrying forward (feed substantial ones to `ce-compound`).
|
||||
9. **Final Status** — readiness verdict, plus anything still blocked or needing human verification.
|
||||
7. **Console Errors** — any console or network errors observed, and whether they were resolved.
|
||||
8. **Human Verifications** — external-interaction legs (OAuth, email, payments, SMS): confirmed, pending, or not applicable.
|
||||
9. **Decisions for a human** — issues too big to fix autonomously: what's broken, why it was escalated, options with trade-offs, and a recommendation.
|
||||
10. **Learnings** — reusable lessons worth carrying forward (feed substantial ones to `ce-compound`).
|
||||
11. **Final Status** — readiness verdict (including the automated-suite result), plus anything still blocked or needing human verification.
|
||||
|
||||
Use repo-relative paths in the doc, never absolute paths, so it stays portable.
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
# Dogfood Report — <branch>
|
||||
|
||||
> Diff-scoped browser QA of `<branch>` vs `main`. Generated by `/ce-dogfood-beta` on <YYYY-MM-DD>.
|
||||
> Diff-scoped browser QA of `<branch>` vs the trunk. Generated by `/ce-dogfood` on <YYYY-MM-DD>.
|
||||
|
||||
## Diff Summary
|
||||
|
||||
@@ -43,7 +43,13 @@ For each issue found and fixed:
|
||||
- **Symptom:** <what the user saw / what failed in the browser>
|
||||
- **Root cause:** <why it happened>
|
||||
- **Fix:** <what changed, repo-relative file paths>
|
||||
- **Regression test:** <test added that fails before / passes after>
|
||||
- **Regression test:** <test added that fails before / passes after — or, for a pure copy/visual fix, the browser-replay/screenshot check used and why no automated test was meaningful>
|
||||
|
||||
## Paper Cuts (by persona)
|
||||
|
||||
<Experiential friction found while walking each flow as each persona. A scenario can `Pass` functionally and still carry paper cuts. Note the persona, severity, and whether it was fixed (sharp ones, via the Phase 5 loop) or deferred. "None" if clean.>
|
||||
|
||||
- **<Persona>** — <paper cut> — <severity> — <fixed `<commit>` / deferred>
|
||||
|
||||
## Console Errors
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: ce-worktree
|
||||
description: Set up isolated git worktrees. Use when starting isolated work, or when ce-work/ce-code-review offers a worktree option; detect existing isolation first.
|
||||
description: Set up isolated git worktrees — create a new branch for fresh work, or attach a worktree to an existing branch/PR/commit to work on it in isolation. Use when starting isolated work or isolating an existing ref; detects existing isolation first.
|
||||
---
|
||||
|
||||
# Worktree Isolation
|
||||
@@ -9,6 +9,13 @@ Ensure the current work happens in an isolated workspace, without disturbing the
|
||||
|
||||
Order of operations: **detect existing isolation -> prefer a native worktree tool -> fall back to plain git.** Never create a worktree the harness cannot see.
|
||||
|
||||
**Two modes, set by the caller's need:**
|
||||
|
||||
- **New work (default).** No specific ref named — create a fresh branch from a base (trunk). This is what `ce-work` uses.
|
||||
- **Isolate an existing ref.** The caller names a ref to work on in isolation — a PR head, an existing branch, or a commit. Attach the worktree to that ref instead of creating a new branch. One hard git rule governs this mode: **a branch can be checked out in only one worktree at a time.** If the named ref is already checked out somewhere (most commonly because it is the current branch in the primary checkout), do **not** create a second worktree for it — report that it is already checked out at `<path>` and let the caller act (work there in place; or, only if a clean separate tree is essential, create a *detached* worktree at the same commit). Never put one branch in two worktrees.
|
||||
|
||||
The steps below (detect -> native tool -> git fallback) apply to both modes; the mode only changes what gets checked out and is reported back to the caller.
|
||||
|
||||
## Step 0: Detect existing isolation
|
||||
|
||||
Before creating anything, check whether the current directory is already a linked worktree. Compare the **resolved absolute** git dir against the **resolved absolute** common git dir — resolve each to an absolute path first and compare those, not the raw `git rev-parse` output. Git mixes absolute and relative forms depending on the current directory (from a subdirectory of a normal checkout, `--git-dir` comes back absolute while `--git-common-dir` may be relative), so a raw string compare yields a false "already isolated":
|
||||
@@ -27,7 +34,7 @@ git rev-parse --show-superproject-working-tree
|
||||
```
|
||||
|
||||
- **Non-empty** output -> you are in a submodule; treat it as a normal checkout and continue to Step 1.
|
||||
- **Empty** output -> you are **already in an isolated worktree**. Report the worktree path (`git rev-parse --show-toplevel`) and current branch, and **work in place**. Do not create another worktree — a worktree-from-worktree lands in the wrong tree and is invisible to the harness that made the current one.
|
||||
- **Empty** output -> you are **already in an isolated worktree**. Report the worktree path (`git rev-parse --show-toplevel`) and current branch. Do not create another worktree — a worktree-from-worktree lands in the wrong tree and is invisible to the harness that made the current one. Then **work in place**: in new-work mode, continue here; in isolate-an-existing-ref mode, check that ref out here (unless it is already the current branch) rather than nesting a worktree.
|
||||
|
||||
## Step 1: Prefer the harness's native worktree tool
|
||||
|
||||
@@ -41,8 +48,10 @@ Only when there is no native tool **and** Step 0 found no existing isolation.
|
||||
2. Choose a meaningful branch name from the work description (e.g. `feat/login`, `fix/email-validation`) — avoid opaque auto-generated names. Pick a base branch (default: origin's default branch, else `main`).
|
||||
3. **Ensure `.worktrees/` is gitignored before creating anything**, so worktree contents are never committed: check `git check-ignore -q .worktrees/` — **with the trailing slash**, so an existing directory-only `.worktrees/` rule is honored even before the directory exists (`git check-ignore .worktrees` without the slash would miss it and dirty a correctly-configured repo). If it is not ignored, add a `.worktrees/` line to `.gitignore`.
|
||||
4. Best-effort refresh the base branch without disturbing the current checkout: `git fetch origin <from-branch>`. This is **non-fatal** — if it errors (no `origin` remote, a differently-named remote, or a local-only branch), do not abort; continue to the next step and use the local ref.
|
||||
5. Create the worktree from the remote base when available, else the local ref: `git worktree add -b <branch-name> .worktrees/<branch-name> origin/<from-branch>`. If `origin/<from-branch>` does not exist, use the local `<from-branch>` ref instead.
|
||||
6. Switch into it: `cd .worktrees/<branch-name>`.
|
||||
5. Create the worktree — the command depends on the mode:
|
||||
- **New work:** `git worktree add -b <branch-name> .worktrees/<branch-name> origin/<from-branch>` (use the local `<from-branch>` ref if `origin/<from-branch>` does not exist). This creates a new branch from the base.
|
||||
- **Isolate an existing ref:** attach to the ref instead of branching — for an existing branch or tag, `git worktree add .worktrees/<slug> <target-ref>`. For a **PR**, check it out **on a local branch** (never a detached `FETCH_HEAD` — that orphans the fix loop's commits instead of updating the PR): `git fetch origin pull/<n>/head:pr-<n>` then `git worktree add .worktrees/pr-<n> pr-<n>`. (To get push-tracking back to the PR instead, create the worktree detached first — `git worktree add --detach .worktrees/pr-<n>` — then `cd` in and run `gh pr checkout <n>`, which is fork-safe.) If git reports the ref is already checked out elsewhere, follow the already-checked-out rule under **Two modes** — do not force a second worktree.
|
||||
6. Switch into it: `cd .worktrees/<branch-name>` (or `.worktrees/<slug>`).
|
||||
|
||||
If `git worktree add` fails with a sandbox or permission error, the requested isolation could not be created. This needs a **blocking** user decision before touching the current checkout — do not silently continue there (the user chose isolation specifically to avoid it, especially when `ce-work` / `ce-code-review` routed here for the worktree option). Report the failure and ask via the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_question` in Antigravity CLI (`agy`), `ask_user` in Pi (via the `pi-ask-user` extension) — offering options such as "work in the current checkout" vs "stop and resolve the permission issue". If no blocking tool exists in the harness or the call errors, present the numbered options in chat and wait for the reply; never skip the confirmation. Only work in the current checkout on explicit confirmation, and do not retry alternative paths automatically.
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ const EXTRA_LEGACY_ARTIFACTS_BY_PLUGIN: Record<string, LegacyPluginArtifacts> =
|
||||
"ce-dhh-rails-style",
|
||||
"ce-doctor",
|
||||
"ce-document-review",
|
||||
"ce-dogfood-beta",
|
||||
"ce-frontend-design",
|
||||
"ce-gemini-imagegen",
|
||||
"ce-feature-video",
|
||||
@@ -262,6 +263,8 @@ const EXTRA_LEGACY_ARTIFACTS_BY_PLUGIN: Record<string, LegacyPluginArtifacts> =
|
||||
"compound:plan",
|
||||
"compound:review",
|
||||
"compound:work",
|
||||
"ce-dogfood-beta",
|
||||
"ce-polish-beta",
|
||||
"create-agent-skill",
|
||||
"deepen-plan",
|
||||
"deprecated:deepen-plan",
|
||||
|
||||
@@ -88,6 +88,8 @@ export const STALE_SKILL_DIRS = [
|
||||
"ce-review-beta",
|
||||
// ce-polish-beta -> ce-polish (promoted to stable)
|
||||
"ce-polish-beta",
|
||||
// ce-dogfood-beta -> ce-dogfood (promoted to stable)
|
||||
"ce-dogfood-beta",
|
||||
|
||||
// Removed skills (no replacement)
|
||||
"ce-andrew-kane-gem-writer",
|
||||
@@ -325,6 +327,9 @@ const LEGACY_SKILL_DESCRIPTION_ALIASES: Record<string, string[]> = {
|
||||
"ce-polish-beta": [
|
||||
"Start the dev server, open the feature in a browser, and iterate on improvements together. Manual invocation only — type /ce-polish to run it.",
|
||||
],
|
||||
"ce-dogfood-beta": [
|
||||
"[BETA] Hands-off end-to-end branch dogfood pass with browser testing, auto-fixes, regression tests, and fix commits.",
|
||||
],
|
||||
proof: [
|
||||
"Publish, view, comment on, and edit markdown via Proof (proofeditor.ai) — create a shareable doc, read a shared doc, and make comment/suggestion/block edits over its API. Use when the user says \"view this in proof\", \"share to proof\", \"publish to proof\", or wants a shareable markdown surface for a spec, plan, or draft, including publish handoffs from ce-brainstorm, ce-ideate, or ce-plan. Do not trigger on \"proof\" meaning evidence, math proofs, proof-of-concept, or \"proofread this\".",
|
||||
],
|
||||
@@ -697,6 +702,14 @@ function currentSkillNameForLegacy(legacyName: string): string {
|
||||
return "ce-doc-review"
|
||||
case "ce-review":
|
||||
return "ce-code-review"
|
||||
// Promoted-from-beta renames: map to the shipping stable name so cleanup
|
||||
// seeds a fingerprint. Without this, loadLegacyFingerprints leaves the
|
||||
// description undefined and isLegacyPluginOwned bails, so the stale beta
|
||||
// dir is never swept on upgrade.
|
||||
case "ce-polish-beta":
|
||||
return "ce-polish"
|
||||
case "ce-dogfood-beta":
|
||||
return "ce-dogfood"
|
||||
default:
|
||||
return legacyName.startsWith("ce-") ? legacyName : `ce-${legacyName}`
|
||||
}
|
||||
|
||||
@@ -160,6 +160,35 @@ describe("cleanupStaleSkillDirs", () => {
|
||||
expect(await exists(path.join(root, "ce-document-review"))).toBe(false)
|
||||
})
|
||||
|
||||
test("removes promoted-from-beta skill dirs via their last-shipped beta description (ce-dogfood-beta, ce-polish-beta)", async () => {
|
||||
// Regression: a beta->stable rename only sweeps the stale flat-install dir if
|
||||
// currentSkillNameForLegacy maps the beta name to the shipping stable skill.
|
||||
// Without that mapping, loadLegacyFingerprints leaves the description undefined
|
||||
// and isLegacyPluginOwned bails before consulting the alias. The on-disk dirs
|
||||
// below still carry the OLD beta description, matched via LEGACY_SKILL_DESCRIPTION_ALIASES.
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "cleanup-beta-promoted-"))
|
||||
await createDir(
|
||||
path.join(root, "ce-dogfood-beta"),
|
||||
skillContent(
|
||||
"ce-dogfood-beta",
|
||||
"[BETA] Hands-off end-to-end branch dogfood pass with browser testing, auto-fixes, regression tests, and fix commits.",
|
||||
),
|
||||
)
|
||||
await createDir(
|
||||
path.join(root, "ce-polish-beta"),
|
||||
skillContent(
|
||||
"ce-polish-beta",
|
||||
"Start the dev server, open the feature in a browser, and iterate on improvements together. Manual invocation only — type /ce-polish to run it.",
|
||||
),
|
||||
)
|
||||
|
||||
const removed = await cleanupStaleSkillDirs(root)
|
||||
|
||||
expect(removed).toBe(2)
|
||||
expect(await exists(path.join(root, "ce-dogfood-beta"))).toBe(false)
|
||||
expect(await exists(path.join(root, "ce-polish-beta"))).toBe(false)
|
||||
})
|
||||
|
||||
test("removes raw colon workflow skill directories", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "cleanup-colon-workflows-"))
|
||||
await createDir(
|
||||
|
||||
@@ -143,7 +143,7 @@ const DESCRIPTION_CHAR_BUDGET = 1024
|
||||
const NAME_CHAR_BUDGET = 64
|
||||
|
||||
const EXPECTED_USER_INVOKED_SKILLS = new Set([
|
||||
"ce-dogfood-beta",
|
||||
"ce-dogfood",
|
||||
"ce-polish",
|
||||
"ce-product-pulse",
|
||||
"ce-promote",
|
||||
|
||||
Reference in New Issue
Block a user