codebyplan 1.11.2 → 1.12.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
@@ -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]
@@ -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
 
@@ -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)
@@ -52,6 +52,19 @@ Pass `--dry-run` through if the skill was invoked with a dry-run arg.
52
52
 
53
53
  Parse JSON from Step 3. Report `pr_url`, `merge_commit`, `branch_deleted`. If `checks_failed: true`, surface `checks_failure_reason` and stop.
54
54
 
55
+ If `branch_deleted === true`, run a conditional Supabase preview-branch teardown for the feat branch that was just merged:
56
+
57
+ > Lifecycle contract: see [[supabase-branch-lifecycle]].
58
+
59
+ - Read `FEAT_BRANCH` from the `feat_branch` field in the Step 3 JSON output — NOT from `git branch --show-current`. By the time Step 4 runs, `codebyplan ship` has already checked out the base branch (unless `--keep-feat` was passed), so the live branch is the base, not the feat branch.
60
+ - Call `mcp__supabase__list_branches` with `project_id: rrvtrumtkhrsbhcyrwvf`.
61
+ - Scan the returned list for an entry whose `name` exactly equals `$FEAT_BRANCH`.
62
+ - If found: call `mcp__supabase__delete_branch` with its `branch_id`. Report the Supabase delete outcome alongside `pr_url` / `merge_commit` / `branch_deleted`.
63
+ - If not found: no-op silently — the GitHub integration may have already removed the preview branch on PR close; not-found is success, NOT an error.
64
+ - 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.
65
+ - Never delete the parent project `rrvtrumtkhrsbhcyrwvf` itself or any persistent/production branch.
66
+ - This coordinates safely with `/cbp-checkpoint-end` — the existence-checked delete makes any second attempt a harmless no-op.
67
+
55
68
  ## Key Rules
56
69
 
57
70
  - **Read branch names from config** — never hardcode "main"
@@ -62,10 +62,10 @@ Infer `TARGET` when absent:
62
62
 
63
63
  ## Step 1 — Read DB Paths Config
64
64
 
65
- Read `.codebyplan.json` to obtain the configured DB path globs:
65
+ Read `.codebyplan/shipment.json` to obtain the configured DB path globs:
66
66
 
67
67
  ```bash
68
- DB_PATHS=$(jq -r '.shipment.surfaces.supabase.db_paths[]? // empty' .codebyplan.json 2>/dev/null)
68
+ DB_PATHS=$(jq -r '.shipment.surfaces.supabase.db_paths[]? // empty' .codebyplan/shipment.json 2>/dev/null)
69
69
  ```
70
70
 
71
71
  If `DB_PATHS` is empty, fall back to defaults:
@@ -80,11 +80,11 @@ Store each pattern as a separate entry for matching in Step 2.
80
80
 
81
81
  ## Step 2 — Detect DB-Path Changes
82
82
 
83
- Resolve the BASE branch from `TARGET`. Read `.codebyplan.json`:
83
+ Resolve the BASE branch from `TARGET`. Read `.codebyplan/git.json`:
84
84
 
85
85
  ```bash
86
- INTEGRATION=$(jq -r '.branch_config.integration // "development"' .codebyplan.json)
87
- PRODUCTION=$(jq -r '.branch_config.production // "main"' .codebyplan.json)
86
+ INTEGRATION=$(jq -r '.branch_config.integration // "development"' .codebyplan/git.json)
87
+ PRODUCTION=$(jq -r '.branch_config.production // "main"' .codebyplan/git.json)
88
88
  ```
89
89
 
90
90
  Set `BASE`:
@@ -156,6 +156,13 @@ Store as `PROJECT_REF`.
156
156
 
157
157
  ## Step 4 — Handle Missing project_ref
158
158
 
159
+ > **Note — Hybrid branch creation**: The preview branch may pre-exist as a CBP-created
160
+ > branch (named identically to the git branch, provisioned lazily by `cbp-supabase-migrate`
161
+ > on first DB change) rather than being auto-created by the GitHub integration on PR open.
162
+ > The by-name resolution in Step 3 works identically for both creation paths because both
163
+ > name the branch verbatim after the git branch. See [[supabase-branch-lifecycle]] for the
164
+ > full lifecycle contract.
165
+
159
166
  If `PROJECT_REF` is empty after Step 3:
160
167
 
161
168
  - `MODE=pre_pr_create`: