codebyplan 1.13.62 → 1.13.63

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 (31) hide show
  1. package/dist/cli.js +1108 -2061
  2. package/package.json +1 -1
  3. package/templates/agents/cbp-e2e-playwright.md +10 -10
  4. package/templates/hooks/cbp-mcp-round-sync.sh +4 -15
  5. package/templates/hooks/cbp-test-hooks.sh +0 -81
  6. package/templates/hooks/hooks.json +0 -9
  7. package/templates/rules/cbp-operating-gotchas.md +8 -10
  8. package/templates/rules/supabase-branch-lifecycle.md +2 -2
  9. package/templates/rules/todo-backend.md +11 -8
  10. package/templates/settings.project.base.json +0 -5
  11. package/templates/skills/cbp-build-cc-settings/reference/cbp-permission-policy.md +1 -1
  12. package/templates/skills/cbp-checkpoint-create/SKILL.md +12 -22
  13. package/templates/skills/cbp-checkpoint-end/SKILL.md +37 -0
  14. package/templates/skills/cbp-checkpoint-plan/SKILL.md +10 -8
  15. package/templates/skills/cbp-checkpoint-start/SKILL.md +27 -19
  16. package/templates/skills/cbp-checkpoint-update/SKILL.md +1 -1
  17. package/templates/skills/cbp-finalize/SKILL.md +2 -2
  18. package/templates/skills/cbp-round-complete/SKILL.md +3 -24
  19. package/templates/skills/cbp-round-plan/SKILL.md +1 -1
  20. package/templates/skills/cbp-session-end/SKILL.md +40 -30
  21. package/templates/skills/cbp-session-start/SKILL.md +7 -7
  22. package/templates/skills/cbp-session-start/qa-regression.md +32 -25
  23. package/templates/skills/cbp-standalone-task-complete/SKILL.md +2 -5
  24. package/templates/skills/cbp-standalone-task-create/SKILL.md +5 -13
  25. package/templates/skills/cbp-standalone-task-start/SKILL.md +10 -10
  26. package/templates/skills/cbp-task-create/SKILL.md +1 -1
  27. package/templates/skills/cbp-task-start/SKILL.md +10 -10
  28. package/templates/skills/cbp-todo/SKILL.md +23 -38
  29. package/templates/skills/cbp-todo/qa-regression.md +21 -27
  30. package/templates/skills/cbp-verify/reference/round-scope.md +1 -2
  31. package/templates/hooks/cbp-mcp-caller-worktree-inject.sh +0 -78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.13.62",
3
+ "version": "1.13.63",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,20 +31,20 @@ pnpm exec playwright install --with-deps chromium
31
31
 
32
32
  Resolve the apps/{app} dev-server port at config-read time via the shared resolver
33
33
  `apps/{app}/e2e/resolve-web-dev-port.ts` — imported by BOTH `playwright.config.ts` and
34
- `e2e/auth.setup.ts` (single source of truth). It reads the per-worktree
34
+ `e2e/auth.setup.ts` (single source of truth). It reads the per-checkout, branch-keyed
35
35
  `.codebyplan/server.local.json` overlay first, then the committed `.codebyplan/server.json`.
36
36
  Match by label rather than array position — a monorepo can have several Next.js allocations
37
37
  with similar label prefixes.
38
38
 
39
39
  **Label-matching rules** (`findWebDevPort`):
40
40
 
41
- - `server.local.json` overlay: each label has the worktree name appended as the last
42
- parenthetical group (e.g. `"Web Dev (<worktree-name>)"`). Strip exactly ONE trailing
41
+ - `server.local.json` overlay: each label has a branch-keyed suffix appended as the last
42
+ parenthetical group (e.g. `"Web Dev (<branch-keyed-suffix>)"`). Strip exactly ONE trailing
43
43
  `" (…)"` group, then require the result `=== "Web Dev"`.
44
- - `"Web Dev (<worktree-name>)"` → strip → `"Web Dev"` ✓
45
- - `"Web Dev (<other-worktree>) (<worktree-name>)"` → strip → `"Web Dev (<other-worktree>)"` ✗
44
+ - `"Web Dev (<branch-keyed-suffix>)"` → strip → `"Web Dev"` ✓
45
+ - `"Web Dev (<other-suffix>) (<branch-keyed-suffix>)"` → strip → `"Web Dev (<other-suffix>)"` ✗
46
46
  - `server.json` committed base: require `label === "Web Dev"` exactly (do NOT strip —
47
- `"Web Dev (<other-worktree>)"` must not match).
47
+ `"Web Dev (<other-suffix>)"` must not match).
48
48
 
49
49
  **Resolution order** (first hit wins):
50
50
 
@@ -108,7 +108,7 @@ import { resolveWebDevPort } from "./e2e/resolve-web-dev-port";
108
108
  // findWebDevPort, parsePortFromUrl, and resolveWebDevPort live in the shared
109
109
  // module ./e2e/resolve-web-dev-port.ts (imported above) — single source of
110
110
  // truth, also consumed by e2e/auth.setup.ts. Resolution order:
111
- // 0. PLAYWRIGHT_BASE_URL → 1. server.local.json → 2. server.json
111
+ // 0. PLAYWRIGHT_BASE_URL → 1. server.local.json (branch-keyed overlay) → 2. server.json
112
112
  // → 3. E2E_BASE_URL → 4. 3010.
113
113
  const port = resolveWebDevPort();
114
114
 
@@ -198,7 +198,7 @@ timing**: it loads creds from `.env.local` + `.codebyplan/e2e.env`, calls
198
198
  the project ref from `NEXT_PUBLIC_SUPABASE_URL`, and writes a `sb-<projectref>-auth-token`
199
199
  cookie (domain `localhost`) into `state.json` using the same `encodeAuthCookie` from
200
200
  `e2e/auth-cookie.ts` that global-setup consumes. This makes seeding deterministic in any
201
- worktree — run `pnpm e2e:auth-setup` (optionally `--port N`) when `state.json` is missing or
201
+ checkout — run `pnpm e2e:auth-setup` (optionally `--port N`) when `state.json` is missing or
202
202
  its refresh token has expired. Do NOT reintroduce a browser-login flow (the `(auth)/login`
203
203
  page is a client component whose `onSubmit` only attaches after hydration — clicking submit
204
204
  pre-hydration falls through to a native GET and never authenticates).
@@ -261,8 +261,8 @@ any already-running process, so the dev-server readiness probe is the active gua
261
261
  path.
262
262
 
263
263
  **Port alignment**: parse `playwright.config.ts` `baseURL` port; compare to the resolved
264
- port from `.codebyplan/server.local.json` (worktree overlay, checked first) then
265
- `.codebyplan/server.json` (committed base). On mismatch ask which is correct, then propose
264
+ port from `.codebyplan/server.local.json` (per-checkout branch-keyed overlay, checked first)
265
+ then `.codebyplan/server.json` (committed base). On mismatch ask which is correct, then propose
266
266
  an Edit to align them.
267
267
 
268
268
  ## Quality Fixture (MANDATORY)
@@ -20,13 +20,10 @@
20
20
  # - Web-UI flag (app_file_approval_by_user) consumption + reset
21
21
  # - Writes both PATCH /api/rounds/${ROUND_ID} and PATCH /api/tasks/${TASK_ID}
22
22
  #
23
- # Caller worktree identity:
24
- # The CLI auto-resolves caller_worktree_id (override flag cache
25
- # in-process tuple API) and hard-fails with exit 1 if it cannot resolve
26
- # on the write path. This hook resolves the worktree id before invoking the
27
- # CLI and passes it via --caller-worktree-id so the server can honor the
28
- # feat-worktree lock. The hook itself stays non-fatal (exits 0) and surfaces
29
- # the CLI's stderr output to the user.
23
+ # Caller identity:
24
+ # Worktree-based caller injection was retired in CHK-225. The hook no longer
25
+ # resolves or threads any worktree id; user identity travels via the MCP JWT.
26
+ # The hook stays fail-open and exits 0.
30
27
  #
31
28
  # Flags:
32
29
  # --dry-run Pass through to CLI (prints merged payload, no API writes).
@@ -83,16 +80,8 @@ if [ -z "$TASK_ID" ]; then
83
80
  exit 0
84
81
  fi
85
82
 
86
- # Resolve worktree id before invoking the CLI so the server can honor the
87
- # feat-worktree lock. On miss (unregistered worktree) the CLI falls back to
88
- # its in-process resolve and hard-fails with guidance if still unresolved.
89
- WORKTREE_ID=$(npx codebyplan resolve-worktree 2>/dev/null)
90
-
91
83
  # Delegate to the codebyplan CLI (single source of truth for merge semantics)
92
84
  CMD_ARGS=("round" "sync-approvals" "--round-id" "$ROUND_ID" "--task-id" "$TASK_ID")
93
- if [ -n "$WORKTREE_ID" ]; then
94
- CMD_ARGS+=("--caller-worktree-id" "$WORKTREE_ID")
95
- fi
96
85
  [ "$DRY_RUN" = "true" ] && CMD_ARGS+=("--dry-run")
97
86
 
98
87
  if npx codebyplan "${CMD_ARGS[@]}"; then
@@ -374,87 +374,6 @@ fi
374
374
 
375
375
  echo ""
376
376
 
377
- # ===== HOOK SMOKE TESTS — cbp-mcp-caller-worktree-inject =====
378
- echo "## Hook Smoke Tests — cbp-mcp-caller-worktree-inject (CHK-198)"
379
-
380
- INJECT_HOOK="$HOOKS_DIR/cbp-mcp-caller-worktree-inject.sh"
381
- # Absolute path — the fail-open test runs the hook from a temp cwd (to isolate it
382
- # from this repo's git context), where the relative "$HOOKS_DIR" no longer resolves.
383
- INJECT_HOOK_ABS="$(cd "$HOOKS_DIR" 2>/dev/null && pwd)/cbp-mcp-caller-worktree-inject.sh"
384
-
385
- if [ ! -f "$INJECT_HOOK" ]; then
386
- test_result "cbp-mcp-caller-worktree-inject.sh present" "passed" "missing"
387
- else
388
- test_result "cbp-mcp-caller-worktree-inject.sh present" "passed" "passed"
389
-
390
- FIRST_LINE=$(head -1 "$INJECT_HOOK")
391
- if echo "$FIRST_LINE" | grep -q '^#!/'; then
392
- test_result "cbp-mcp-caller-worktree-inject.sh has shebang" "passed" "passed"
393
- else
394
- test_result "cbp-mcp-caller-worktree-inject.sh has shebang" "passed" "missing"
395
- fi
396
-
397
- if grep -q '@scope: org-shared' "$INJECT_HOOK"; then
398
- test_result "cbp-mcp-caller-worktree-inject.sh has @scope: org-shared" "passed" "passed"
399
- else
400
- test_result "cbp-mcp-caller-worktree-inject.sh has @scope: org-shared" "passed" "missing"
401
- fi
402
-
403
- # Fail-open: run from a non-repo temp dir with no worktree cache and no
404
- # CLAUDE_PROJECT_DIR — neither the cache nor the CLI fallback can resolve a
405
- # worktree, so the hook must exit 0 with empty stdout (no updatedInput).
406
- ISO=$(mktemp -d)
407
- OUTPUT=$( (cd "$ISO" && env -u CLAUDE_PROJECT_DIR bash "$INJECT_HOOK_ABS" <<< '{"tool_input":{"task_id":"x"}}') 2>/dev/null )
408
- EXIT_CODE=$?
409
- if [ "$EXIT_CODE" = "0" ] && [ -z "$OUTPUT" ]; then
410
- test_result "cbp-mcp-caller-worktree-inject.sh fail-open (unresolvable) exits 0 + empty stdout" "passed" "passed"
411
- else
412
- test_result "cbp-mcp-caller-worktree-inject.sh fail-open (unresolvable) exits 0 + empty stdout" "passed" "failed (exit=$EXIT_CODE)"
413
- fi
414
- rm -rf "$ISO"
415
-
416
- # C6 — input already carries a non-empty caller_worktree_id → never overwrite;
417
- # early-return with exit 0 and empty stdout (no resolution attempted).
418
- OUTPUT=$(echo '{"tool_input":{"caller_worktree_id":"11111111-1111-1111-1111-111111111111"}}' | bash "$INJECT_HOOK" 2>/dev/null)
419
- EXIT_CODE=$?
420
- if [ "$EXIT_CODE" = "0" ] && [ -z "$OUTPUT" ]; then
421
- test_result "cbp-mcp-caller-worktree-inject.sh C6 keeps existing caller_worktree_id (exit 0 + empty stdout)" "passed" "passed"
422
- else
423
- test_result "cbp-mcp-caller-worktree-inject.sh C6 keeps existing caller_worktree_id (exit 0 + empty stdout)" "passed" "failed (exit=$EXIT_CODE)"
424
- fi
425
-
426
- # Injection — a worktree.local.json whose .branch matches the current git branch
427
- # makes the cache fast-path resolve. Use a synthetic UUID so the assertion proves
428
- # the cache value (not the live CLI) was injected. Skipped when no concrete git
429
- # branch resolves (detached HEAD / non-git checkout) or jq is unavailable.
430
- CUR_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
431
- if [ -n "$CUR_BRANCH" ] && [ "$CUR_BRANCH" != "HEAD" ] && command -v jq >/dev/null 2>&1; then
432
- ISO=$(mktemp -d)
433
- mkdir -p "$ISO/.codebyplan"
434
- FAKE_WT="abcdef01-2345-6789-abcd-ef0123456789"
435
- jq -n --arg b "$CUR_BRANCH" --arg w "$FAKE_WT" \
436
- '{worktree_id:$w, branch:$b}' > "$ISO/.codebyplan/worktree.local.json"
437
- OUTPUT=$(CLAUDE_PROJECT_DIR="$ISO" bash "$INJECT_HOOK" <<< '{"tool_input":{"task_id":"x"}}' 2>/dev/null)
438
- EXIT_CODE=$?
439
- INJECTED=$(echo "$OUTPUT" | jq -r '.hookSpecificOutput.updatedInput.caller_worktree_id // empty' 2>/dev/null)
440
- # Sibling-key survival — CC's updatedInput REPLACES tool_input wholesale (it is
441
- # not a partial merge), so the hook must echo back every original field merged
442
- # with caller_worktree_id. Assert the non-target sibling key (task_id) survives;
443
- # this is the assertion gap that let the replace-vs-merge bug ship in round 2.
444
- PRESERVED=$(echo "$OUTPUT" | jq -r '.hookSpecificOutput.updatedInput.task_id // empty' 2>/dev/null)
445
- if [ "$EXIT_CODE" = "0" ] && [ "$INJECTED" = "$FAKE_WT" ] && [ "$PRESERVED" = "x" ]; then
446
- test_result "cbp-mcp-caller-worktree-inject.sh injects caller_worktree_id AND preserves sibling keys" "passed" "passed"
447
- else
448
- test_result "cbp-mcp-caller-worktree-inject.sh injects caller_worktree_id AND preserves sibling keys" "passed" "failed (exit=$EXIT_CODE injected=$INJECTED preserved=$PRESERVED)"
449
- fi
450
- rm -rf "$ISO"
451
- else
452
- test_result "cbp-mcp-caller-worktree-inject.sh injection test (no branch resolvable — skipped)" "passed" "passed"
453
- fi
454
- fi
455
-
456
- echo ""
457
-
458
377
  # ===== HOOK SMOKE TESTS — cbp-session-start-hook =====
459
378
  echo "## Hook Smoke Tests — cbp-session-start-hook (CHK-178)"
460
379
 
@@ -69,15 +69,6 @@
69
69
  "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/cbp-mcp-migration-guard.sh"
70
70
  }
71
71
  ]
72
- },
73
- {
74
- "matcher": "mcp__codebyplan__(update_checkpoint|complete_checkpoint|update_task|complete_task|add_round|update_round|complete_round|create_standalone_task|update_standalone_task|complete_standalone_task|add_standalone_round|update_standalone_round|complete_standalone_round|update_standalone_file_change)",
75
- "hooks": [
76
- {
77
- "type": "command",
78
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/cbp-mcp-caller-worktree-inject.sh"
79
- }
80
- ]
81
72
  }
82
73
  ],
83
74
  "PostToolUse": [
@@ -25,11 +25,13 @@ SHARED tooling behavior only — repo-specific gotchas belong in that repo's own
25
25
  clobbers existing `decisions` / `discoveries` / `check_results`. Always read the current row,
26
26
  merge your change into the full object/array, then write the whole thing back.
27
27
 
28
- - **`resolve-worktree` empty output = a NULL `(device, path, branch)` tuple, not a broken
29
- resolver.** When identity is unresolved the server can collapse the caller to the repo's main
30
- worktree, so feat-locked writes get rejected. Pass `caller_worktree_id` on every MCP mutation,
31
- and confirm ownership by matching the row's repo path + branch to the current directory before
32
- mutating.
28
+ - **User-level locks are invisible until a mutation they block.** `get_checkpoints` /
29
+ `get_tasks` succeed even when another user holds the assignment; the 403 fires only on
30
+ `update_*` / `complete_*`. The lock keys on the JWT user (`ctx.userId`) vs the row's
31
+ `assigned_user_id` (null = open). `caller_worktree_id` / `worktree_id` params are
32
+ accepted-and-ignored — do not thread them. Verify `assigned_user_id` matches
33
+ `npx codebyplan whoami` before mutating; recover a stale assignment with
34
+ `release_assignment` (maintainer).
33
35
 
34
36
  - **Full-repo lint/type baselines are often pre-existing red.** A round must gate on the files
35
37
  it changed, not the whole-repo baseline — scope lint/tsc checks to the round's changed set so a
@@ -40,14 +42,10 @@ SHARED tooling behavior only — repo-specific gotchas belong in that repo's own
40
42
  `update_task` alone — updating only the task leaves the round entries unapproved and
41
43
  `complete_task` rejects with "files are not approved".
42
44
 
43
- - **CLI transport uses REST (reads) and OAuth+MCP (writes) — a 502 from `codebyplan round sync-approvals` is transient MCP churn, not an outage.** The CLI exits 0 with a warning and MCP tools still work. A missing `CODEBYPLAN_API_KEY` surfaces as an `ApiError`, not a 502. `sync-approvals` can also drag untracked per-device dirs into `files_changed` — run it from the repo root or pass `--caller-worktree-id`.
45
+ - **CLI transport uses REST (reads) and OAuth+MCP (writes) — a 502 from `codebyplan round sync-approvals` is transient MCP churn, not an outage.** The CLI exits 0 with a warning and MCP tools still work. A missing `CODEBYPLAN_API_KEY` surfaces as an `ApiError`, not a 502. `sync-approvals` can also drag untracked per-device dirs into `files_changed` — run it from the repo root.
44
46
 
45
47
  - **`codebyplan claude update` requires a TTY.** On non-TTY stdin (CI, piped) it half-applies then errors. Re-run with `--yes` to accept defaults non-interactively.
46
48
 
47
- - **Checkpoint locks are invisible until a mutation they block.** `get_checkpoints` / `get_tasks` succeed even when another worktree holds the lock; the 403 fires only on `update_*` / `complete_*`. Verify the row's `worktree_id` matches the caller before mutating. A null-`worktree_id` checkpoint can still be actively shipped by whichever worktree physically holds its feat branch — check `git worktree list` first.
48
-
49
- - **`update_task` accepts `caller_worktree_id` for lock-verify only — it does NOT assign ownership.** Ownership assignment goes through the web UI or the dedicated assignment path. Don't conflate `caller_worktree_id` with `assigned_worktree_id`.
50
-
51
49
  - **Re-run config-driven gates after merging main into a feat branch.** A merge can add or change `.codebyplan/shipment.json`, ports, branch config, `e2e.json`, and `eslint.json` — treat the post-merge state as a fresh baseline before continuing.
52
50
 
53
51
  ## Behavioral Preferences
@@ -47,7 +47,7 @@ The Supabase branch is removed wherever the git branch is deleted:
47
47
  | Skill | Trigger |
48
48
  |---|---|
49
49
  | `cbp-checkpoint-end` | stale-branch cleanup + current feat-branch delete on ship |
50
- | `codebyplan worktree remove CHK-NNN` | worktree teardown removes the coupled Supabase branch |
50
+ | `codebyplan worktree remove <path>` | git-worktree teardown removes the coupled Supabase branch (branch-keyed, not identity-keyed) |
51
51
  | `cbp-ship-main` | `branch_deleted` event after PR merge |
52
52
  | `cbp-standalone-task-complete` | `branch_deleted` event after standalone PR merge (Step 7.3) |
53
53
 
@@ -93,7 +93,7 @@ or auto-created by the GitHub integration — both paths use the same branch nam
93
93
  | Role | Skill |
94
94
  |---|---|
95
95
  | Create (lazy) | `cbp-supabase-migrate` (Step 2.3) |
96
- | Delete | `cbp-checkpoint-end`, `cbp-standalone-task-complete`, `codebyplan worktree remove`, `cbp-ship-main` |
96
+ | Delete | `cbp-checkpoint-end`, `cbp-standalone-task-complete`, `codebyplan worktree remove <path>`, `cbp-ship-main` |
97
97
  | PR gate | `cbp-supabase-branch-check` |
98
98
 
99
99
  Each skill in the Skill Map above carries an inline back-reference to this rule at its create or teardown step.
@@ -12,16 +12,16 @@ The todos queue is materialised by `apps/todo-worker` (CHK-122) and consumed by
12
12
 
13
13
  ## 1. Six workflow invariants — DB-layer guards, never bypassable
14
14
 
15
- Defined in `supabase/migrations/20260511211900_chk111_workflow_invariants.sql`. These are `BEFORE UPDATE` triggers — they refuse invalid state transitions and produce structured `RAISE EXCEPTION` errors with `HINT` pointing at the offending row. **Do NOT port these to TS.** The DB layer is the bypass-proof contract.
15
+ Defined in `supabase/migrations/20260511211900_chk111_workflow_invariants.sql` (updated by `supabase/migrations/20260612000000_chk225_task1_user_locks.sql`). These are `BEFORE UPDATE` triggers — they refuse invalid state transitions and produce structured `RAISE EXCEPTION` errors with `HINT` pointing at the offending row. **Do NOT port these to TS.** The DB layer is the bypass-proof contract.
16
16
 
17
17
  | # | Trigger | What it enforces |
18
18
  |---|---------|------------------|
19
- | 1 | `trg_enforce_checkpoint_activation_worktree` | A checkpoint cannot be activated without `worktree_id` set |
20
- | 2 | `enforce_standalone_task_worktree` (via task workflow) | A standalone task cannot be moved to `in_progress` without `assigned_worktree_id` |
19
+ | 1 | `trg_enforce_checkpoint_activation_worktree` | A checkpoint cannot be activated without `assigned_user_id` set (CHK-225: was `worktree_id`) |
20
+ | 2 | `trg_enforce_standalone_task_workflow_invariants` | A standalone task cannot be moved to `in_progress` without `assigned_user_id` (CHK-225: was `assigned_worktree_id`) |
21
21
  | 3 | `trg_enforce_task_workflow_invariants` | ≤ 1 `in_progress` task per checkpoint |
22
22
  | 4 | `trg_enforce_single_in_progress_round_per_task` | ≤ 1 `in_progress` round per task |
23
- | 5 | `trg_enforce_single_active_scope_per_worktree` | ≤ 1 active (checkpoint OR standalone task) per worktree |
24
- | 6 | `trg_enforce_standalone_task_scope_per_worktree` | ≤ 1 `in_progress` standalone task per worktree |
23
+ | 5 | `trg_enforce_single_active_scope_per_worktree` | ≤ 1 active (checkpoint OR standalone task) per `assigned_user_id` (CHK-225: was per `worktree_id`) |
24
+ | 6 | `trg_enforce_standalone_task_scope_per_worktree` | ≤ 1 `in_progress` standalone task per `assigned_user_id` (CHK-225: was per `assigned_worktree_id`) |
25
25
 
26
26
  The worker is a passive cross-checker (`apps/todo-worker/src/invariants/check.ts`) — if its check disagrees with the DB, the DB wins.
27
27
 
@@ -34,7 +34,7 @@ MCP write → enqueueTodosJob → todos_jobs (status='pending')
34
34
 
35
35
  worker claim_todos_job (SELECT … FOR UPDATE SKIP LOCKED)
36
36
 
37
- computeTodos(repo, worktree, user) → desired rows
37
+ computeTodos(repo, user) → desired rows
38
38
 
39
39
  apply_todos RPC → todos table (status='current' / 'pending')
40
40
 
@@ -71,7 +71,7 @@ The queue head (`get_todos` `rows[0]`) maps to one of these slash commands. The
71
71
 
72
72
  ## 5. Heartbeat policy
73
73
 
74
- The worker's `node-cron` heartbeat runs at `0 0 * * *` (UTC midnight). It enumerates every `(repo, worktree, user)` tuple with an active checkpoint OR in-progress standalone task and enqueues a `HEARTBEAT_SWEEP` todos_jobs row for each. This catches drift from missed `enqueueTodosJob` calls in MCP writers.
74
+ The worker's `node-cron` heartbeat runs at `0 0 * * *` (UTC midnight). It enumerates every `(repo, user)` pair with an active checkpoint OR in-progress standalone task (via `assigned_user_id`) and enqueues a `HEARTBEAT_SWEEP` todos_jobs row for each. This catches drift from missed `enqueueTodosJob` calls in MCP writers.
75
75
 
76
76
  Backoff: a failed job retries at `now + 2^attempts minutes` (cap 60min). After 3 attempts, the job stays `failed` and the heartbeat picks it up again at the next sweep.
77
77
 
@@ -83,7 +83,6 @@ The shared enqueue helper lives at `packages/mcp-tools/src/tools/enqueue-todos.t
83
83
  enqueueTodosJob(
84
84
  client: SupabaseClient,
85
85
  repoId: string,
86
- callerWorktreeId: string | undefined,
87
86
  userId: string | null,
88
87
  reason: string
89
88
  ): Promise<void>
@@ -108,6 +107,8 @@ Every workflow mutator MUST call `void enqueueTodosJob(...)` after the mutation
108
107
 
109
108
  CHK-111 shipped the original todos queue as Postgres triggers + a 583-LOC `regenerate_todos_for_repo` PL/pgSQL function. CHK-122 ported the regen to `apps/todo-worker` (Node) for shared infrastructure with `apps/docs-ingest` (CHK-116), easier testing, and per-user fanout. The 10 `trg_*_todos` triggers and the 4 `wrap_*` wrappers were dropped in migration `20260521000000_chk122_drop_legacy_todos_regen.sql`. The 6 BEFORE-UPDATE invariant triggers stayed.
110
109
 
110
+ CHK-225 updated the invariant triggers from worktree-scoped to user-scoped (`assigned_user_id`). The trigger names were preserved for continuity; only the function bodies changed. Migration: `20260612000000_chk225_task1_user_locks.sql`.
111
+
111
112
  ## 8. Deployment — Railway
112
113
 
113
114
  `apps/todo-worker` runs as a Railway service alongside `apps/backend`. Setup:
@@ -119,3 +120,5 @@ CHK-111 shipped the original todos queue as Postgres triggers + a 583-LOC `regen
119
120
  5. Save the resulting `project_ref` to `.codebyplan.json` `shipment.surfaces.railway-todo-worker.project_ref`.
120
121
 
121
122
  Smoke after deploy: run `/cbp-finalize` in any worktree → tail Railway logs → expect a `claim → apply` cycle within `WORKER_POLL_MS`.
123
+
124
+ **CHK-225 deploy note**: apply migration `20260612100000` (drops `worktree_id` from `todos` + `todos_jobs`, dedup todos rows, updates `apply_todos` RPC) BEFORE running `railway up` for the worker. Until the migration lands, the worker's 3-arg `apply_todos` call fails with a function-not-found error.
@@ -66,7 +66,6 @@
66
66
  "mcp__codebyplan__create_project",
67
67
  "mcp__codebyplan__create_repo",
68
68
  "mcp__codebyplan__delete_session_log",
69
- "mcp__codebyplan__delete_worktree",
70
69
  "mcp__codebyplan__release_assignment",
71
70
  "mcp__stripe__create_customer",
72
71
  "mcp__stripe__create_product",
@@ -161,7 +160,6 @@
161
160
  "mcp__codebyplan__get_task_templates",
162
161
  "mcp__codebyplan__get_tasks",
163
162
  "mcp__codebyplan__get_todos",
164
- "mcp__codebyplan__get_worktrees",
165
163
  "mcp__codebyplan__list_tech_stack_sync_sessions",
166
164
  "mcp__codebyplan__get_chunk",
167
165
  "mcp__codebyplan__get_library_toc",
@@ -183,7 +181,6 @@
183
181
  "mcp__codebyplan__create_session_log",
184
182
  "mcp__codebyplan__update_session_log",
185
183
  "mcp__codebyplan__update_session_state",
186
- "mcp__codebyplan__create_worktree",
187
184
  "mcp__codebyplan__flag_stale_chunk",
188
185
  "mcp__codebyplan__update_eslint_repo_config",
189
186
  "mcp__codebyplan__update_server_config",
@@ -196,8 +193,6 @@
196
193
  "Bash(npx codebyplan supabase:*)",
197
194
  "Bash(codebyplan whoami:*)",
198
195
  "Bash(npx codebyplan whoami:*)",
199
- "Bash(codebyplan resolve-worktree:*)",
200
- "Bash(npx codebyplan resolve-worktree:*)",
201
196
  "Bash(codebyplan version-status:*)",
202
197
  "Bash(npx codebyplan version-status:*)",
203
198
  "Bash(codebyplan worktree:*)",
@@ -25,7 +25,7 @@ Precedence is `deny > ask > allow`; arrays union across scopes (managed/user/pro
25
25
  - **Non-lifecycle, non-shipment `/cbp-*` skills** — authoring (`cbp-build-cc-*`), frontend (`cbp-frontend-*`), git (`cbp-git-*`, `cbp-merge-main`, `cbp-refresh-infra`), round work (`cbp-round-plan`, `cbp-verify` — `cbp-verify` is the autonomous verify stage that runs deterministic gates, proves execution, spawns the fresh-context reviewer, and routes to `cbp-round-complete` or `cbp-round-plan`, so it runs without a prompt), setup/configure (`cbp-setup-*`, `cbp-ship-configure`, `cbp-supabase-*`), task prep (`cbp-task-create`/`-start`, `cbp-standalone-task-check`/`-testing`), planning (`cbp-checkpoint-plan`/`-update`), plus `cbp-session-start` and `cbp-todo`. Invoking a skill is the intended mode of operation; the gated side effects happen inside via the Bash/MCP tools the skill calls, which carry their own tiering. The lifecycle/state-transition and plan-approval skills are the exception — they live in `ask` (next section).
26
26
  - **All `mcp__codebyplan__*` reads** (`get_*`, `list_*`, `search_*`, `health_check`, `lookup_symbol`, `resolve_library_id`, `get_chunk`).
27
27
  - **Routine workflow-write MCP tools** the pipeline calls many times per task: create/update/complete checkpoint, task, and round; session log + session-state writes; `create_worktree`, `add_library`, `flag_stale_chunk`, `update_server_config`, `update_eslint_repo_config`, `update_task_template`. Gating these with `ask` would make the autonomous workflow unusable.
28
- - **Read/safe CLI commands** (both `codebyplan X` and `npx codebyplan X`): `whoami`, `resolve-worktree`, `statusline`, `ports`, `tech-stack`, `eslint`, `round`, `help`, `--version`.
28
+ - **Read/safe CLI commands** (both `codebyplan X` and `npx codebyplan X`): `whoami`, `statusline`, `ports`, `tech-stack`, `eslint`, `round`, `help`, `--version`.
29
29
 
30
30
  ### `ask` — the deliberate confirm-gate
31
31
 
@@ -60,35 +60,25 @@ Skip when the description has zero domain matches OR the user already named a ta
60
60
 
61
61
  Lightweight inference from the description — no deep analysis. **Title**: concise, ≤80 chars. **Goal**: ≤300 chars, a faithful restatement of intent (not a plan).
62
62
 
63
- ### Step 6: Claim-or-Open Prompt
64
-
65
- Ask the user via AskUserQuestion whether to claim this checkpoint now:
66
-
67
- - **Claim for me + this worktree** (default) — resolve `npx codebyplan resolve-worktree 2>/dev/null` and set it as the checkpoint `worktree_id` at create. The creator carries momentum straight through plan → start.
68
- - **Leave it open** — create with `worktree_id` null so anyone free can claim it later via `/cbp-checkpoint-start`.
69
-
70
- Record the choice; it drives both the create call (Step 7) and the plan→start routing in `/cbp-checkpoint-plan`.
71
-
72
- ### Step 7: Create Checkpoint Row
63
+ ### Step 6: Create Checkpoint Row
73
64
 
74
65
  `codebyplan checkpoint create` (CLI write-through: writes `.codebyplan/state/checkpoints/<id>.json` + REST). Pass flags:
75
66
  - `--repo-id` (from `.codebyplan/repo.json`), `--title`, `--goal`, `--deadline`, `--status pending`
76
67
  - `--ideas` JSON `[{ description: <raw user text> }]`
77
- - `--worktree-id` the resolved worktree **only if the user chose "claim"**; omit when "leave open"
78
68
 
79
- Do **not** pass `--number` — the database auto-assigns the next per-repo checkpoint number via a `BEFORE INSERT` trigger (advisory-locked `MAX(number)+1` scoped to `repo_id`). The DB-assigned number comes back on the created row (and is written into `.codebyplan/state/checkpoints/<id>.json`); read it for the branch slug (Step 8) and the result display (Step 9).
69
+ Do **not** pass `--number` — the database auto-assigns the next per-repo checkpoint number via a `BEFORE INSERT` trigger (advisory-locked `MAX(number)+1` scoped to `repo_id`). The DB-assigned number comes back on the created row (and is written into `.codebyplan/state/checkpoints/<id>.json`); read it for the branch slug (Step 7) and the result display (Step 8).
80
70
 
81
71
  Break-glass fallback: MCP `create_checkpoint` (also omit `number`) when the CLI is unavailable.
82
72
 
83
- This is the first identity-stamping point when claiming, passing `worktree_id` here engages the CHK-104 hard-lock from birth. No `context`, `research`, `plan`, or tasks are written here.
73
+ Assignment (`assigned_user_id`) is stamped server-side from the caller's JWT when the checkpoint is activated via `/cbp-checkpoint-start` it is not set at create time. No `context`, `research`, `plan`, or tasks are written here.
84
74
 
85
- ### Step 8: Create + Switch to Feat Branch
75
+ ### Step 7: Create + Switch to Feat Branch
86
76
 
87
- `{NNN}` below is the DB-assigned checkpoint number read back from the Step 7 `codebyplan checkpoint create` response.
77
+ `{NNN}` below is the DB-assigned checkpoint number read back from the Step 6 `codebyplan checkpoint create` response.
88
78
 
89
79
  Read `.codebyplan/git.json` `branch_config.production` (default `"main"`) as `BASE`. codebyplan repos are main-only — never create or branch from a `development`/integration branch.
90
80
 
91
- **8.1 — Reuse the cloud-created branch when present.** When the repo is GitHub-connected, the CHK-207 `create-feat-branch` Edge Function fires on the Step 7 row INSERT, creates `feat/CHK-{NNN}-<slug>` on origin, and writes `branch_name` back to the checkpoint row. Creating a second, differently-slugged branch here orphans the cloud one — so re-read the row first:
81
+ **7.1 — Reuse the cloud-created branch when present.** When the repo is GitHub-connected, the CHK-207 `create-feat-branch` Edge Function fires on the Step 6 row INSERT, creates `feat/CHK-{NNN}-<slug>` on origin, and writes `branch_name` back to the checkpoint row. Creating a second, differently-slugged branch here orphans the cloud one — so re-read the row first:
92
82
 
93
83
  ```bash
94
84
  sleep 5 # give the INSERT webhook a moment to write branch_name back
@@ -96,14 +86,14 @@ npx codebyplan sync 2>/dev/null || true
96
86
  BRANCH=$(jq -r '.branch_name // empty' ".codebyplan/state/checkpoints/{checkpoint-id}.json" 2>/dev/null)
97
87
  ```
98
88
 
99
- (Break-glass: MCP `get_checkpoints` and read the row's `branch_name`.) If `BRANCH` is non-empty, check out the existing remote branch and skip 8.2 entirely — do NOT push (it already exists on origin) and do NOT persist `--branch-name` (the Edge Function already recorded it):
89
+ (Break-glass: MCP `get_checkpoints` and read the row's `branch_name`.) If `BRANCH` is non-empty, check out the existing remote branch and skip 7.2 entirely — do NOT push (it already exists on origin) and do NOT persist `--branch-name` (the Edge Function already recorded it):
100
90
 
101
91
  ```bash
102
92
  git fetch origin "$BRANCH"
103
93
  git checkout -b "$BRANCH" --track "origin/$BRANCH"
104
94
  ```
105
95
 
106
- **8.2 — Fallback: create the branch locally.** Only when `BRANCH` is empty (repo not GitHub-connected, or the webhook hasn't landed). Compute the slug deterministically:
96
+ **7.2 — Fallback: create the branch locally.** Only when `BRANCH` is empty (repo not GitHub-connected, or the webhook hasn't landed). Compute the slug deterministically:
107
97
 
108
98
  ```bash
109
99
  SLUG=$(codebyplan slug "{checkpoint title}")
@@ -122,13 +112,13 @@ Persist the branch via `codebyplan checkpoint update --id <checkpoint-id> --bran
122
112
 
123
113
  **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.
124
114
 
125
- ### Step 9: Show Result + Auto-Trigger Plan
115
+ ### Step 8: Show Result + Auto-Trigger Plan
126
116
 
127
117
  ```
128
118
  ## Checkpoint Created
129
119
 
130
120
  **CHK-NNN**: [title] • **Deadline**: [date] • **Branch**: feat/CHK-NNN-slug
131
- **Claim**: [claimed by this worktree / left open]
121
+ **Assignment**: stamped on activation via /cbp-checkpoint-start
132
122
  **Worktree**: `npx codebyplan worktree add CHK-{NNN}`
133
123
 
134
124
  Now planning CHK-NNN… handing off to /cbp-checkpoint-plan.
@@ -139,6 +129,6 @@ Auto-trigger `/cbp-checkpoint-plan {NNN}` in the same context. This skill create
139
129
  ## Integration
140
130
 
141
131
  - **Runs inline**: mechanical setup only — no assessment, research, Q&A, plan, or tasks
142
- - **Reads**: `.codebyplan/state/checkpoints/*.json` (local-first; `npx codebyplan sync` if stale; MCP `get_checkpoints` break-glass); `.codebyplan/repo.json`, `.codebyplan/git.json`; `npx codebyplan resolve-worktree`
143
- - **Writes**: `codebyplan checkpoint create` (description-only ideas + deadline + optional worktree_id), `codebyplan checkpoint update --branch-name` (break-glass: MCP `create_checkpoint` / `update_checkpoint`)
132
+ - **Reads**: `.codebyplan/state/checkpoints/*.json` (local-first; `npx codebyplan sync` if stale; MCP `get_checkpoints` break-glass); `.codebyplan/repo.json`, `.codebyplan/git.json`
133
+ - **Writes**: `codebyplan checkpoint create` (description-only ideas + deadline), `codebyplan checkpoint update --branch-name` (break-glass: MCP `create_checkpoint` / `update_checkpoint`)
144
134
  - **Triggers**: `/cbp-checkpoint-plan` (auto)
@@ -265,6 +265,41 @@ After the feat branch git delete, run the same conditional Supabase teardown for
265
265
  - If not found: no-op silently — idempotent, not-found is success.
266
266
  - 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.
267
267
 
268
+ ### Step 9.5: Physical Git-Worktree Cleanup
269
+
270
+ After the feat branch has been git-deleted (Step 9), check whether a git worktree is still
271
+ checked out to that now-deleted branch and clean it up if safe.
272
+
273
+ ```bash
274
+ git worktree list --porcelain
275
+ ```
276
+
277
+ Scan the output for any `worktree` entry whose `branch` field matches `refs/heads/$FEAT_BRANCH`
278
+ (i.e. the feat branch just deleted in Step 9). For each matching worktree path:
279
+
280
+ **Case A — the current session is NOT inside that worktree path:**
281
+
282
+ ```bash
283
+ codebyplan worktree remove <path>
284
+ git worktree prune
285
+ ```
286
+
287
+ Record the removed path in `WORKTREES_REMOVED[]`. If `codebyplan worktree remove` exits
288
+ non-zero, emit a non-blocking warning and continue — a failed removal does not halt shipment.
289
+
290
+ **Case B — the current session IS inside that worktree path (i.e. `$PWD` starts with
291
+ `<path>`):**
292
+
293
+ Do NOT self-remove. Surface a single directive (no A/B/C menu):
294
+
295
+ > "This session is inside the worktree at `<path>`. After switching to your main checkout,
296
+ > run `codebyplan worktree remove <path>` to clean it up."
297
+
298
+ Record the path in `WORKTREES_REMOVED[]` as `{ path, status: 'pending_manual_cleanup' }`.
299
+
300
+ If `git worktree list --porcelain` finds no worktree checked out to the deleted feat branch,
301
+ skip this step silently — `WORKTREES_REMOVED` stays empty.
302
+
268
303
  ### Step 10: Save Shipment Results and Summary
269
304
 
270
305
  Update checkpoint context via `codebyplan checkpoint update <id> --context '{"shipment": {...}}'` (CLI write-through); use MCP `update_checkpoint` as documented break-glass when the CLI is unavailable. The `shipment` block contains both branch promotion AND runtime surface results (from `/cbp-ship` Step 7):
@@ -280,6 +315,7 @@ context.shipment: {
280
315
  stale_branches_cleaned: [list of deleted git branches],
281
316
  feat_branch_deleted: true/false,
282
317
  supabase_branches_deleted: [list of Supabase preview branch names removed in Steps 8–9],
318
+ worktrees_removed: WORKTREES_REMOVED, // from Step 9.5 — paths removed or pending manual cleanup
283
319
  e2e_images_uploaded: E2E_IMAGES_UPLOADED // from Step 7.5 — { count, stored_paths, skipped, error? } (CHK-171)
284
320
  }
285
321
  ```
@@ -310,6 +346,7 @@ Present summary:
310
346
  - Stale branches deleted: [N] ([list])
311
347
  - Feat branch: [deleted/kept]
312
348
  - Supabase preview branches deleted: [N] ([list from supabase_branches_deleted], or "none")
349
+ - Git worktrees cleaned: [N] ([paths from worktrees_removed], or "none")
313
350
 
314
351
  ### Before/After Branch State
315
352
  - Before: [list of feat/* branches]
@@ -8,7 +8,7 @@ effort: xhigh
8
8
 
9
9
  # Checkpoint Plan Command
10
10
 
11
- Runs INLINE (no subagent) — all analysis and Q&A stay in the main session. This is the rigour stage that prevents half-baked plans: it discovers shortcomings, decides whether existing dependencies suffice or a new one is warranted, compares competing approaches, and only THEN creates tasks. It produces `plan.steps[]` + tasks but **never activates the checkpoint and never claims a user/worktree** — that is `/cbp-checkpoint-start`.
11
+ Runs INLINE (no subagent) — all analysis and Q&A stay in the main session. This is the rigour stage that prevents half-baked plans: it discovers shortcomings, decides whether existing dependencies suffice or a new one is warranted, compares competing approaches, and only THEN creates tasks. It produces `plan.steps[]` + tasks but **never activates the checkpoint and never claims a user** — that is `/cbp-checkpoint-start`.
12
12
 
13
13
  ## Pipeline
14
14
 
@@ -37,7 +37,7 @@ Malformed (non-numeric, contains `-`): surface `checkpoint-plan: invalid argumen
37
37
  2. Read task files under `.codebyplan/state/checkpoints/<id>/tasks/*.json` (fallback: MCP `get_tasks(checkpoint_id)`) — load existing tasks. This sets the mode:
38
38
  - **fresh** — zero tasks: full plan + create all tasks.
39
39
  - **additive re-plan** — tasks exist: gap-analyse against them; only ADD new tasks or refine requirements for gaps. NEVER delete or overwrite an in-flight task.
40
- 3. Note whether `worktree_id` is set (claimed at create) — drives routing in Step 11.
40
+ 3. Note whether `assigned_user_id` is set (claimed at activation) — drives routing in Step 11.
41
41
 
42
42
  ### Step 2: Assess Ideas + Codebase
43
43
 
@@ -134,16 +134,18 @@ Final write of the complete `checkpoint.context` JSONB via `codebyplan checkpoin
134
134
  ...
135
135
  ```
136
136
 
137
- This skill does **NOT** activate the checkpoint and does **NOT** claim a user/worktree.
137
+ This skill does **NOT** activate the checkpoint and does **NOT** claim a user.
138
138
 
139
- - **Claimed by THIS session** — `worktree_id` is set AND equals `npx codebyplan resolve-worktree 2>/dev/null`: auto-trigger `/cbp-checkpoint-start` in the same context (the creator carries momentum into activation).
140
- - **Otherwise** — `worktree_id` is null, set to a different worktree, or `resolve-worktree` is empty: surface a single directive — `Next: /cbp-checkpoint-start` — so the owning session (or anyone free, if open) claims and starts it. Never auto-activate a checkpoint owned by a different worktree.
139
+ Resolve the current user: `npx codebyplan whoami --json` `user_id`.
140
+
141
+ - **Open or yours** — `whoami` returns a `user_id` AND `assigned_user_id` is either null (unclaimed) or equals caller `user_id`: auto-trigger `/cbp-checkpoint-start` in the same context. `assigned_user_id` is stamped on activation, so a freshly-created checkpoint (null) is claimed by `/cbp-checkpoint-start` itself — the creator carries momentum into activation.
142
+ - **Owned by another, or no identity** — `assigned_user_id` is non-null and differs from caller `user_id`, or `whoami` returns no `user_id`: surface a single directive — `Next: /cbp-checkpoint-start` — so the owning session (or anyone free, if open) claims and starts it. Never auto-activate a checkpoint owned by a different user.
141
143
 
142
144
  ## Integration
143
145
 
144
- - **Reads**: `.codebyplan/state/checkpoints/<id>.json`, `checkpoints/<id>/tasks/*.json`, `session/current.json` (local-first; `npx codebyplan sync` if stale; break-glass: MCP `get_current_task`, `get_checkpoints`, `get_tasks`)
146
+ - **Reads**: `.codebyplan/state/checkpoints/<id>.json`, `checkpoints/<id>/tasks/*.json`, `session/current.json` (local-first; `npx codebyplan sync` if stale; break-glass: MCP `get_current_task`, `get_checkpoints`, `get_tasks`); `npx codebyplan whoami --json`
145
147
  - **Writes**: `codebyplan checkpoint update` (ideas assessment, context, plan, research), `codebyplan task create` (break-glass: MCP `update_checkpoint`, `create_task`)
146
148
  - **Spawns**: `cbp-research` (level 2+ only), config-matched `cbp-e2e-*` specialist (opt-in discovery probe, `whole_checkpoint_mode` — see `context/testing/e2e.md` dispatch contract)
147
149
  - **Triggered by**: `/cbp-checkpoint-create` (auto), or user directly
148
- - **Triggers**: `/cbp-checkpoint-start` (auto when claimed at create; directive when left open)
149
- - **Never**: activates the checkpoint or claims a user/worktree — that is `/cbp-checkpoint-start`
150
+ - **Triggers**: `/cbp-checkpoint-start` (auto when open/unclaimed or assigned to the current user; directive when owned by another or no identity)
151
+ - **Never**: activates the checkpoint or claims a user — that is `/cbp-checkpoint-start`