codebyplan 1.11.2 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/cli.js +590 -405
  2. package/package.json +1 -1
  3. package/templates/hooks/README.md +1 -13
  4. package/templates/hooks/cbp-statusline.mjs +44 -0
  5. package/templates/hooks/cbp-statusline.py +24 -2
  6. package/templates/hooks/cbp-statusline.sh +22 -2
  7. package/templates/hooks/cbp-test-coverage-gate.sh +8 -0
  8. package/templates/hooks/cbp-test-hooks.sh +0 -42
  9. package/templates/hooks/hooks.json +0 -9
  10. package/templates/rules/README.md +8 -1
  11. package/templates/rules/supabase-branch-lifecycle.md +99 -0
  12. package/templates/settings.project.base.json +1 -2
  13. package/templates/skills/cbp-build-cc-settings/reference/cbp-conventions.md +1 -2
  14. package/templates/skills/cbp-checkpoint-create/SKILL.md +2 -0
  15. package/templates/skills/cbp-checkpoint-end/SKILL.md +27 -5
  16. package/templates/skills/cbp-checkpoint-start/SKILL.md +2 -2
  17. package/templates/skills/cbp-git-worktree-remove/SKILL.md +17 -1
  18. package/templates/skills/cbp-session-start/SKILL.md +28 -3
  19. package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/SKILL.md +1 -1
  20. package/templates/skills/cbp-setup-eslint/SKILL.md +199 -0
  21. package/templates/skills/cbp-setup-eslint/reference/base.md +82 -0
  22. package/templates/skills/cbp-setup-eslint/reference/cli.md +56 -0
  23. package/templates/skills/cbp-setup-eslint/reference/e2e.md +68 -0
  24. package/templates/skills/cbp-setup-eslint/reference/jest.md +59 -0
  25. package/templates/skills/cbp-setup-eslint/reference/nestjs.md +69 -0
  26. package/templates/skills/cbp-setup-eslint/reference/nextjs.md +63 -0
  27. package/templates/skills/cbp-setup-eslint/reference/node.md +74 -0
  28. package/templates/skills/cbp-setup-eslint/reference/react-native.md +60 -0
  29. package/templates/skills/cbp-setup-eslint/reference/react.md +82 -0
  30. package/templates/skills/cbp-setup-eslint/reference/tailwind.md +64 -0
  31. package/templates/skills/cbp-setup-eslint/reference/testing-react.md +57 -0
  32. package/templates/skills/cbp-setup-eslint/reference/vitest.md +62 -0
  33. package/templates/skills/cbp-ship-main/SKILL.md +13 -0
  34. package/templates/skills/cbp-supabase-branch-check/SKILL.md +12 -5
  35. package/templates/skills/cbp-supabase-migrate/SKILL.md +139 -9
  36. package/templates/skills/cbp-supabase-migrate/reference/preflight-dry-run.md +1 -1
  37. package/templates/skills/cbp-supabase-setup/SKILL.md +13 -7
  38. package/templates/skills/cbp-supabase-setup/reference/branching-setup.md +2 -2
  39. package/templates/skills/cbp-task-complete/SKILL.md +1 -3
  40. package/templates/skills/cbp-task-start/SKILL.md +5 -3
  41. package/templates/hooks/cbp-mcp-worktree-inject.sh +0 -76
  42. /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/maestro.md +0 -0
  43. /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/playwright.md +0 -0
  44. /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/tauri.md +0 -0
  45. /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/vscode.md +0 -0
  46. /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/xcuitest.md +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.11.2",
3
+ "version": "1.13.0",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -228,18 +228,6 @@ Denies any `git stash` command (including `git -C <dir> stash` and `git stash po
228
228
 
229
229
  ---
230
230
 
231
- ### `cbp-mcp-worktree-inject.sh` — PreToolUse, matcher `mcp__codebyplan__(update_task|complete_task|complete_round|update_checkpoint)`
232
-
233
- Auto-injects `caller_worktree_id` into CodeByPlan MCP mutation calls when it's missing, resolving it via `npx codebyplan resolve-worktree` (with `--fallback-from-branch`). Closes the manual worktree-pinning workaround so MCP writes pass the server-side worktree pre-guard without hand-editing every call.
234
-
235
- **Blocks vs warns**: neither — emits an `updatedInput` to add the field when resolvable, otherwise passes the call through unchanged (graceful passthrough preserves backwards-compat).
236
-
237
- **Skips when**: the call already carries `caller_worktree_id`, or no worktree can be resolved (both → clean passthrough). A no-op in repos that don't use the CodeByPlan MCP.
238
-
239
- **Opt out**: settings.json override removing this entry, or plugin disable.
240
-
241
- ---
242
-
243
231
  ### `cbp-mcp-round-sync.sh` — PostToolUse, matcher `mcp__codebyplan__complete_round`
244
232
 
245
233
  After a `complete_round` MCP call succeeds, reconciles the round's `files_changed[]` against `git status`: new files in the diff are added (unapproved), and approval records no longer in the diff are flagged `removed_from_diff` (never deleted — lifecycle preserved).
@@ -258,7 +246,7 @@ After a `complete_round` MCP call succeeds, reconciles the round's `files_change
258
246
 
259
247
  Test suite for the plugin's 10 registered hooks. Runs two passes:
260
248
 
261
- 1. **Header check** — every registered hook (`lint-format-on-edit`, `test-coverage-gate`, `pre-commit-quality-gate`, `maestro-yaml-validate`, `notify`, `auto-test-hooks`, `mcp-migration-guard`, `validate-git-stash-deny`, `cbp-mcp-worktree-inject`, `cbp-mcp-round-sync`) carries the required `# Hook:` and `# Purpose:` header comments. `statusline` uses its own `# Claude Code Status Line` marker.
249
+ 1. **Header check** — every registered hook (`lint-format-on-edit`, `test-coverage-gate`, `pre-commit-quality-gate`, `maestro-yaml-validate`, `notify`, `auto-test-hooks`, `mcp-migration-guard`, `validate-git-stash-deny`, `cbp-mcp-round-sync`) carries the required `# Hook:` and `# Purpose:` header comments. `statusline` uses its own `# Claude Code Status Line` marker.
262
250
  2. **Functional smoke tests** — each hook is invoked with synthetic stdin matching its fast-path / graceful-degrade input; all must exit 0.
263
251
 
264
252
  Not in `hooks.json` — invoked indirectly via `auto-test-hooks.sh` on hook edits, or directly via `bash ${CLAUDE_PLUGIN_ROOT}/hooks/test-hooks.sh`.
@@ -118,6 +118,7 @@ function main() {
118
118
  rate_limits: true,
119
119
  repo_pr: true,
120
120
  worktree: true,
121
+ infra_drift: true,
121
122
  no_color: false,
122
123
  };
123
124
  try {
@@ -136,6 +137,7 @@ function main() {
136
137
  "rate_limits",
137
138
  "repo_pr",
138
139
  "worktree",
140
+ "infra_drift",
139
141
  ]) {
140
142
  if (typeof parsed.lines[k] === "boolean") cfg[k] = parsed.lines[k];
141
143
  }
@@ -374,6 +376,48 @@ function main() {
374
376
  }
375
377
  }
376
378
 
379
+ // ============================================================
380
+ // LINE 7 — Infra drift (monorepo feat branches behind main)
381
+ // ============================================================
382
+ // Only the codebyplan monorepo (templates/ present) on a feat branch can carry
383
+ // stale .claude/ infra. No fetch — counts against the cached origin/main only.
384
+ if (shouldShow("INFRA_DRIFT", cfg.infra_drift)) {
385
+ if (
386
+ BRANCH.startsWith("feat/") &&
387
+ fs.existsSync(
388
+ path.join(root, "packages", "codebyplan-package", "templates")
389
+ )
390
+ ) {
391
+ let behind = 0;
392
+ try {
393
+ behind = parseInt(
394
+ execFileSync(
395
+ "git",
396
+ [
397
+ "-C",
398
+ root,
399
+ "rev-list",
400
+ "--count",
401
+ "HEAD..origin/main",
402
+ "--",
403
+ ".claude",
404
+ "packages/codebyplan-package/templates",
405
+ ],
406
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
407
+ ).trim(),
408
+ 10
409
+ );
410
+ } catch {
411
+ behind = 0;
412
+ }
413
+ if (Number.isFinite(behind) && behind > 0) {
414
+ out.push(
415
+ `${C.YELLOW}⚠ infra ${behind} behind${C.RST} ${C.DIM}→ /cbp-refresh-infra${C.RST}`
416
+ );
417
+ }
418
+ }
419
+ }
420
+
377
421
  process.stdout.write(out.length ? out.join("\n") + "\n" : "");
378
422
  }
379
423
 
@@ -80,7 +80,8 @@ def main():
80
80
  # ---- Config: line toggles + no_color -------------------------------------
81
81
  cfg = {
82
82
  "identity": True, "context": True, "cost": True,
83
- "rate_limits": True, "repo_pr": True, "worktree": True, "no_color": False,
83
+ "rate_limits": True, "repo_pr": True, "worktree": True,
84
+ "infra_drift": True, "no_color": False,
84
85
  }
85
86
  try:
86
87
  with open(os.path.join(root, ".codebyplan", "statusline.json"), "r", encoding="utf-8") as fh:
@@ -90,7 +91,7 @@ def main():
90
91
  cfg["no_color"] = parsed["no_color"]
91
92
  lines = parsed.get("lines")
92
93
  if isinstance(lines, dict):
93
- for k in ["identity", "context", "cost", "rate_limits", "repo_pr", "worktree"]:
94
+ for k in ["identity", "context", "cost", "rate_limits", "repo_pr", "worktree", "infra_drift"]:
94
95
  if isinstance(lines.get(k), bool):
95
96
  cfg[k] = lines[k]
96
97
  except Exception:
@@ -321,6 +322,27 @@ def main():
321
322
  l6 += " %s%s%s" % (DIM, wt_path_disp, RST)
322
323
  out.append(l6)
323
324
 
325
+ # ===== LINE 7 — Infra drift (monorepo feat branches behind main) =====
326
+ # Only the codebyplan monorepo (templates/ present) on a feat branch can carry
327
+ # stale .claude/ infra. No fetch — counts against the cached origin/main only.
328
+ if should_show("INFRA_DRIFT", cfg["infra_drift"]):
329
+ if branch.startswith("feat/") and os.path.isdir(
330
+ os.path.join(root, "packages", "codebyplan-package", "templates")
331
+ ):
332
+ behind = 0
333
+ try:
334
+ res = subprocess.run(
335
+ ["git", "-C", root, "rev-list", "--count", "HEAD..origin/main",
336
+ "--", ".claude", "packages/codebyplan-package/templates"],
337
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True,
338
+ )
339
+ if res.returncode == 0:
340
+ behind = int(res.stdout.strip() or "0")
341
+ except Exception:
342
+ behind = 0
343
+ if behind > 0:
344
+ out.append("%s⚠ infra %d behind%s %s→ /cbp-refresh-infra%s" % (YELLOW, behind, RST, DIM, RST))
345
+
324
346
  sys.stdout.write(("\n".join(out) + "\n") if out else "")
325
347
 
326
348
 
@@ -15,7 +15,7 @@
15
15
  #
16
16
  # DISPLAY OPTIONS (team-shared, committed)
17
17
  # .codebyplan/statusline.json -> { "lines": {identity,context,cost,rate_limits,
18
- # repo_pr,worktree}, "no_color": bool }
18
+ # repo_pr,worktree,infra_drift}, "no_color": bool }
19
19
  #
20
20
  # ENV-VAR OVERRIDES (env > config > default)
21
21
  # CBP_STATUSLINE_HIDE_IDENTITY=1 suppress line 1 (folder, branch, model, effort, …)
@@ -24,6 +24,7 @@
24
24
  # CBP_STATUSLINE_HIDE_RATE_LIMITS=1 suppress line 4 (5h / 7d rate limits)
25
25
  # CBP_STATUSLINE_HIDE_REPO_PR=1 suppress line 5 (repo host/owner/name, PR)
26
26
  # CBP_STATUSLINE_HIDE_WORKTREE=1 suppress line 6 (worktree name/branch/path)
27
+ # CBP_STATUSLINE_HIDE_INFRA_DRIFT=1 suppress line 7 (.claude infra commits behind main)
27
28
  # CBP_STATUSLINE_NO_COLOR=1 strip all ANSI colour codes (also honoured by $NO_COLOR)
28
29
  #
29
30
  # TEST SEAMS (no effect in normal use)
@@ -104,7 +105,7 @@ eval "$(echo "$INPUT" | jq -r '
104
105
 
105
106
  # ---- Config: line toggles + no_color from .codebyplan/statusline.json --------
106
107
  CFG_IDENTITY=true; CFG_CONTEXT=true; CFG_COST=true
107
- CFG_RATE_LIMITS=true; CFG_REPO_PR=true; CFG_WORKTREE=true; CFG_NO_COLOR=false
108
+ CFG_RATE_LIMITS=true; CFG_REPO_PR=true; CFG_WORKTREE=true; CFG_INFRA_DRIFT=true; CFG_NO_COLOR=false
108
109
  CBP_CFG="$CBP_ROOT/.codebyplan/statusline.json"
109
110
  if [ -f "$CBP_CFG" ] && command -v jq >/dev/null 2>&1; then
110
111
  # Use `!= false` / `== true` (NOT jq `//`): the `//` operator treats an explicit
@@ -117,6 +118,7 @@ if [ -f "$CBP_CFG" ] && command -v jq >/dev/null 2>&1; then
117
118
  "CFG_RATE_LIMITS=\(.lines.rate_limits != false)",
118
119
  "CFG_REPO_PR=\(.lines.repo_pr != false)",
119
120
  "CFG_WORKTREE=\(.lines.worktree != false)",
121
+ "CFG_INFRA_DRIFT=\(.lines.infra_drift != false)",
120
122
  "CFG_NO_COLOR=\(.no_color == true)"
121
123
  ' "$CBP_CFG" 2>/dev/null)"
122
124
  fi
@@ -401,3 +403,21 @@ if should_show WORKTREE "$CFG_WORKTREE"; then
401
403
  printf "%b\n" "$L6"
402
404
  fi
403
405
  fi
406
+
407
+ # ============================================================
408
+ # LINE 7 — Infra drift (monorepo feat branches behind main)
409
+ # ============================================================
410
+ # Only the codebyplan monorepo (templates/ present) on a feat branch can carry
411
+ # stale .claude/ infra. No fetch — counts against the cached origin/main only.
412
+ if should_show INFRA_DRIFT "$CFG_INFRA_DRIFT"; then
413
+ case "$BRANCH" in
414
+ feat/*)
415
+ if [ -d "$CBP_ROOT/packages/codebyplan-package/templates" ]; then
416
+ BEHIND="$(git -C "$CBP_ROOT" rev-list --count HEAD..origin/main -- .claude packages/codebyplan-package/templates 2>/dev/null)"
417
+ if [ -n "$BEHIND" ] && [ "$BEHIND" -gt 0 ] 2>/dev/null; then
418
+ printf "%b\n" "${YELLOW}⚠ infra ${BEHIND} behind${RST} ${DIM}→ /cbp-refresh-infra${RST}"
419
+ fi
420
+ fi
421
+ ;;
422
+ esac
423
+ fi
@@ -64,6 +64,14 @@ while IFS= read -r FILE; do
64
64
  continue
65
65
  fi
66
66
 
67
+ # Skip files under a __tests__/ directory — fixtures, helpers, setup, and
68
+ # other test infrastructure are imported by the test files that exercise
69
+ # them; requiring a dedicated .test.ts for a fixture is nonsensical.
70
+ if echo "$FILE" | grep -qE '/__tests__/'; then
71
+ SKIPPED=$((SKIPPED + 1))
72
+ continue
73
+ fi
74
+
67
75
  # Skip infrastructure / generated / config files (generic skips — harmless if user doesn't have these dirs)
68
76
  if echo "$FILE" | grep -qE '^\.claude/|^docs/|^supabase/|\.config\.|\.json$|\.md$|\.ya?ml$|\.sh$|\.scss$|\.css$'; then
69
77
  continue
@@ -207,48 +207,6 @@ else
207
207
  fi
208
208
  fi
209
209
 
210
- # --- cbp-mcp-worktree-inject.sh ---
211
-
212
- if [ ! -f "$HOOKS_DIR/cbp-mcp-worktree-inject.sh" ]; then
213
- test_result "cbp-mcp-worktree-inject.sh present" "passed" "missing"
214
- else
215
- test_result "cbp-mcp-worktree-inject.sh present" "passed" "passed"
216
-
217
- FIRST_LINE=$(head -1 "$HOOKS_DIR/cbp-mcp-worktree-inject.sh")
218
- if echo "$FIRST_LINE" | grep -q '^#!/'; then
219
- test_result "cbp-mcp-worktree-inject.sh has shebang" "passed" "passed"
220
- else
221
- test_result "cbp-mcp-worktree-inject.sh has shebang" "passed" "missing"
222
- fi
223
-
224
- if grep -q '@scope: org-shared' "$HOOKS_DIR/cbp-mcp-worktree-inject.sh"; then
225
- test_result "cbp-mcp-worktree-inject.sh has @scope: org-shared" "passed" "passed"
226
- else
227
- test_result "cbp-mcp-worktree-inject.sh has @scope: org-shared" "passed" "missing"
228
- fi
229
-
230
- FIXTURES_DIR="$HOOKS_DIR/__test-fixtures__/cbp-mcp-worktree-inject"
231
- if [ -d "$FIXTURES_DIR" ]; then
232
- # already-has-id.json — expect exit 0 (passthrough)
233
- ACTUAL_EXIT=$(bash "$HOOKS_DIR/cbp-mcp-worktree-inject.sh" < "$FIXTURES_DIR/already-has-id.json" >/dev/null 2>&1; echo $?)
234
- if [ "$ACTUAL_EXIT" = "0" ]; then
235
- test_result "cbp-mcp-worktree-inject.sh already-has-id exits 0" "passed" "passed"
236
- else
237
- test_result "cbp-mcp-worktree-inject.sh already-has-id exits 0" "passed" "failed (exit $ACTUAL_EXIT)"
238
- fi
239
-
240
- # missing-id-both-empty.json — resolvers return empty (in test env), expect exit 0
241
- ACTUAL_EXIT=$(bash "$HOOKS_DIR/cbp-mcp-worktree-inject.sh" < "$FIXTURES_DIR/missing-id-both-empty.json" >/dev/null 2>&1; echo $?)
242
- if [ "$ACTUAL_EXIT" = "0" ]; then
243
- test_result "cbp-mcp-worktree-inject.sh missing-id passthrough exits 0" "passed" "passed"
244
- else
245
- test_result "cbp-mcp-worktree-inject.sh missing-id passthrough exits 0" "passed" "failed (exit $ACTUAL_EXIT)"
246
- fi
247
- else
248
- test_result "cbp-mcp-worktree-inject.sh fixtures dir present" "passed" "missing"
249
- fi
250
- fi
251
-
252
210
  # --- cbp-mcp-round-sync.sh ---
253
211
 
254
212
  if [ ! -f "$HOOKS_DIR/cbp-mcp-round-sync.sh" ]; then
@@ -35,15 +35,6 @@
35
35
  "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/cbp-mcp-migration-guard.sh"
36
36
  }
37
37
  ]
38
- },
39
- {
40
- "matcher": "mcp__codebyplan__(update_task|complete_task|complete_round|update_checkpoint)",
41
- "hooks": [
42
- {
43
- "type": "command",
44
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/cbp-mcp-worktree-inject.sh"
45
- }
46
- ]
47
38
  }
48
39
  ],
49
40
  "PostToolUse": [
@@ -34,7 +34,14 @@ The `install`/`update`/`uninstall` flow handles these files identically to how i
34
34
 
35
35
  ## Current status
36
36
 
37
- Zero rules are shipped today. The wiring is in place so future PRs can land curated rules through the same install/update/uninstall flow as other templates.
37
+ Four rules are shipped:
38
+
39
+ | Rule file | Summary |
40
+ |---|---|
41
+ | `scope-vocabulary.md` | Canonical scope-marker enum (`org-shared` / `project-shared` / `repo-only:<name>`) enforced by three validators |
42
+ | `context-file-loading.md` | Context-file load contract — who loads what, when, and how missing files are handled |
43
+ | `todo-backend.md` | Todos queue contract, six DB-layer workflow invariants, and writer obligations for MCP mutators |
44
+ | `supabase-branch-lifecycle.md` | Supabase preview-branch lifecycle mirrors the git feat-branch lifecycle — lazy create on first DB change, delete wherever the git branch is removed |
38
45
 
39
46
  ## Contributing a rule
40
47
 
@@ -0,0 +1,99 @@
1
+ ---
2
+ scope: org-shared
3
+ description: Supabase preview-branch lifecycle mirrors the git feat-branch lifecycle
4
+ paths:
5
+ - "supabase/migrations/**"
6
+ - ".codebyplan/shipment.json"
7
+ - ".codebyplan/git.json"
8
+ - ".claude/skills/cbp-supabase-migrate/**"
9
+ - ".claude/skills/cbp-supabase-branch-check/**"
10
+ - ".claude/skills/cbp-checkpoint-end/**"
11
+ - ".claude/skills/cbp-git-worktree-remove/**"
12
+ - ".claude/skills/cbp-ship-main/**"
13
+ ---
14
+
15
+ # Supabase Branch Lifecycle
16
+
17
+ When a checkpoint or standalone task does DB work on its own feat branch, that branch gets
18
+ a Supabase preview branch whose lifecycle mirrors the git branch — created on first DB
19
+ change, deleted when the git branch is removed.
20
+
21
+ ## Contract
22
+
23
+ Each feat branch that touches the database owns exactly one Supabase preview branch, named
24
+ identically to the git branch. The git branch is the source of truth; the Supabase branch
25
+ follows it.
26
+
27
+ ## Create (lazy)
28
+
29
+ The Supabase branch is created by `cbp-supabase-migrate` (Step 2.3) on the **first DB
30
+ change** for the branch — never eagerly at git-branch creation time. Creation is guarded
31
+ by the skill's db_paths detection so the step fires only when the invocation actually
32
+ touches the database.
33
+
34
+ The branch is named **verbatim** after the git branch, slashes included (e.g.
35
+ `feat/CHK-144-supabase-branch-lifecycle`). This identical naming is the linchpin: the
36
+ GitHub branching integration reconciles to the same branch on PR open rather than creating
37
+ a duplicate.
38
+
39
+ After creation, the branch name and resulting `project_ref` are recorded on the parent
40
+ checkpoint or standalone task context (via `mcp__codebyplan__update_checkpoint` /
41
+ `mcp__codebyplan__update_task`) and may also be noted in `.codebyplan/shipment.json` so
42
+ cleanup skills can discover and remove it without querying the API.
43
+
44
+ ## Delete
45
+
46
+ The Supabase branch is removed wherever the git branch is deleted:
47
+
48
+ | Skill | Trigger |
49
+ |---|---|
50
+ | `cbp-checkpoint-end` | stale-branch cleanup + current feat-branch delete on ship |
51
+ | `cbp-git-worktree-remove` | worktree teardown removes the coupled Supabase branch |
52
+ | `cbp-ship-main` | `branch_deleted` event after PR merge |
53
+
54
+ Deletion is **existence-checked and idempotent** — a not-found response is treated as
55
+ success. This tolerates the GitHub integration auto-deleting the preview branch on PR
56
+ close before the skill runs.
57
+
58
+ These skills use `delete_branch` (MCP tool), never `merge_branch`. Production migrations
59
+ reach main via the existing merge path (GitHub rebuild / `last_shipped_migration_version`);
60
+ there is no separate Supabase branch merge step.
61
+
62
+ ## Exclusions
63
+
64
+ This lifecycle applies **only to feat branches**. It MUST NOT fire for:
65
+
66
+ - The main/production branch (`branch_config.production`, default `"main"`)
67
+ - Any branch listed in `branch_config.protected[]`
68
+ - A standalone task running directly on the production branch
69
+ - The integration branch (`branch_config.integration`) when non-null — it gets a persistent branch via `cbp-supabase-setup`, not a lazy ephemeral one.
70
+
71
+ `cbp-supabase-migrate` Step 2.3 Guard 1 enforces this by reading `.codebyplan/git.json`
72
+ and skipping provisioning when the current branch matches `$PRODUCTION`, `$PROTECTED`, or
73
+ a non-null `$INTEGRATION`.
74
+
75
+ ## Guards
76
+
77
+ Skills that delete Supabase branches MUST:
78
+
79
+ 1. Verify the branch name matches the feat branch being removed (never delete by
80
+ `project_ref` alone).
81
+ 2. Never delete the parent/production project (`rrvtrumtkhrsbhcyrwvf`) itself.
82
+ 3. Never delete a persistent/long-lived branch that does not correspond to the git branch
83
+ being torn down.
84
+
85
+ ## PR Gate (retained)
86
+
87
+ `cbp-supabase-branch-check` runs as a required PR gate, independent of the create/delete
88
+ coupling. By-name resolution works whether the branch was created by `cbp-supabase-migrate`
89
+ or auto-created by the GitHub integration — both paths use the same branch name.
90
+
91
+ ## Skill Map
92
+
93
+ | Role | Skill |
94
+ |---|---|
95
+ | Create (lazy) | `cbp-supabase-migrate` (Step 2.3) |
96
+ | Delete | `cbp-checkpoint-end`, `cbp-git-worktree-remove`, `cbp-ship-main` |
97
+ | PR gate | `cbp-supabase-branch-check` |
98
+
99
+ Each skill in the Skill Map above carries an inline back-reference to this rule at its create or teardown step.
@@ -39,8 +39,7 @@
39
39
  "Bash(git push --force:*)",
40
40
  "Bash(git push -f:*)",
41
41
  "Bash(git checkout -- :*)",
42
- "Bash(git add .:*)",
43
- "Bash(git add -A:*)",
42
+ "Bash(git add:*)",
44
43
  "Bash(rm -rf:*)",
45
44
  "Bash(pnpm add:*)",
46
45
  "Bash(pnpm install:*)",
@@ -47,8 +47,7 @@ These values are part of the CBP baseline and should not be weakened:
47
47
  "Bash(git push --force:*)",
48
48
  "Bash(git push -f:*)",
49
49
  "Bash(git checkout -- :*)",
50
- "Bash(git add .:*)",
51
- "Bash(git add -A:*)",
50
+ "Bash(git add:*)",
52
51
  "Bash(rm -rf:*)",
53
52
  "Bash(pnpm add:*)",
54
53
  "Bash(pnpm install:*)",
@@ -95,6 +95,8 @@ git push -u origin "feat/CHK-{NNN}-{slug}"
95
95
 
96
96
  Slug: lowercase, dash-joined, punctuation dropped, ≤40 chars. Persist the branch via MCP `update_checkpoint(checkpoint_id, branch_name: "feat/CHK-{NNN}-{slug}")`. (The dedicated `/cbp-git-branch-feat-create` skill is the canonical config-driven helper if you prefer to delegate.)
97
97
 
98
+ **Note — Supabase preview branch**: no Supabase branch is created here. Creation is lazy — it happens on the first DB change when `/cbp-supabase-migrate` runs on this feat branch, which provisions a Supabase branch named identically to the git branch. See `cbp-supabase-migrate` Step 2.3 for the creation protocol.
99
+
98
100
  ### Step 10: Show Result + Auto-Trigger Plan
99
101
 
100
102
  ```
@@ -141,13 +141,25 @@ C) Keep all
141
141
 
142
142
  Only delete with explicit user confirmation. Delete both local and remote:
143
143
  ```bash
144
- git branch -d "$BRANCH" 2>/dev/null
145
- git push origin --delete "$BRANCH"
144
+ git branch -d "$BRANCH" && git push origin --delete "$BRANCH"
146
145
  ```
147
146
 
147
+ Only after both the local and remote git delete above succeed, run a conditional Supabase preview-branch teardown for that branch name (do not suppress the delete output — a failed local delete must stay visible):
148
+
149
+ > Lifecycle contract: see [[supabase-branch-lifecycle]].
150
+
151
+ - Call `mcp__supabase__list_branches` with `project_id: rrvtrumtkhrsbhcyrwvf`.
152
+ - Scan the returned list for an entry whose `name` exactly equals `$BRANCH`.
153
+ - If found: call `mcp__supabase__delete_branch` with its `branch_id`. Record the branch name in `SUPABASE_BRANCHES_DELETED[]`.
154
+ - If not found: no-op silently — the GitHub integration may have already removed it on PR close; not-found is success, NOT an error.
155
+ - If the `list_branches` call itself fails (network, auth, or a non-success response — distinct from a successful lookup that returns no match): emit a non-blocking warning that the Supabase preview branch for `$BRANCH` may still exist and should be verified in the dashboard. Do not treat an API failure as a not-found success.
156
+ - Never delete the parent project `rrvtrumtkhrsbhcyrwvf` itself or any persistent/production branch.
157
+
158
+ Accumulate all Supabase branch names removed across the loop in `SUPABASE_BRANCHES_DELETED`.
159
+
148
160
  ### Step 9: Current Feat Branch Cleanup
149
161
 
150
- Present comprehensive summary first (Step 10), then ask about current feat branch via AskUserQuestion:
162
+ Before asking about the current feat branch, present a partial summary of what has shipped so far — runtime surfaces (from `/cbp-ship` Step 7), stale branches cleaned (Step 8), and the `SUPABASE_BRANCHES_DELETED` accumulated so far — then ask about the current feat branch via AskUserQuestion (the complete shipment record is finalised in Step 10):
151
163
 
152
164
  ```
153
165
  The current feat branch [BRANCH] has been fully merged.
@@ -166,6 +178,14 @@ git branch -d "$FEAT_BRANCH"
166
178
  git push origin --delete "$FEAT_BRANCH"
167
179
  ```
168
180
 
181
+ After the feat branch git delete, run the same conditional Supabase teardown for `$FEAT_BRANCH`:
182
+
183
+ - Call `mcp__supabase__list_branches` with `project_id: rrvtrumtkhrsbhcyrwvf`.
184
+ - Scan for an entry whose `name` exactly equals `$FEAT_BRANCH`.
185
+ - If found: call `mcp__supabase__delete_branch` with its `branch_id`. Add `$FEAT_BRANCH` to `SUPABASE_BRANCHES_DELETED[]`.
186
+ - If not found: no-op silently — idempotent, not-found is success.
187
+ - If the `list_branches` call itself fails (network, auth, or a non-success response — distinct from a successful lookup that returns no match): emit a non-blocking warning that the Supabase preview branch for `$FEAT_BRANCH` may still exist and should be verified in the dashboard. Do not treat an API failure as a not-found success.
188
+
169
189
  ### Step 10: Save Shipment Results and Summary
170
190
 
171
191
  Update checkpoint context via MCP `update_checkpoint`. The `shipment` block contains both branch promotion AND runtime surface results (from `/cbp-ship` Step 7):
@@ -177,8 +197,9 @@ context.shipment: {
177
197
  feat_to_base: { pr_url, merged: true/false },
178
198
  surfaces: [...], // populated by /cbp-ship — per-surface deploy results
179
199
  skipped: [...], // populated by /cbp-ship — surfaces explicitly skipped
180
- stale_branches_cleaned: [list of deleted branches],
181
- feat_branch_deleted: true/false
200
+ stale_branches_cleaned: [list of deleted git branches],
201
+ feat_branch_deleted: true/false,
202
+ supabase_branches_deleted: [list of Supabase preview branch names removed in Steps 8–9]
182
203
  }
183
204
  ```
184
205
 
@@ -198,6 +219,7 @@ Present summary:
198
219
  ### Branch Operations
199
220
  - Stale branches deleted: [N] ([list])
200
221
  - Feat branch: [deleted/kept]
222
+ - Supabase preview branches deleted: [N] ([list from supabase_branches_deleted], or "none")
201
223
 
202
224
  ### Before/After Branch State
203
225
  - Before: [list of feat/* branches]
@@ -57,7 +57,7 @@ This mirrors the CHK-104 hard-lock model — never wrest a checkpoint from a liv
57
57
 
58
58
  If the checkpoint is already `active` AND `worktree_id` already equals `CALLER_WT` (the Step 3 no-op row), skip this step entirely and proceed to Step 5 — nothing to write.
59
59
 
60
- Otherwise set the checkpoint `active` via MCP `update_checkpoint(checkpoint_id, status: "active"`, plus `worktree_id: CALLER_WT` when claiming per Step 3, plus `caller_worktree_id: CALLER_WT` so the hard-lock pre-guard accepts the call (omit `caller_worktree_id` only when `CALLER_WT` is empty). If the checkpoint was already `active` but a claim is still needed, skip the status write and only write `worktree_id`.
60
+ Otherwise set the checkpoint `active` via MCP `update_checkpoint(checkpoint_id, status: "active"`, plus `worktree_id: CALLER_WT` when claiming per Step 3. The server resolves the caller's worktree identity from the JWT/ctx (CHK-140 TASK-3 — `caller_worktree_id` input field removed). If the checkpoint was already `active` but a claim is still needed, skip the status write and only write `worktree_id`.
61
61
 
62
62
  ### Step 5: Route
63
63
 
@@ -78,7 +78,7 @@ Show a one-line confirmation before routing:
78
78
  ## Integration
79
79
 
80
80
  - **Reads**: MCP `get_checkpoints`, `get_tasks`; `npx codebyplan resolve-worktree`
81
- - **Writes**: MCP `update_checkpoint` (status + worktree_id, with caller_worktree_id pre-guard)
81
+ - **Writes**: MCP `update_checkpoint` (status + worktree_id; server resolves caller worktree from JWT/ctx)
82
82
  - **Triggered by**: `/cbp-checkpoint-plan` (auto when claimed at create), `/cbp-todo` (planned-but-pending gate), or user directly
83
83
  - **Triggers**: `/cbp-task-start` (auto when claimed), or `/cbp-checkpoint-plan` (when the checkpoint is unplanned)
84
84
  - **Never**: plans or creates tasks — that is `/cbp-checkpoint-plan`
@@ -116,7 +116,12 @@ Only use `--force` if the user confirms.
116
116
 
117
117
  ### Step 9: Delete Branch (if requested)
118
118
 
119
- **Protected branch check:** If `$BRANCH_NAME` is `main`, `development`, or `preview` — refuse deletion and stop.
119
+ **Protected branch check:** Read the protected set from `.codebyplan/git.json`:
120
+ ```bash
121
+ PRODUCTION=$(jq -r '.branch_config.production // "main"' .codebyplan/git.json)
122
+ PROTECTED=$(jq -r '.branch_config.protected[]? // empty' .codebyplan/git.json)
123
+ ```
124
+ If `$BRANCH_NAME` equals `$PRODUCTION` or appears in `$PROTECTED` — refuse deletion and stop.
120
125
 
121
126
  **Checkpoint verification:** Before deleting a feat branch, verify that the associated checkpoint has completed via `/cbp-checkpoint-end`. If the checkpoint is still active, warn the user that unshipped work may be lost.
122
127
 
@@ -126,6 +131,17 @@ git branch -d "$BRANCH_NAME" && git push origin --delete "$BRANCH_NAME"
126
131
 
127
132
  Use `-d` (not `-D`) to prevent deleting unmerged work. If it fails because the branch is not fully merged, inform the user and ask if they want to force delete with `-D`.
128
133
 
134
+ After the git branch delete succeeds, run a conditional Supabase preview-branch teardown for `$BRANCH_NAME`:
135
+
136
+ > Lifecycle contract: see [[supabase-branch-lifecycle]].
137
+
138
+ - Call `mcp__supabase__list_branches` with `project_id: rrvtrumtkhrsbhcyrwvf`.
139
+ - Scan the returned list for an entry whose `name` exactly equals `$BRANCH_NAME`.
140
+ - If found: call `mcp__supabase__delete_branch` with its `branch_id`. Report "Supabase preview branch deleted: `$BRANCH_NAME`".
141
+ - If not found: no-op silently — the GitHub integration may have already removed it on PR close; not-found is success, NOT an error.
142
+ - If the `list_branches` call itself fails (network, auth, or a non-success response — distinct from a successful lookup that returns no match): emit a non-blocking warning that the Supabase preview branch for `$BRANCH_NAME` may still exist and should be verified in the dashboard. Do not treat an API failure as a not-found success.
143
+ - Never delete the parent project `rrvtrumtkhrsbhcyrwvf` itself or any persistent/production branch.
144
+
129
145
  ### Step 10: Show Result
130
146
 
131
147
  ```
@@ -12,7 +12,7 @@ Activate the session, open a fresh session log, and surface the previous log's p
12
12
 
13
13
  ## Instructions
14
14
 
15
- Run Steps 0 through 5.8 silently (no intermediate output) — except Step 1.4 may surface a one-line fast-forward note or warning, and Step 5.7 may surface an approval gate. (Step numbers are organizational labels; execution order is 0 → 1 → 1.4 → 2 → 3 → 4 → 4.5 → 5 → 5.7 → 5.8 → 6 → 7.) Produce ONE output block at Step 6, then auto-trigger or stop per Step 7.
15
+ Run Steps 0 through 5.8 silently (no intermediate output) — except Step 1.4 may surface a one-line fast-forward note or warning, Step 1.5 may surface a one-line infra-drift nudge, and Step 5.7 may surface an approval gate. (Step numbers are organizational labels; execution order is 0 → 1 → 1.4 → 1.5 → 2 → 3 → 4 → 4.5 → 5 → 5.7 → 5.8 → 6 → 7.) Produce ONE output block at Step 6, then auto-trigger or stop per Step 7.
16
16
 
17
17
  ### Step 0: MCP Health Check
18
18
 
@@ -49,7 +49,7 @@ RESOLVE_JSON=$(npx codebyplan resolve-worktree --json)
49
49
 
50
50
  Extract `worktree_id` and `error_kind` from the JSON output.
51
51
 
52
- - `error_kind` is `null` or `"tuple_miss"` → healthy. `WORKTREE_ID` = `worktree_id` (may be `null`: a legitimate main-repo or unregistered-worktree case — proceed normally; downstream hard-lock pre-guards may reject mutations on assigned rows).
52
+ - `error_kind` is `null` or `"tuple_miss"` → healthy. `WORKTREE_ID` = `worktree_id` (may be `null`: a legitimate main-repo or unregistered-worktree case — proceed normally; the server resolves worktree identity from the JWT/ctx, falling back to the repo main-worktree when no specific worktree is matched).
53
53
  - `error_kind` is `local_config_read_failed`, `local_config_write_failed`, `legacy_file_blocks_dir`, `api_failed`, `git_failed`, or `unhandled` → **broken local state**. Hold the `error_kind` for Step 6 to display as a distress warning. Session continues (non-blocking — unlike `/cbp-todo`, session-start does NOT hard-stop on a non-tuple-miss distress).
54
54
 
55
55
  Pass `WORKTREE_ID` to MCP tools that support it. Null `WORKTREE_ID` means the (device, path, branch) tuple is unregistered — note this for Step 6.
@@ -77,6 +77,31 @@ CURRENT="$(git rev-parse --abbrev-ref HEAD)"
77
77
 
78
78
  Never rebase, reset, force-push, or stash. A non-fast-forwardable home branch is a signal to reconcile manually, not to overwrite.
79
79
 
80
+ ### Step 1.5: Infra Drift Check
81
+
82
+ Surface — never block — when this worktree's CBP tooling has fallen behind. Runs after Step 1.4 and may add one line to the Step 6 output. Two mutually-exclusive concepts, keyed on repo type (`$PRODUCTION` is the branch resolved in Step 1.4):
83
+
84
+ - **Monorepo (concept A)** — both `packages/codebyplan-package/templates/` and `scripts/infra-drift.mjs` exist. Step 1.4 skips the fetch on a feat branch, so refresh `origin/$PRODUCTION` best-effort first, then run the reporter:
85
+
86
+ ```bash
87
+ git fetch origin "$PRODUCTION" 2>/dev/null || true
88
+ node scripts/infra-drift.mjs 2>/dev/null || true
89
+ ```
90
+
91
+ The script self-guards (feat branch + behind > 0) and emits at most one `⚠ .claude/ infra is N behind — run /cbp-refresh-infra` line. Hold any output for Step 6.
92
+
93
+ - **Consumer (concept B)** — no `templates/`, but an install manifest exists (`.claude/.cbp.manifest.json`, falling back to `.cbp-claude.manifest.json` then `.codebyplan-claude.manifest.json`). Compare its `version` to the latest published `codebyplan`, offline-safe:
94
+
95
+ ```bash
96
+ LATEST="$(npm view codebyplan version 2>/dev/null)"
97
+ ```
98
+
99
+ When `$LATEST` is non-empty and newer than the manifest `version`, hold one line for Step 6: `⚠ codebyplan {installed} → {LATEST} — run npx codebyplan@latest claude update`. Any failure (offline, npm absent) → silent.
100
+
101
+ - **Neither** → skip silently.
102
+
103
+ Concept B never fires in the monorepo — the `templates/` guard routes the source repo to concept A only (its manifest `version` is intentionally stale). Fully non-blocking; every failure path falls through with no output.
104
+
80
105
  ### Step 2: Check Dev Server
81
106
 
82
107
  **Skip if `server_type` is `"none"`.**
@@ -198,7 +223,7 @@ Three-branch gate using `owned_count` and `total_count` from Step 5.8:
198
223
 
199
224
  - **Triggered by**: user invocation, `/clear` recovery
200
225
  - **Resolves**: `npx codebyplan resolve-worktree --json` (worktree id + distress signal; non-tuple-miss distress is non-blocking at session-start)
201
- - **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for the Step 1.4 home-branch fast-forward), MCP `get_session_logs` (worktree-filtered, limit 1 — single call shared by Step 4 and Step 4.5), MCP `health_check`, MCP `get_current_task`, MCP `get_rounds`, MCP `get_checkpoints` (two calls: `{ repo_id, status: 'active' }` for the Step 5.8 ownership partition; `{ repo_id }` unfiltered for the Step 4.5 freshness probe, which may resolve a non-active checkpoint), MCP `get_tasks` / `get_rounds` for the Step 4.5 freshness probe
226
+ - **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for the Step 1.4 home-branch fast-forward), MCP `get_session_logs` (worktree-filtered, limit 1 — single call shared by Step 4 and Step 4.5), MCP `health_check`, MCP `get_current_task`, MCP `get_rounds`, MCP `get_checkpoints` (two calls: `{ repo_id, status: 'active' }` for the Step 5.8 ownership partition; `{ repo_id }` unfiltered for the Step 4.5 freshness probe, which may resolve a non-active checkpoint), MCP `get_tasks` / `get_rounds` for the Step 4.5 freshness probe; `scripts/infra-drift.mjs` + a best-effort `git fetch` (Step 1.5 monorepo drift) or the install manifest + `npm view codebyplan version` (Step 1.5 consumer drift)
202
227
  - **Writes**: MCP `create_session_log` (new, possibly empty), MCP `update_session_state` (activate)
203
228
  - **Spawns**: none
204
229
  - **Triggers**: `/cbp-git-commit` (conditional, on user approval), `handoff.command` (on fresh handoff hit at Step 4.5), `/cbp-todo` (auto fall-through when owned_count >= 1 or total_count === 0; STOPS with no trigger when total_count >= 1 AND owned_count === 0)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  scope: org-shared
3
- name: cbp-e2e-setup
3
+ name: cbp-setup-e2e
4
4
  description: Detect installed E2E frameworks, ask which to enable, record credentials source (gitignored env-file path + var names only, never secrets), and write/refresh .codebyplan/e2e.json. Interactive, idempotent.
5
5
  argument-hint: "[--force]"
6
6
  model: sonnet