codebyplan 1.13.39 → 1.13.40
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/dist/cli.js +24631 -354
- package/package.json +4 -2
- package/templates/agents/cbp-cc-executor.md +4 -4
- package/templates/agents/cbp-round-executor.md +2 -10
- package/templates/agents/cbp-task-check.md +2 -0
- package/templates/agents/cbp-task-planner.md +2 -5
- package/templates/hooks/README.md +14 -2
- package/templates/hooks/cbp-session-start-hook.sh +32 -0
- package/templates/hooks/cbp-test-coverage-gate.sh +20 -6
- package/templates/hooks/cbp-test-hooks.sh +72 -0
- package/templates/hooks/hooks.json +11 -0
- package/templates/settings.project.base.json +10 -0
- package/templates/skills/cbp-checkpoint-check/SKILL.md +10 -10
- package/templates/skills/cbp-checkpoint-complete/SKILL.md +7 -7
- package/templates/skills/cbp-checkpoint-create/SKILL.md +11 -9
- package/templates/skills/cbp-checkpoint-end/SKILL.md +7 -10
- package/templates/skills/cbp-checkpoint-plan/SKILL.md +10 -10
- package/templates/skills/cbp-checkpoint-start/SKILL.md +6 -6
- package/templates/skills/cbp-checkpoint-update/SKILL.md +9 -9
- package/templates/skills/cbp-git-commit/SKILL.md +8 -4
- package/templates/skills/cbp-merge-main/SKILL.md +2 -5
- package/templates/skills/cbp-round-check/SKILL.md +12 -8
- package/templates/skills/cbp-round-complete/SKILL.md +16 -10
- package/templates/skills/cbp-round-end/SKILL.md +9 -10
- package/templates/skills/cbp-round-execute/SKILL.md +7 -6
- package/templates/skills/cbp-round-input/SKILL.md +24 -12
- package/templates/skills/cbp-round-start/SKILL.md +36 -16
- package/templates/skills/cbp-round-update/SKILL.md +14 -10
- package/templates/skills/cbp-session-end/SKILL.md +22 -12
- package/templates/skills/cbp-session-start/SKILL.md +20 -47
- package/templates/skills/cbp-ship/SKILL.md +4 -4
- package/templates/skills/cbp-ship-main/SKILL.md +4 -5
- package/templates/skills/cbp-supabase-migrate/SKILL.md +12 -9
- package/templates/skills/cbp-task-check/SKILL.md +10 -10
- package/templates/skills/cbp-task-complete/SKILL.md +11 -9
- package/templates/skills/cbp-task-create/SKILL.md +7 -5
- package/templates/skills/cbp-task-start/SKILL.md +15 -17
- package/templates/skills/cbp-task-testing/SKILL.md +18 -18
- package/templates/skills/cbp-todo/SKILL.md +21 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebyplan",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.40",
|
|
4
4
|
"description": "CLI for CodeByPlan — AI-powered development planning and tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,7 +45,9 @@
|
|
|
45
45
|
"node": ">=18"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@napi-rs/keyring": "^1.1.6"
|
|
48
|
+
"@napi-rs/keyring": "^1.1.6",
|
|
49
|
+
"@supabase/supabase-js": "^2.106.0",
|
|
50
|
+
"ws": ">=8.20.1"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
53
|
"@eslint/js": "^9.18.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
scope: org-shared
|
|
3
3
|
name: cbp-cc-executor
|
|
4
4
|
description: Authoring executor for `.claude/` infrastructure. Applies approved changes across rules, skills, agents, context, CLAUDE.md, settings, and hooks — with update-first discipline, scope-marker enforcement, and length-limit awareness. Callable by the main conversation, `/cbp-checkpoint-end`, and `round-executor` (for in-scope `.claude/` infra deliverables).
|
|
5
|
-
tools: Read, Write, Edit, Glob, Grep, Skill, Task, AskUserQuestion,
|
|
5
|
+
tools: Read, Write, Edit, Glob, Grep, Skill, Task, AskUserQuestion, Bash(npx codebyplan task create *)
|
|
6
6
|
model: sonnet
|
|
7
7
|
effort: xhigh
|
|
8
8
|
---
|
|
@@ -134,7 +134,7 @@ Record every applied change with `authored_via` and `status`.
|
|
|
134
134
|
|
|
135
135
|
- **Downgrade `create` → `update`** — apply silently, note in output.
|
|
136
136
|
- **Unclear fit between two existing files** — `AskUserQuestion` with the two candidates and their descriptions.
|
|
137
|
-
- **Change exceeds this invocation's scope** (e.g. proposal implies a broader refactor) — create a task via
|
|
137
|
+
- **Change exceeds this invocation's scope** (e.g. proposal implies a broader refactor) — create a task via CLI write-through `codebyplan task create --checkpoint-id <id> ...` per `cbp-task-create` Step 3.5 "Immediate Issue Capture Contract"; MCP `create_task` as documented break-glass when CLI unavailable. Record in `deferred_changes` with `task_id_created`.
|
|
138
138
|
- **Hook or settings change with cross-environment implications** — require explicit `scope` field from caller; if missing or ambiguous, ask.
|
|
139
139
|
|
|
140
140
|
### Phase 5: Post-Apply Sanity
|
|
@@ -197,7 +197,7 @@ Block-limit violations are non-negotiable — split before applying.
|
|
|
197
197
|
- **Signed creates, edited updates** — creates route through build-cc skills (they embed the signature); updates use direct Edit on already-signed files.
|
|
198
198
|
- **Never create agents** — only `update`. Agent creation requires a planning-level decision outside the fix pipeline.
|
|
199
199
|
- **Length limits are enforced pre-apply** — refuse to produce a file that will fail the block limit.
|
|
200
|
-
- **Surface, don't silently swallow** — ambiguity → `AskUserQuestion`; out-of-scope → MCP `create_task
|
|
200
|
+
- **Surface, don't silently swallow** — ambiguity → `AskUserQuestion`; out-of-scope → CLI `codebyplan task create` (MCP `create_task` as documented break-glass).
|
|
201
201
|
- **Fresh inventory per invocation** — never reuse a cached inventory from a prior call.
|
|
202
202
|
|
|
203
203
|
## Integration
|
|
@@ -206,5 +206,5 @@ Block-limit violations are non-negotiable — split before applying.
|
|
|
206
206
|
- **Reads**: `.claude/` inventory, `validate-structure-lengths.sh`, target files
|
|
207
207
|
- **Writes**: `.claude/` files (via `/cbp-build-cc-*` skills for creates, direct Edit for updates)
|
|
208
208
|
- **Calls skills**: `/cbp-build-cc-rule`, `/cbp-build-cc-skill`, `/cbp-build-cc-claude-file`, `/cbp-build-cc-settings`
|
|
209
|
-
- **Creates tasks**: via
|
|
209
|
+
- **Creates tasks**: via CLI write-through `codebyplan task create` when a change exceeds invocation scope; MCP `create_task` as documented break-glass when CLI unavailable
|
|
210
210
|
- **Enforced by**: `validate-structure-lengths.sh` (length), `validate-structure-scope.sh` (scope marker), `validate-structure-patterns.sh` (path layout)
|
|
@@ -212,16 +212,6 @@ If modifying managed files (`.claude/*`, `.claude/docs/architecture/*`, etc.):
|
|
|
212
212
|
|
|
213
213
|
**Why:** Routing commands do this automatically. If you bypass routing, you MUST do source consultation manually. Skills contain coding patterns and conventions that must be followed.
|
|
214
214
|
|
|
215
|
-
### Step 2.4: Architecture Map Consultation
|
|
216
|
-
|
|
217
|
-
For ANY module you are about to edit (app code or managed files), check for a pre-computed architecture map:
|
|
218
|
-
|
|
219
|
-
1. For each path in `files_to_modify`, derive its module and Glob `.claude/architecture/<module-slug>.md`.
|
|
220
|
-
2. If a map exists, read it BEFORE Step 3 — it surfaces the module's boundaries, internal structure, dependencies, and where-things-live landmarks, reducing broad file-system scans.
|
|
221
|
-
3. If `.claude/architecture/` is absent or the module has no map, proceed without it (not a blocker).
|
|
222
|
-
|
|
223
|
-
See `.claude/context/architecture-map.md` for the full consultation contract. Unlike Step 2 (managed files only), this step fires for every round regardless of file type.
|
|
224
|
-
|
|
225
215
|
### Step 2.5: Search Before Creating
|
|
226
216
|
|
|
227
217
|
For each file with action `create` in `files_to_modify`:
|
|
@@ -610,6 +600,8 @@ Which would you prefer?
|
|
|
610
600
|
- **Spawned by**: `/cbp-round-execute` Step 3 (single-wave 3-AGENT path or per-wave 3-WAVE path)
|
|
611
601
|
- **Returns to**: `/cbp-round-execute` which collects output and runs per-wave `cbp-testing-qa-agent`
|
|
612
602
|
- **Depends on**: `cbp-task-planner` agent (provides approved plan)
|
|
603
|
+
- **Reads**: All task/round context arrives via the Input Contract (approved plan from `/cbp-round-start`). When the executor needs to read additional round or task state, read `.codebyplan/state/checkpoints/<id>/tasks/<id>/rounds/<id>.json` (local-first). If missing/stale, run `npx codebyplan sync` once and re-read. Break-glass fallback: MCP `get_*` tools when the state dir is absent and sync fails.
|
|
604
|
+
- **Writes**: DB-side mutations are surfaced as `improvements_noted` entries for the orchestrator to execute (executor frontmatter excludes MCP DB tools — see Step 0.2 carve-out).
|
|
613
605
|
- **May spawn**: `cbp-database-agent` (Supabase operations), `general-purpose` (background batch writes), and `cbp-cc-executor` (in-scope `.claude/` infra deliverables, `source: 'round-executor'`) as sub-executors. (NOT any `cbp-e2e-*` specialist — e2e is orchestrator-owned, spawned by `/cbp-round-execute` Step 5 per the Step 0.2 carve-out.)
|
|
614
606
|
|
|
615
607
|
## Structure Knowledge
|
|
@@ -216,3 +216,5 @@ When no divergence is detected, set `scope_divergence_detected: false` and proce
|
|
|
216
216
|
|
|
217
217
|
- **Spawned by**: `/cbp-task-check` command
|
|
218
218
|
- **Returns to**: `/cbp-task-check` which routes based on verdict
|
|
219
|
+
- **Reads**: All task, checkpoint, and rounds data arrives via the Input Contract (passed by `/cbp-task-check`). Local `.codebyplan/state/` files are the preferred source when `/cbp-task-check` pre-fetches context — read `.codebyplan/state/checkpoints/<checkpointId>/tasks/<taskId>.json` and `rounds/*.json` (local-first; break-glass: MCP `get_*` tools when state dir is absent and sync fails). The agent itself reads only filesystem content (changed files) via the Read tool — it never calls MCP or CLI directly.
|
|
220
|
+
- **Writes**: None — review only, never edits.
|
|
@@ -376,11 +376,6 @@ delegation_hint:
|
|
|
376
376
|
|
|
377
377
|
Read `.claude/rules/*.md` and relevant architecture docs.
|
|
378
378
|
|
|
379
|
-
If `.claude/architecture/` contains map file(s) for the target module(s), read them before
|
|
380
|
-
finalizing scope — they provide pre-computed structural context (boundaries, dependencies,
|
|
381
|
-
landmarks) that reduces the need for broad file-system scans. See
|
|
382
|
-
`.claude/context/architecture-map.md` for the consultation contract.
|
|
383
|
-
|
|
384
379
|
### Phase 4: Clarify Requirements (Context-First)
|
|
385
380
|
|
|
386
381
|
Before any AskUserQuestion call, check (1) `checkpoint.context`, (2) `task.context`, (3) codebase via Grep/Glob/Read. Only ask if all three fail. When asking, prefix with `Checked: [sources]. Not found. Asking: [question]`. If a question IS answered in context, use that answer directly — do not re-ask.
|
|
@@ -590,3 +585,5 @@ Use TaskCreate for plan step visibility.
|
|
|
590
585
|
|
|
591
586
|
- **Spawned by**: `/cbp-round-start` (Step 5)
|
|
592
587
|
- **Returns to**: `/cbp-round-start` for user approval
|
|
588
|
+
- **Reads**: All DB state arrives via the Input Contract (pre-fetched by `/cbp-round-start`). Local `.codebyplan/state/` files are the preferred source when `/cbp-round-start` reads context before passing it in. Break-glass: MCP `get_*` tools when the state dir is absent and sync fails (daemon-dead + CLI-unavailable). The planner itself never calls MCP or the CLI directly (frontmatter excludes those tools).
|
|
589
|
+
- **Writes**: None — planner never mutates DB state.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `codebyplan` npm package ships a small, portable set of Claude Code hooks. They run in your project, use only generic primitives (`git rev-parse`, `${CLAUDE_PROJECT_DIR}`, `${CLAUDE_PLUGIN_ROOT}`), and degrade gracefully (exit 0) when their preconditions aren't met.
|
|
4
4
|
|
|
5
|
-
Hook registration lives in [`hooks/hooks.json`](./hooks.json) — PreToolUse, PostToolUse, and
|
|
5
|
+
Hook registration lives in [`hooks/hooks.json`](./hooks.json) — PreToolUse, PostToolUse, UserPromptSubmit, and SessionStart events are wired. (`Notification`, `SessionEnd`, `Stop`, and `SubagentStop` are also schema-permitted but unused here.)
|
|
6
6
|
|
|
7
7
|
**`cbp-statusline.sh` is auto-wired via `settings.project.base.json`.** The `statusLine` block is shipped inside `templates/settings.project.base.json` and merged into the consumer's `.claude/settings.json` automatically by `codebyplan claude install` (and on every `codebyplan claude update`). No manual copy-paste is required.
|
|
8
8
|
|
|
@@ -243,13 +243,25 @@ drops below threshold (e.g. after `/compact` or `/clear`), so the notice re-fire
|
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
246
|
+
### `cbp-session-start-hook.sh` — SessionStart
|
|
247
|
+
|
|
248
|
+
Hydrates the local-first state mirror at session start: runs `codebyplan sync` (full hydrate when cold, delta pull otherwise) and `codebyplan watch start` (idempotent — pidfile + process-alive check) so the per-worktree Realtime down-sync daemon is running for the session.
|
|
249
|
+
|
|
250
|
+
**Blocks vs warns**: never blocks — exit 0 always. All command errors are swallowed.
|
|
251
|
+
|
|
252
|
+
**Skips when**: no `.codebyplan/repo.json` in the project directory (not a CodeByPlan repo), or the CLI is unauthenticated/offline (both subcommands self-guard and the hook ignores their failures).
|
|
253
|
+
|
|
254
|
+
**Opt out**: settings.json override removing the `SessionStart` entry, or plugin disable.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
246
258
|
## Supporting (not registered)
|
|
247
259
|
|
|
248
260
|
### `test-hooks.sh` — invoked by `auto-test-hooks.sh`
|
|
249
261
|
|
|
250
262
|
Test suite for the plugin's 9 registered hooks. Runs two passes:
|
|
251
263
|
|
|
252
|
-
1. **Header check** — every registered hook (`lint-format-on-edit`, `test-coverage-gate`, `pre-commit-quality-gate`, `maestro-yaml-validate`, `auto-test-hooks`, `mcp-migration-guard`, `validate-git-stash-deny`, `cbp-mcp-round-sync`, `cbp-context-window-notify`) carries the required `# Hook:` and `# Purpose:` header comments. `statusline` uses its own `# Claude Code Status Line` marker.
|
|
264
|
+
1. **Header check** — every registered hook (`lint-format-on-edit`, `test-coverage-gate`, `pre-commit-quality-gate`, `maestro-yaml-validate`, `auto-test-hooks`, `mcp-migration-guard`, `validate-git-stash-deny`, `cbp-mcp-round-sync`, `cbp-context-window-notify`, `cbp-session-start-hook`) carries the required `# Hook:` and `# Purpose:` header comments. `statusline` uses its own `# Claude Code Status Line` marker.
|
|
253
265
|
2. **Functional smoke tests** — each hook is invoked with synthetic stdin matching its fast-path / graceful-degrade input; all must exit 0.
|
|
254
266
|
|
|
255
267
|
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`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @scope: org-shared
|
|
3
|
+
# Hook: SessionStart
|
|
4
|
+
# Purpose: Hydrate .codebyplan/state/ via `codebyplan sync` and ensure the
|
|
5
|
+
# per-worktree watch daemon is running. Hook-safe: all errors
|
|
6
|
+
# swallowed, always exits 0. No-op when unauthenticated/offline.
|
|
7
|
+
#
|
|
8
|
+
# Runtime bound: the registered settings entry carries "timeout": 30 so the
|
|
9
|
+
# Claude Code hook runtime kills a hung sync/start — never shell-wrap with
|
|
10
|
+
# `timeout` (absent on macOS). `codebyplan sync` self-bounds its HTTP calls;
|
|
11
|
+
# `watch start` returns immediately after the detached spawn.
|
|
12
|
+
#
|
|
13
|
+
# Concurrency: `codebyplan watch start` is atomically idempotent — it takes an
|
|
14
|
+
# exclusive-create .pid.lock (O_EXCL) around the pidfile check + spawn, so two
|
|
15
|
+
# SessionStart hooks firing together in the same worktree start at most one
|
|
16
|
+
# daemon (the loser exits 0 with "start already in progress").
|
|
17
|
+
|
|
18
|
+
# Resolve the project dir: Claude Code sets CLAUDE_PROJECT_DIR; fall back to pwd.
|
|
19
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
20
|
+
|
|
21
|
+
# No-op when not inside a codebyplan repo (sentinel file absent).
|
|
22
|
+
if [ ! -f "$PROJECT_DIR/.codebyplan/repo.json" ]; then
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Hydrate state cache from backend (no-op offline / unauthenticated).
|
|
27
|
+
npx codebyplan sync >/dev/null 2>&1 || true
|
|
28
|
+
|
|
29
|
+
# Ensure per-worktree watch daemon is running (no-op when already up).
|
|
30
|
+
npx codebyplan watch start >/dev/null 2>&1 || true
|
|
31
|
+
|
|
32
|
+
exit 0
|
|
@@ -59,15 +59,20 @@ CHECKED=0
|
|
|
59
59
|
SKIPPED=0
|
|
60
60
|
|
|
61
61
|
while IFS= read -r FILE; do
|
|
62
|
-
# Skip test files themselves
|
|
63
|
-
|
|
62
|
+
# Skip test files themselves. The leading [.-] class also matches framework
|
|
63
|
+
# spec conventions that suffix the marker with a dash — e.g. NestJS
|
|
64
|
+
# `*.e2e-spec.ts` (dot-form `.spec.ts` plus dash-form `-spec.ts`/`-test.ts`).
|
|
65
|
+
if echo "$FILE" | grep -qE '[.-](test|spec)\.(ts|tsx|js|jsx)$'; then
|
|
64
66
|
continue
|
|
65
67
|
fi
|
|
66
68
|
|
|
67
|
-
# Skip files under a __tests__
|
|
68
|
-
# setup, manual mocks, and other
|
|
69
|
-
#
|
|
70
|
-
|
|
69
|
+
# Skip files under a __tests__/, __mocks__/, or top-level test/ directory —
|
|
70
|
+
# fixtures, helpers, setup (seed.ts, load-env.ts), manual mocks, and other
|
|
71
|
+
# test infrastructure are imported by the test files that exercise them;
|
|
72
|
+
# requiring a dedicated .test.ts for a fixture is nonsensical. `/test/` covers
|
|
73
|
+
# the NestJS e2e convention (apps/<app>/test/*.e2e-spec.ts) alongside the
|
|
74
|
+
# __tests__/ and __mocks__/ layouts.
|
|
75
|
+
if echo "$FILE" | grep -qE '/__tests__/|/__mocks__/|/test/'; then
|
|
71
76
|
SKIPPED=$((SKIPPED + 1))
|
|
72
77
|
continue
|
|
73
78
|
fi
|
|
@@ -88,6 +93,15 @@ while IFS= read -r FILE; do
|
|
|
88
93
|
continue
|
|
89
94
|
fi
|
|
90
95
|
|
|
96
|
+
# Skip NestJS framework wrappers — *.module.ts are dependency-injection wiring
|
|
97
|
+
# barrels and *.decorator.ts are metadata-only; behavior lives in the
|
|
98
|
+
# providers/handlers they wire, which carry their own specs. Same category as
|
|
99
|
+
# the index barrel skip above.
|
|
100
|
+
if echo "$FILE" | grep -qE '\.(module|decorator)\.(ts|js)$'; then
|
|
101
|
+
SKIPPED=$((SKIPPED + 1))
|
|
102
|
+
continue
|
|
103
|
+
fi
|
|
104
|
+
|
|
91
105
|
# Skip Next.js App Router framework files — page/layout/loading/error/route/
|
|
92
106
|
# etc. are framework-convention wrappers, not logic-bearing modules. Same
|
|
93
107
|
# category as the barrel/index skip above: behavior lives in the components
|
|
@@ -455,6 +455,78 @@ fi
|
|
|
455
455
|
|
|
456
456
|
echo ""
|
|
457
457
|
|
|
458
|
+
# ===== HOOK SMOKE TESTS — cbp-session-start-hook =====
|
|
459
|
+
echo "## Hook Smoke Tests — cbp-session-start-hook (CHK-178)"
|
|
460
|
+
|
|
461
|
+
SESSION_START_HOOK="$HOOKS_DIR/cbp-session-start-hook.sh"
|
|
462
|
+
|
|
463
|
+
if [ ! -f "$SESSION_START_HOOK" ]; then
|
|
464
|
+
test_result "cbp-session-start-hook.sh present" "passed" "missing"
|
|
465
|
+
else
|
|
466
|
+
test_result "cbp-session-start-hook.sh present" "passed" "passed"
|
|
467
|
+
|
|
468
|
+
# Header/@scope check
|
|
469
|
+
FIRST_LINE=$(head -1 "$SESSION_START_HOOK")
|
|
470
|
+
if echo "$FIRST_LINE" | grep -q '^#!/'; then
|
|
471
|
+
test_result "cbp-session-start-hook.sh has shebang" "passed" "passed"
|
|
472
|
+
else
|
|
473
|
+
test_result "cbp-session-start-hook.sh has shebang" "passed" "missing"
|
|
474
|
+
fi
|
|
475
|
+
|
|
476
|
+
if grep -q '@scope: org-shared' "$SESSION_START_HOOK"; then
|
|
477
|
+
test_result "cbp-session-start-hook.sh has @scope: org-shared" "passed" "passed"
|
|
478
|
+
else
|
|
479
|
+
test_result "cbp-session-start-hook.sh has @scope: org-shared" "passed" "missing"
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
# Syntax check
|
|
483
|
+
if bash -n "$SESSION_START_HOOK" 2>/dev/null; then
|
|
484
|
+
test_result "cbp-session-start-hook.sh bash -n syntax ok" "passed" "passed"
|
|
485
|
+
else
|
|
486
|
+
test_result "cbp-session-start-hook.sh bash -n syntax ok" "passed" "failed"
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
# Graceful-degrade: run in a temp dir without .codebyplan/repo.json — hook
|
|
490
|
+
# must exit 0 (no-op) without invoking npx (stub npx as a no-op in PATH).
|
|
491
|
+
ISO=$(mktemp -d)
|
|
492
|
+
# Create a stub npx in a temp bin dir so npx invocations are harmless no-ops.
|
|
493
|
+
STUB_BIN=$(mktemp -d)
|
|
494
|
+
printf '#!/bin/bash\nexit 0\n' > "$STUB_BIN/npx"
|
|
495
|
+
chmod +x "$STUB_BIN/npx"
|
|
496
|
+
ACTUAL_EXIT=$(CLAUDE_PROJECT_DIR="$ISO" PATH="$STUB_BIN:$PATH" bash "$SESSION_START_HOOK" >/dev/null 2>&1; echo $?)
|
|
497
|
+
# In the no-repo.json path npx must NOT be invoked at all — use a recording
|
|
498
|
+
# stub marker dir to assert zero invocations.
|
|
499
|
+
rm -rf "$ISO" "$STUB_BIN"
|
|
500
|
+
if [ "$ACTUAL_EXIT" = "0" ]; then
|
|
501
|
+
test_result "cbp-session-start-hook.sh graceful-degrade (no repo.json) exits 0" "passed" "passed"
|
|
502
|
+
else
|
|
503
|
+
test_result "cbp-session-start-hook.sh graceful-degrade (no repo.json) exits 0" "passed" "failed (exit=$ACTUAL_EXIT)"
|
|
504
|
+
fi
|
|
505
|
+
|
|
506
|
+
# Positive path: with .codebyplan/repo.json present, the hook must invoke
|
|
507
|
+
# `npx codebyplan sync` AND `npx codebyplan watch start` (recording stub
|
|
508
|
+
# writes each invocation's args to a marker file), and still exit 0.
|
|
509
|
+
ISO=$(mktemp -d)
|
|
510
|
+
STUB_BIN=$(mktemp -d)
|
|
511
|
+
MARKER="$STUB_BIN/invocations.log"
|
|
512
|
+
mkdir -p "$ISO/.codebyplan"
|
|
513
|
+
printf '{}' > "$ISO/.codebyplan/repo.json"
|
|
514
|
+
printf '#!/bin/bash
|
|
515
|
+
echo "$@" >> "%s"
|
|
516
|
+
exit 0
|
|
517
|
+
' "$MARKER" > "$STUB_BIN/npx"
|
|
518
|
+
chmod +x "$STUB_BIN/npx"
|
|
519
|
+
ACTUAL_EXIT=$(CLAUDE_PROJECT_DIR="$ISO" PATH="$STUB_BIN:$PATH" bash "$SESSION_START_HOOK" >/dev/null 2>&1; echo $?)
|
|
520
|
+
if [ "$ACTUAL_EXIT" = "0" ] && grep -q "codebyplan sync" "$MARKER" 2>/dev/null && grep -q "codebyplan watch start" "$MARKER" 2>/dev/null; then
|
|
521
|
+
test_result "cbp-session-start-hook.sh positive path invokes sync + watch start, exits 0" "passed" "passed"
|
|
522
|
+
else
|
|
523
|
+
test_result "cbp-session-start-hook.sh positive path invokes sync + watch start, exits 0" "passed" "failed (exit=$ACTUAL_EXIT)"
|
|
524
|
+
fi
|
|
525
|
+
rm -rf "$ISO" "$STUB_BIN"
|
|
526
|
+
fi
|
|
527
|
+
|
|
528
|
+
echo ""
|
|
529
|
+
|
|
458
530
|
# ===== SUMMARY =====
|
|
459
531
|
echo "=== TEST SUMMARY ==="
|
|
460
532
|
echo -e "Passed: ${GREEN}$PASSED${NC}"
|
|
@@ -194,6 +194,16 @@
|
|
|
194
194
|
"Bash(npx codebyplan lsp:*)",
|
|
195
195
|
"Bash(codebyplan round:*)",
|
|
196
196
|
"Bash(npx codebyplan round:*)",
|
|
197
|
+
"Bash(codebyplan sync:*)",
|
|
198
|
+
"Bash(npx codebyplan sync:*)",
|
|
199
|
+
"Bash(codebyplan watch:*)",
|
|
200
|
+
"Bash(npx codebyplan watch:*)",
|
|
201
|
+
"Bash(codebyplan checkpoint:*)",
|
|
202
|
+
"Bash(npx codebyplan checkpoint:*)",
|
|
203
|
+
"Bash(codebyplan task:*)",
|
|
204
|
+
"Bash(npx codebyplan task:*)",
|
|
205
|
+
"Bash(codebyplan session:*)",
|
|
206
|
+
"Bash(npx codebyplan session:*)",
|
|
197
207
|
"Bash(codebyplan help:*)",
|
|
198
208
|
"Bash(npx codebyplan help:*)",
|
|
199
209
|
"Bash(codebyplan --version:*)",
|
|
@@ -14,17 +14,17 @@ Full re-evaluation: compares initial ideas vs delivered work, aggregates files a
|
|
|
14
14
|
|
|
15
15
|
### Step 1: Identify Checkpoint
|
|
16
16
|
|
|
17
|
-
**If arguments provided:** Parse `$ARGUMENTS` for CHK-NNN format, extract number.
|
|
17
|
+
**If arguments provided:** Parse `$ARGUMENTS` for CHK-NNN format, extract number. Scan `.codebyplan/state/checkpoints/*.json` to find by `number` field (local-first; if missing/stale run `npx codebyplan sync` once; break-glass: MCP `get_checkpoints`).
|
|
18
18
|
|
|
19
|
-
**If NO arguments:**
|
|
19
|
+
**If NO arguments:** Read `.codebyplan/state/session/current.json` to get the active checkpoint (fallback: MCP `get_current_task`).
|
|
20
20
|
|
|
21
21
|
If no checkpoint found, show error and stop.
|
|
22
22
|
|
|
23
23
|
### Step 2: Load All Data
|
|
24
24
|
|
|
25
|
-
1.
|
|
26
|
-
2.
|
|
27
|
-
3. For each task,
|
|
25
|
+
1. Read `.codebyplan/state/checkpoints/<id>.json` for checkpoint details (context, research, qa, ideas, goal, user_context). Break-glass: MCP `get_checkpoints`.
|
|
26
|
+
2. Read task files under `.codebyplan/state/checkpoints/<id>/tasks/*.json` (fallback: MCP `get_tasks`).
|
|
27
|
+
3. For each task, read round files under `.codebyplan/state/checkpoints/<id>/tasks/<taskId>/rounds/*.json` (fallback: MCP `get_rounds`).
|
|
28
28
|
|
|
29
29
|
### Step 3: Before/After Comparison
|
|
30
30
|
|
|
@@ -109,7 +109,7 @@ Aggregate the files touched across all tasks (reusing Step 4's deduplicated tabl
|
|
|
109
109
|
credential_vars: [from e2e.json — env var names only, never secrets]
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
Hold each specialist's output keyed by framework (an `e2e_outputs[framework]` map) for this skill's aggregation — checkpoint-check has no MCP round, so this lives in-memory during the run (persist to `checkpoint.context` via
|
|
112
|
+
Hold each specialist's output keyed by framework (an `e2e_outputs[framework]` map) for this skill's aggregation — checkpoint-check has no MCP round, so this lives in-memory during the run (persist to `checkpoint.context` via the Step 7 CLI write-through — `codebyplan checkpoint update` — if a durable record is needed). `test_strategy` is intentionally omitted — the agent resolves it from `.codebyplan/e2e.json` and the DB tech-stack record.
|
|
113
113
|
|
|
114
114
|
3. **Wait for all specialists to complete.** Each agent's output carries `whole_checkpoint_aggregated: true` confirming whole-checkpoint formatting.
|
|
115
115
|
|
|
@@ -120,7 +120,7 @@ Aggregate the files touched across all tasks (reusing Step 4's deduplicated tabl
|
|
|
120
120
|
Continue to Step 6.
|
|
121
121
|
|
|
122
122
|
5. **On fail** (any framework `f`: `e2e_outputs[f].status === 'failed'` OR `e2e_outputs[f].test_results.failed > 0`): build a failure summary from `e2e_outputs[*].test_results.failures[]` aggregated and grouped by `category`. Surface via `AskUserQuestion`:
|
|
123
|
-
- **(a) Create fix-task in CHK-{NNN} (recommended)** —
|
|
123
|
+
- **(a) Create fix-task in CHK-{NNN} (recommended)** — run `codebyplan task create` (CLI write-through; break-glass: MCP `create_task`) with `checkpoint_id=current_checkpoint_id`, `title="Fix checkpoint-level e2e failures (CHK-{NNN})"`, `requirements` containing the detailed failure breakdown (category counts, files involved, pages broken, screenshot paths from `e2e_outputs[*].screenshots[]`), AND `context: { source_checkpoint_id, e2e_failure_summary: { category_counts, pages_broken, screenshot_paths }, fix_type: "checkpoint_e2e" }` so downstream `cbp-task-planner` can verify failure premises. Per `cbp-round-end` reference `findings-presentation.md` "Infra Issue Absorption Contract — Resolve-in-Current-Scope by Default", checkpoint-level e2e failures absorb into the active checkpoint — not standalone.
|
|
124
124
|
- **(b) Surface as warning only — proceed to checkpoint-end** — append `| Checkpoint E2E | warning | N failures (deferred) |` to Step 5 QA Summary; continue to Step 6.
|
|
125
125
|
- **(c) Halt — review manually** — STOP and wait for the user.
|
|
126
126
|
|
|
@@ -145,7 +145,7 @@ If unapproved files exist:
|
|
|
145
145
|
|
|
146
146
|
### Step 7: Save Results
|
|
147
147
|
|
|
148
|
-
Update checkpoint via MCP `update_checkpoint
|
|
148
|
+
Update checkpoint via `codebyplan checkpoint update --id <id> --qa <json> --context <json>` (CLI write-through; break-glass: MCP `update_checkpoint`):
|
|
149
149
|
- `qa`: aggregated QA results
|
|
150
150
|
- `context`: add `check_results` with before/after, file summary, assessment
|
|
151
151
|
|
|
@@ -155,6 +155,6 @@ If all clear, auto-trigger `/cbp-checkpoint-end`.
|
|
|
155
155
|
|
|
156
156
|
## Integration
|
|
157
157
|
|
|
158
|
-
- **Reads**: MCP `get_checkpoints`, `get_tasks`, `get_rounds`, `get_current_task`
|
|
159
|
-
- **Writes**: MCP `update_checkpoint`
|
|
158
|
+
- **Reads**: `.codebyplan/state/checkpoints/<id>.json`, `checkpoints/<id>/tasks/*.json`, `checkpoints/<id>/tasks/<taskId>/rounds/*.json`, `session/current.json` (local-first; `npx codebyplan sync` if stale; break-glass: MCP `get_checkpoints`, `get_tasks`, `get_rounds`, `get_current_task`)
|
|
159
|
+
- **Writes**: `codebyplan checkpoint update --qa ... --context ...` (CLI write-through; break-glass: MCP `update_checkpoint`)
|
|
160
160
|
- **Triggers**: `/cbp-checkpoint-end` (auto, if ready) or STOPS for `/cbp-task-create` (if issues)
|
|
@@ -19,7 +19,7 @@ Parse the argument:
|
|
|
19
19
|
| Shape | Regex | Resolves to |
|
|
20
20
|
|-------|-------|-------------|
|
|
21
21
|
| `{chk}` (e.g. `108`) | `^[0-9]+$` | Target CHK-{chk} |
|
|
22
|
-
| _(empty)_ | — |
|
|
22
|
+
| _(empty)_ | — | Resolve from local state per Step 1.5/2 (MCP `get_current_task` break-glass) — the active checkpoint |
|
|
23
23
|
|
|
24
24
|
Anything else is malformed — surface this error and stop:
|
|
25
25
|
|
|
@@ -42,8 +42,8 @@ Given the parse from Step 0.5:
|
|
|
42
42
|
|
|
43
43
|
| Parse | Resolution path |
|
|
44
44
|
|-------|-----------------|
|
|
45
|
-
| `{chk}` |
|
|
46
|
-
| _(empty)_ |
|
|
45
|
+
| `{chk}` | Scan `.codebyplan/state/checkpoints/*.json` for `number === {chk}` (local-first; if missing/stale run `npx codebyplan sync` once; break-glass: MCP `get_checkpoints`). |
|
|
46
|
+
| _(empty)_ | Read `.codebyplan/state/session/current.json` for the active checkpoint (fallback: MCP `get_current_task`). If no active checkpoint, show error and stop. |
|
|
47
47
|
|
|
48
48
|
### Step 2: Verify Checkpoint End Has Run
|
|
49
49
|
|
|
@@ -59,7 +59,7 @@ Stop here.
|
|
|
59
59
|
|
|
60
60
|
### Step 2.5: Verify All Tasks Complete
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Read task files under `.codebyplan/state/checkpoints/<id>/tasks/*.json` (fallback: MCP `get_tasks`). Verify all tasks have status `completed`.
|
|
63
63
|
|
|
64
64
|
If incomplete tasks remain:
|
|
65
65
|
```
|
|
@@ -82,7 +82,7 @@ If any critical failures, warn the user but don't block.
|
|
|
82
82
|
|
|
83
83
|
### Step 4: Complete Checkpoint
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Run `codebyplan checkpoint complete --id <checkpoint-id>` (CLI write-through: updates `.codebyplan/state/checkpoints/<id>.json` + REST; break-glass: MCP `complete_checkpoint`).
|
|
86
86
|
|
|
87
87
|
This automatically:
|
|
88
88
|
- Sets status to `completed`
|
|
@@ -105,5 +105,5 @@ This automatically:
|
|
|
105
105
|
## Integration
|
|
106
106
|
|
|
107
107
|
- **Triggered by**: `/cbp-checkpoint-end` (auto, after successful shipment)
|
|
108
|
-
- **Reads**: MCP `get_current_task`, `get_tasks`, `get_rounds`
|
|
109
|
-
- **Writes**: MCP `complete_checkpoint`
|
|
108
|
+
- **Reads**: `.codebyplan/state/session/current.json`, `checkpoints/<id>.json`, `checkpoints/<id>/tasks/*.json` (local-first; `npx codebyplan sync` if stale; break-glass: MCP `get_current_task`, `get_tasks`, `get_rounds`)
|
|
109
|
+
- **Writes**: `codebyplan checkpoint complete --id <id>` (CLI write-through; break-glass: MCP `complete_checkpoint`)
|
|
@@ -20,7 +20,7 @@ Runs INLINE. This is the **mechanical** stage only: capture raw user input, infe
|
|
|
20
20
|
|
|
21
21
|
### Step 1: Check for Existing Checkpoint Data
|
|
22
22
|
|
|
23
|
-
Source `repo_id` from `.codebyplan/repo.json`. If `$ARGUMENTS` contains a checkpoint number,
|
|
23
|
+
Source `repo_id` from `.codebyplan/repo.json`. If `$ARGUMENTS` contains a checkpoint number, read `.codebyplan/state/checkpoints/` to find the matching file by `number` field (local-first). If missing/stale, run `npx codebyplan sync` once and re-read. Break-glass fallback: MCP `get_checkpoints` when the state dir is absent and sync fails. If the checkpoint already has `ideas[]` with descriptions, reuse `ideas[].description` (do not re-ask) and skip Step 2.
|
|
24
24
|
|
|
25
25
|
### Step 2: Get Checkpoint Description
|
|
26
26
|
|
|
@@ -71,14 +71,16 @@ Record the choice; it drives both the create call (Step 8) and the plan→start
|
|
|
71
71
|
|
|
72
72
|
### Step 7: Determine Next Checkpoint Number
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Scan `.codebyplan/state/checkpoints/*.json` for the highest `number` field + 1. If state dir is absent, run `npx codebyplan sync` once. Break-glass fallback: MCP `get_checkpoints` when sync fails.
|
|
75
75
|
|
|
76
76
|
### Step 8: Create Checkpoint Row
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
- `
|
|
80
|
-
- `
|
|
81
|
-
- `
|
|
78
|
+
`codebyplan checkpoint create` (CLI write-through: writes `.codebyplan/state/checkpoints/<id>.json` + REST). Pass flags:
|
|
79
|
+
- `--repo-id` (from `.codebyplan/repo.json`), `--number`, `--title`, `--goal`, `--deadline`, `--status pending`
|
|
80
|
+
- `--ideas` JSON `[{ description: <raw user text> }]`
|
|
81
|
+
- `--worktree-id` the resolved worktree **only if the user chose "claim"**; omit when "leave open"
|
|
82
|
+
|
|
83
|
+
Break-glass fallback: MCP `create_checkpoint` when the CLI is unavailable.
|
|
82
84
|
|
|
83
85
|
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.
|
|
84
86
|
|
|
@@ -93,7 +95,7 @@ git checkout -b "feat/CHK-{NNN}-{slug}" "origin/$BASE" 2>/dev/null \
|
|
|
93
95
|
git push -u origin "feat/CHK-{NNN}-{slug}"
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
Slug: lowercase, dash-joined, punctuation dropped, ≤40 chars. Persist the branch via
|
|
98
|
+
Slug: lowercase, dash-joined, punctuation dropped, ≤40 chars. Persist the branch via `codebyplan checkpoint update --id <checkpoint-id> --branch-name "feat/CHK-{NNN}-{slug}"` (CLI write-through; break-glass: MCP `update_checkpoint`). (The dedicated `/cbp-git-branch-feat-create` skill is the canonical config-driven helper if you prefer to delegate.)
|
|
97
99
|
|
|
98
100
|
**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
101
|
|
|
@@ -113,6 +115,6 @@ Auto-trigger `/cbp-checkpoint-plan {NNN}` in the same context. This skill create
|
|
|
113
115
|
## Integration
|
|
114
116
|
|
|
115
117
|
- **Runs inline**: mechanical setup only — no assessment, research, Q&A, plan, or tasks
|
|
116
|
-
- **Reads**: MCP `get_checkpoints
|
|
117
|
-
- **Writes**:
|
|
118
|
+
- **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`
|
|
119
|
+
- **Writes**: `codebyplan checkpoint create` (description-only ideas + deadline + optional worktree_id), `codebyplan checkpoint update --branch-name` (break-glass: MCP `create_checkpoint` / `update_checkpoint`)
|
|
118
120
|
- **Triggers**: `/cbp-checkpoint-plan` (auto)
|
|
@@ -34,7 +34,7 @@ Before any shipment logic, ensure the feat branch is current against main. Shipm
|
|
|
34
34
|
|
|
35
35
|
### Step 1: Get Active Checkpoint
|
|
36
36
|
|
|
37
|
-
Use MCP `get_current_task`
|
|
37
|
+
Read local state `.codebyplan/state/checkpoints/<id>.json` to get the active checkpoint; on miss run `npx codebyplan sync` once and re-read. Use MCP `get_current_task` as documented break-glass when the state dir is absent and sync fails.
|
|
38
38
|
|
|
39
39
|
If no active checkpoint, show error and stop.
|
|
40
40
|
|
|
@@ -166,13 +166,12 @@ Only after both the local and remote git delete above succeed, run a conditional
|
|
|
166
166
|
|
|
167
167
|
> Lifecycle contract: see [[supabase-branch-lifecycle]].
|
|
168
168
|
|
|
169
|
-
-
|
|
170
|
-
- Call `mcp__supabase__list_branches` with the resolved `project_id`.
|
|
169
|
+
- Call `mcp__supabase__list_branches` with `project_id: rrvtrumtkhrsbhcyrwvf`.
|
|
171
170
|
- Scan the returned list for an entry whose `name` exactly equals `$BRANCH`.
|
|
172
171
|
- If found: call `mcp__supabase__delete_branch` with its `branch_id`. Record the branch name in `SUPABASE_BRANCHES_DELETED[]`.
|
|
173
172
|
- If not found: no-op silently — the GitHub integration may have already removed it on PR close; not-found is success, NOT an error.
|
|
174
173
|
- 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.
|
|
175
|
-
- Never delete the
|
|
174
|
+
- Never delete the parent project `rrvtrumtkhrsbhcyrwvf` itself or any persistent/production branch.
|
|
176
175
|
|
|
177
176
|
Accumulate all Supabase branch names removed across the loop in `SUPABASE_BRANCHES_DELETED`.
|
|
178
177
|
|
|
@@ -199,17 +198,15 @@ git push origin --delete "$FEAT_BRANCH"
|
|
|
199
198
|
|
|
200
199
|
After the feat branch git delete, run the same conditional Supabase teardown for `$FEAT_BRANCH`:
|
|
201
200
|
|
|
202
|
-
-
|
|
203
|
-
- Call `mcp__supabase__list_branches` with the resolved `project_id`.
|
|
201
|
+
- Call `mcp__supabase__list_branches` with `project_id: rrvtrumtkhrsbhcyrwvf`.
|
|
204
202
|
- Scan for an entry whose `name` exactly equals `$FEAT_BRANCH`.
|
|
205
203
|
- If found: call `mcp__supabase__delete_branch` with its `branch_id`. Add `$FEAT_BRANCH` to `SUPABASE_BRANCHES_DELETED[]`.
|
|
206
204
|
- If not found: no-op silently — idempotent, not-found is success.
|
|
207
205
|
- 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.
|
|
208
|
-
- Never delete the branch where `is_default` is true in the `list_branches` response (the production/parent project branch) or any other persistent/long-lived branch.
|
|
209
206
|
|
|
210
207
|
### Step 10: Save Shipment Results and Summary
|
|
211
208
|
|
|
212
|
-
Update checkpoint context via MCP `update_checkpoint
|
|
209
|
+
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):
|
|
213
210
|
|
|
214
211
|
```
|
|
215
212
|
context.shipment: {
|
|
@@ -284,7 +281,7 @@ Auto-trigger `/cbp-checkpoint-complete`.
|
|
|
284
281
|
## Integration
|
|
285
282
|
|
|
286
283
|
- **Triggered by**: `/cbp-checkpoint-check` (auto, when ready)
|
|
287
|
-
- **Reads**: MCP `get_current_task
|
|
288
|
-
- **Writes**:
|
|
284
|
+
- **Reads**: Local state `.codebyplan/state/checkpoints/<id>.json`; on miss `npx codebyplan sync` once; MCP `get_current_task` as documented break-glass when the state dir is absent and sync fails. Also reads `.codebyplan/git.json` (`branch_config`), `.codebyplan/server.json` (`auto_push_enabled`), `.codebyplan/shipment.json` (`shipment`), git branches.
|
|
285
|
+
- **Writes**: `codebyplan checkpoint update <id> --context '...'` (CLI write-through) for context.shipment; MCP `update_checkpoint` break-glass. Note: `mcp__supabase__list_branches` / `mcp__supabase__delete_branch` calls in Steps 8–9 are Supabase MCP (not CodeByPlan MCP) and are unchanged.
|
|
289
286
|
- **Calls**: `/cbp-merge-main` (Step 0, sync); `/cbp-ship-main` (Step 5, branch promotion to main); `/cbp-ship` (Step 7, runtime surface deploy + verification)
|
|
290
287
|
- **Triggers**: `/cbp-checkpoint-complete` (auto, after successful shipment)
|