codebyplan 1.13.43 → 1.13.45

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 (42) hide show
  1. package/dist/cli.js +5079 -1556
  2. package/package.json +1 -1
  3. package/templates/agents/cbp-task-check.md +1 -3
  4. package/templates/agents/cbp-task-planner.md +8 -6
  5. package/templates/github-workflows/publish.yml +93 -21
  6. package/templates/hooks/cbp-auto-test-hooks.sh +1 -0
  7. package/templates/hooks/cbp-e2e-spec-patterns.sh +100 -0
  8. package/templates/hooks/cbp-lint-format-on-edit.sh +1 -0
  9. package/templates/hooks/cbp-maestro-yaml-validate.sh +1 -0
  10. package/templates/hooks/cbp-pre-commit-quality-gate.sh +1 -0
  11. package/templates/hooks/cbp-statusline.sh +0 -0
  12. package/templates/hooks/cbp-subagent-statusline.sh +0 -0
  13. package/templates/hooks/cbp-test-coverage-gate.sh +1 -0
  14. package/templates/hooks/cbp-test-hooks.sh +1 -0
  15. package/templates/hooks/hooks.json +4 -0
  16. package/templates/hooks/verify-parity.sh +20 -0
  17. package/templates/rules/parallel-waves.md +8 -3
  18. package/templates/rules/scope-vocabulary.md +4 -3
  19. package/templates/settings.project.base.json +22 -0
  20. package/templates/skills/cbp-build-cc-claude-file/SKILL.md +11 -1
  21. package/templates/skills/cbp-build-cc-claude-file/scripts/validate-claude-file.sh +72 -0
  22. package/templates/skills/cbp-build-cc-mode/SKILL.md +12 -16
  23. package/templates/skills/cbp-build-cc-rule/SKILL.md +11 -1
  24. package/templates/skills/cbp-build-cc-rule/scripts/validate-rule.sh +69 -0
  25. package/templates/skills/cbp-build-cc-settings/SKILL.md +2 -2
  26. package/templates/skills/cbp-build-cc-settings/scripts/validate-settings.sh +67 -0
  27. package/templates/skills/cbp-checkpoint-create/SKILL.md +12 -4
  28. package/templates/skills/cbp-checkpoint-end/SKILL.md +19 -11
  29. package/templates/skills/cbp-git-commit/SKILL.md +10 -12
  30. package/templates/skills/cbp-git-worktree-create/SKILL.md +7 -48
  31. package/templates/skills/cbp-git-worktree-remove/SKILL.md +23 -40
  32. package/templates/skills/cbp-map-architecture/SKILL.md +1 -0
  33. package/templates/skills/cbp-merge-main/SKILL.md +21 -26
  34. package/templates/skills/cbp-refresh-arch-map/SKILL.md +1 -0
  35. package/templates/skills/cbp-round-check/SKILL.md +37 -36
  36. package/templates/skills/cbp-round-execute/SKILL.md +9 -3
  37. package/templates/skills/cbp-session-end/SKILL.md +27 -47
  38. package/templates/skills/cbp-session-start/SKILL.md +35 -51
  39. package/templates/skills/cbp-standalone-task-start/SKILL.md +10 -19
  40. package/templates/skills/cbp-supabase-migrate/SKILL.md +24 -27
  41. package/templates/skills/cbp-task-start/SKILL.md +9 -21
  42. package/templates/skills/cbp-task-testing/SKILL.md +18 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.13.43",
3
+ "version": "1.13.45",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -82,9 +82,7 @@ Review all QA items across all rounds:
82
82
  - **Auto items**: Verify all passed (build, lint, types, tests)
83
83
  - **Default items**: Verify all resolved (pass or skipped with reason)
84
84
 
85
- **E2E pass vs skipped distinction**: When reading `auto_qa.items[]` for `check: 'e2e'`, do NOT conflate `status: 'pass'` with `status: 'skipped'`. A spec that ran with `passed === 0 && skipped > 0` for any path touching `files_changed` is a hard fail, not a pass verdict text MUST explicitly call this out: "E2E spec authored but assertions did not execute (skip-gated)." Do NOT issue a READY verdict on a zero-assertion e2e run; route to a fix round per `rules/e2e-mandatory.md`.
86
-
87
- **Committed-screenshot check**: For any round where `round.context.e2e_eligible[]` is non-empty, verify `round.context.e2e_gallery[]` is non-empty. Refuse a READY verdict when it is empty — verdict text: "E2E ran but produced zero committed screenshots — open a fix round per `rules/e2e-mandatory.md` § Committed-Screenshot Enforcement." Sole exception: when `vscode-test` is the ONLY eligible framework, an empty `e2e_gallery[]` is allowed (SD-3, behavior-only extensions).
85
+ **E2E deterministic gate**: For each round where `round.context.e2e_eligible[]` is non-empty, run `codebyplan e2e verify-round --round-id <round_id> --task-id <task_id>`. Exit 0 = pass. Exit 1 = hard-fail — refuse a READY verdict and surface the stdout JSON's `failed_checks[]` verbatim in the verdict text. The CLI deterministically evaluates all three e2e hard-fails that were previously judged manually: `e2e_eligible_skipped` (eligible framework with no specialist output and no valid skip reason), `zero_assertion_run` (`passed === 0 && skipped > 0` on a path touching `files_changed` — "E2E spec authored but assertions did not execute (skip-gated)"), and `empty_gallery` (eligible UI-touching run with zero committed screenshots, per `rules/e2e-mandatory.md` § Committed-Screenshot Enforcement; the sole vscode-test-only exception is honored by the CLI). On any exit-1, route to a fix round per `rules/e2e-mandatory.md`.
88
86
 
89
87
  List any pending or failed items. Determine if they are blockers.
90
88
 
@@ -552,13 +552,15 @@ plan.waves:
552
552
 
553
553
  If `files_to_modify[]` contains ≤5 files across a single app, skip decomposition and emit a single wave or omit `waves[]` entirely (single-wave default in `round-execute` handles this gracefully).
554
554
 
555
- **Verification checklist** before finalising waves:
555
+ **Verification** before finalising waves — invariants I (disjoint files), II (acyclic `depends_on` DAG), and III (3–15 files per wave, with the small-plan lower-bound exemption) are deterministic set/graph checks; run the validator instead of self-checking them in prose:
556
556
 
557
- - [ ] Every file in `files_to_modify[]` appears in exactly one wave
558
- - [ ] `depends_on[]` forms a DAG (no cycles)
559
- - [ ] UI-bearing waves have `frontend-design` + `frontend-a11y` in `skill_preloads[]`
560
- - [ ] Each wave has ≥3 files (if not, merge into sibling wave)
561
- - [ ] No wave has >15 files (if so, apply the proximity-split algorithm)
557
+ ```bash
558
+ printf '%s' "$PLAN_JSON" | codebyplan validate-waves --json
559
+ ```
560
+
561
+ (`$PLAN_JSON` is the `{ "waves": [...] }` structure; pass a file path as the first argument instead of stdin if preferred.) Exit 0 = invariants I–III satisfied. Exit non-zero = one or more violations — the `--json` `violations[]` array names the failing invariant (`I`/`II`/`III`) and offending wave/file; fix the decomposition and re-run before emitting the plan. The validator does NOT check invariant IV (UI skill preloads) — that remains a manual step:
562
+
563
+ - [ ] UI-bearing waves have `frontend-design` + `frontend-a11y` in `skill_preloads[]` (invariant IV — not covered by `validate-waves`)
562
564
 
563
565
  ### Phase 6: Build Context Summary
564
566
 
@@ -2,14 +2,16 @@
2
2
  #
3
3
  # This workflow publishes the codebyplan npm package on every merge to main
4
4
  # where the committed package.json version exceeds the version currently on npm.
5
- # No release PR, no conventional-commit parsing the version committed in the
6
- # feat branch is the version that ships.
5
+ # It also auto-publishes prerelease versions (e.g. 1.14.0-beta.1) from feat/**
6
+ # branches to a scoped dist-tag (e.g. --tag beta) — see the beta channel docs
7
+ # for the full workflow. No release PR, no conventional-commit parsing — the
8
+ # version committed in the feat branch is the version that ships.
7
9
  #
8
10
  # Two values a consuming repo must adjust:
9
11
  # 1. paths: — set to the package directory whose package.json drives versioning
10
12
  # (current value: 'packages/codebyplan-package/**')
11
13
  # 2. npm view <package-name> — replace `codebyplan` with the actual package name
12
- # in the "Check version vs published" step
14
+ # in the "Check version vs published" and exact-version check steps
13
15
  #
14
16
  # Everything else (OIDC auth, pnpm 10.12.4, Node 20, build:npm → publish) is
15
17
  # intentionally generic and works as-is for any single-package npm publish.
@@ -20,6 +22,7 @@ on:
20
22
  push:
21
23
  branches:
22
24
  - main
25
+ - "feat/**"
23
26
  paths:
24
27
  - "packages/codebyplan-package/**"
25
28
  workflow_dispatch:
@@ -40,6 +43,7 @@ jobs:
40
43
  outputs:
41
44
  should_publish: ${{ steps.check.outputs.should_publish }}
42
45
  version: ${{ steps.check.outputs.version }}
46
+ dist_tag: ${{ steps.check.outputs.dist_tag }}
43
47
  steps:
44
48
  - name: Checkout
45
49
  uses: actions/checkout@v4
@@ -54,27 +58,80 @@ jobs:
54
58
  fi
55
59
  echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
56
60
 
57
- # Pre-release versions (e.g. 1.2.3-beta.1) must never auto-publish to the
58
- # 'latest' dist-tag sort -V is not semver-aware for pre-release suffixes,
59
- # and this workflow always publishes to latest. Publish pre-releases
60
- # manually with `npm publish --tag <pre-id>`. The version core (X.Y.Z) has
61
- # no hyphen, so any '-' marks a pre-release.
61
+ # Compute dist-tag from version:
62
+ # prerelease (contains hyphen) extract id between '-' and first '.'
63
+ # e.g. 1.14.0-beta.1 "beta"; 1.14.0-rc.1 "rc"
64
+ # stable "latest"
62
65
  case "$VERSION" in
63
66
  *-*)
64
- echo "VERSION=${VERSION} is a pre-release — skipping auto-publish."
65
- echo "should_publish=false" >> "$GITHUB_OUTPUT"
66
- exit 0
67
+ PRE_ID="${VERSION#*-}"
68
+ DIST_TAG="${PRE_ID%%.*}"
69
+ ;;
70
+ *)
71
+ DIST_TAG="latest"
67
72
  ;;
68
73
  esac
74
+ echo "dist_tag=${DIST_TAG}" >> "$GITHUB_OUTPUT"
69
75
 
70
- # Manual dispatch: always publish (recovery / forced re-publish path).
76
+ # ── workflow_dispatch ──────────────────────────────────────────────
77
+ # Manual trigger: always publish (recovery / forced re-publish path).
78
+ # Uses the computed dist_tag so manual prerelease publishes are tagged
79
+ # correctly.
71
80
  if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
72
- echo "Manual dispatch — skipping gt check, will publish."
81
+ echo "Manual dispatch — will publish (dist-tag: ${DIST_TAG})."
73
82
  echo "should_publish=true" >> "$GITHUB_OUTPUT"
74
83
  exit 0
75
84
  fi
76
85
 
77
- # Push path: compare committed version against the npm registry.
86
+ # ── feat/** push ───────────────────────────────────────────────────
87
+ # C4 guard: three conditions must hold before publishing from feat/**:
88
+ # (1) prerelease-required: stable versions on feat/** are skipped.
89
+ # (2) exact-version check: if this exact version is already on npm, skip.
90
+ # (3) path filter: already enforced by on.push.paths above.
91
+ case "${{ github.ref }}" in
92
+ refs/heads/feat/*)
93
+ case "$VERSION" in
94
+ *-*)
95
+ # Prerelease — check if this exact version already exists on npm.
96
+ EXACT_OUT=$(npm view "codebyplan@${VERSION}" version 2>&1)
97
+ EXACT_EXIT=$?
98
+ if [ "$EXACT_EXIT" -eq 0 ]; then
99
+ echo "VERSION=${VERSION} already published — skipping (exact-version guard)."
100
+ echo "should_publish=false" >> "$GITHUB_OUTPUT"
101
+ exit 0
102
+ fi
103
+ if echo "$EXACT_OUT" | grep -q "E404"; then
104
+ echo "VERSION=${VERSION} not yet published — will publish (dist-tag: ${DIST_TAG})."
105
+ echo "should_publish=true" >> "$GITHUB_OUTPUT"
106
+ exit 0
107
+ fi
108
+ # Non-E404 failure: transient registry error — abort to avoid
109
+ # a duplicate publish in a degraded state.
110
+ echo "ERROR: npm view codebyplan@${VERSION} failed (exit ${EXACT_EXIT}): ${EXACT_OUT}"
111
+ exit 1
112
+ ;;
113
+ *)
114
+ # C4 prerelease-required gate: stable versions on feat/** skip.
115
+ echo "VERSION=${VERSION} is not a prerelease — feat/** branches require a prerelease version (e.g. run 'codebyplan bump --prerelease beta'). Skipping."
116
+ echo "should_publish=false" >> "$GITHUB_OUTPUT"
117
+ exit 0
118
+ ;;
119
+ esac
120
+ ;;
121
+ esac
122
+
123
+ # ── main push ──────────────────────────────────────────────────────
124
+ # Prerelease versions must never auto-publish to latest from main.
125
+ # The version core (X.Y.Z) has no hyphen, so any '-' marks a prerelease.
126
+ case "$VERSION" in
127
+ *-*)
128
+ echo "VERSION=${VERSION} is a pre-release — skipping auto-publish on main (use a feat/** branch or workflow_dispatch)."
129
+ echo "should_publish=false" >> "$GITHUB_OUTPUT"
130
+ exit 0
131
+ ;;
132
+ esac
133
+
134
+ # Stable on main: compare committed version against the npm registry.
78
135
  # Distinguish "never published" (E404 → first publish) from a transient
79
136
  # registry/network error (abort rather than blindly publish).
80
137
  NPM_OUT=$(npm view codebyplan version 2>&1)
@@ -88,7 +145,9 @@ jobs:
88
145
  echo "ERROR: npm view failed (exit ${NPM_EXIT}): ${NPM_OUT}"
89
146
  exit 1
90
147
  fi
91
- PUBLISHED="$NPM_OUT"
148
+ # Extract the first semver-looking line so a registry deprecation/advisory
149
+ # notice in NPM_OUT cannot corrupt the sort -V comparison below.
150
+ PUBLISHED=$(printf '%s\n' "$NPM_OUT" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
92
151
 
93
152
  # sort -V: version-aware sort. If VERSION sorts AFTER PUBLISHED, it is greater.
94
153
  GREATER=$(printf '%s\n%s\n' "$PUBLISHED" "$VERSION" | sort -V | tail -n1)
@@ -147,7 +206,7 @@ jobs:
147
206
  working-directory: packages/codebyplan-package
148
207
  run: |
149
208
  echo "dry_run=true — skipping actual publish."
150
- echo "Would publish: codebyplan@${{ needs.check-version.outputs.version }}"
209
+ echo "Would publish: codebyplan@${{ needs.check-version.outputs.version }} (dist-tag: ${{ needs.check-version.outputs.dist_tag }})"
151
210
 
152
211
  # No NODE_AUTH_TOKEN: npm >= 11.5.1 exchanges the GitHub OIDC token for a
153
212
  # short-lived publish token. Requires a Trusted Publisher configured for
@@ -158,10 +217,14 @@ jobs:
158
217
  # source repository visibility: private" otherwise). This works for both
159
218
  # public and private repos. If your source repo is PUBLIC and you want
160
219
  # supply-chain attestation, add `--provenance` to the command below.
220
+ #
221
+ # --tag: routes stable publishes to "latest" and prerelease publishes to
222
+ # the prerelease id (e.g. "beta", "rc"). C1 guarantee: betas never land on
223
+ # "latest" because dist_tag is computed from the version string.
161
224
  - name: Publish to npm
162
225
  if: ${{ github.event_name != 'workflow_dispatch' || inputs.dry_run != true }}
163
226
  working-directory: packages/codebyplan-package
164
- run: npm publish --access public
227
+ run: npm publish --access public --tag ${{ needs.check-version.outputs.dist_tag }}
165
228
 
166
229
  tag-and-release:
167
230
  name: Tag + GitHub release
@@ -199,9 +262,18 @@ jobs:
199
262
  if gh release view "${TAG}" > /dev/null 2>&1; then
200
263
  echo "GitHub release ${TAG} already exists — skipping."
201
264
  else
202
- gh release create "${TAG}" \
203
- --title "codebyplan v${VERSION}" \
204
- --notes "codebyplan v${VERSION} published to npm: https://www.npmjs.com/package/codebyplan/v/${VERSION} (install: npm install -g codebyplan@${VERSION})" \
205
- --latest
265
+ # C1 guard: prerelease versions get --prerelease (NOT --latest);
266
+ # stable versions get --latest (existing behaviour preserved).
267
+ if echo "${VERSION}" | grep -q "-"; then
268
+ gh release create "${TAG}" \
269
+ --title "codebyplan v${VERSION}" \
270
+ --notes "codebyplan v${VERSION} — published to npm: https://www.npmjs.com/package/codebyplan/v/${VERSION} (install: npm install -g codebyplan@${VERSION})" \
271
+ --prerelease
272
+ else
273
+ gh release create "${TAG}" \
274
+ --title "codebyplan v${VERSION}" \
275
+ --notes "codebyplan v${VERSION} — published to npm: https://www.npmjs.com/package/codebyplan/v/${VERSION} (install: npm install -g codebyplan@${VERSION})" \
276
+ --latest
277
+ fi
206
278
  echo "Created GitHub release ${TAG}."
207
279
  fi
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # @hook: PostToolUse Edit|Write
3
4
  # Hook: PostToolUse for Edit|Write on hook files
4
5
  # Purpose: Run test-hooks.sh when a plugin hook file is modified.
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # @scope: org-shared
3
+ # @event: PreToolUse
4
+ # @matcher: Edit|Write|MultiEdit
5
+ # Guard spec files against two patterns banned by rules/e2e-mandatory.md and
6
+ # context/testing/e2e.md:
7
+ #
8
+ # 1. In-spec env skip gates — test.skip(!process.env.X, ...) bypasses preflight,
9
+ # produces zero-assertion runs, and hides missing env vars behind a green check.
10
+ # (rules/e2e-mandatory.md § No In-Spec Env-Skip Gate)
11
+ #
12
+ # 2. waitForLoadState("networkidle") locators — networkidle is deprecated in
13
+ # Playwright and causes flaky / hanging tests; use load or domcontentloaded
14
+ # instead. (context/testing/e2e.md § locator hygiene)
15
+ #
16
+ # Only fires on spec files: file_path ending in .spec.ts OR containing /e2e/
17
+ # or starting with e2e/. All other file paths exit 0 immediately.
18
+ #
19
+ # Exit codes:
20
+ # 0 — no violation (or not a spec file)
21
+ # 2 — violation found; message on stderr
22
+
23
+ set -euo pipefail
24
+
25
+ INPUT_JSON="$(cat)"
26
+ TOOL_NAME="$(echo "$INPUT_JSON" | jq -r '.tool_name // empty')"
27
+ FILE_PATH="$(echo "$INPUT_JSON" | jq -r '.tool_input.file_path // empty')"
28
+
29
+ # ------------------------------------------------------------------
30
+ # Fast-path: only enforce on spec / e2e files
31
+ # ------------------------------------------------------------------
32
+ is_spec=0
33
+ case "$FILE_PATH" in
34
+ *.spec.ts) is_spec=1 ;;
35
+ */e2e/*) is_spec=1 ;;
36
+ e2e/*) is_spec=1 ;;
37
+ esac
38
+
39
+ [ "$is_spec" -eq 0 ] && exit 0
40
+
41
+ # ------------------------------------------------------------------
42
+ # Extract content depending on tool type
43
+ # ------------------------------------------------------------------
44
+ NEW_CONTENT=""
45
+ if [ "$TOOL_NAME" = "Write" ]; then
46
+ NEW_CONTENT="$(echo "$INPUT_JSON" | jq -r '.tool_input.content // empty')"
47
+ elif [ "$TOOL_NAME" = "Edit" ]; then
48
+ NEW_CONTENT="$(echo "$INPUT_JSON" | jq -r '.tool_input.new_string // empty')"
49
+ elif [ "$TOOL_NAME" = "MultiEdit" ]; then
50
+ NEW_CONTENT="$(echo "$INPUT_JSON" | jq -r '[.tool_input.edits[].new_string // ""] | join("\n")')"
51
+ fi
52
+
53
+ [ -z "$NEW_CONTENT" ] && exit 0
54
+
55
+ # ------------------------------------------------------------------
56
+ # Check 1: in-spec env-skip gate (test.skip with process.env)
57
+ # Pattern: test.skip( followed by optional whitespace and optional !
58
+ # then process.env
59
+ # ------------------------------------------------------------------
60
+ if echo "$NEW_CONTENT" | grep -qE 'test\.skip\([[:space:]]*!?process\.env'; then
61
+ cat >&2 <<'EOF'
62
+ [hook] cbp-e2e-spec-patterns: in-spec env skip gate detected
63
+
64
+ Pattern: test.skip(!process.env.X, ...) or test.skip(process.env.X, ...)
65
+
66
+ This pattern is banned by rules/e2e-mandatory.md § "No In-Spec Env-Skip Gate":
67
+ "Spec files MUST NOT contain in-spec env skip gates such as
68
+ test.skip(!process.env.X, ...). They bypass preflight, produce
69
+ zero-assertion runs, and hide missing env vars behind a green check."
70
+
71
+ The only valid mechanism for env-conditional skipping is pre-flight
72
+ (context/testing/e2e.md Step 6.5.1). Configure missing env vars in
73
+ .codebyplan/e2e.json credential_vars and let the preflight block the run.
74
+
75
+ EOF
76
+ exit 2
77
+ fi
78
+
79
+ # ------------------------------------------------------------------
80
+ # Check 2: waitForLoadState("networkidle") locator
81
+ # networkidle is deprecated and causes flaky / hanging tests.
82
+ # ------------------------------------------------------------------
83
+ if echo "$NEW_CONTENT" | grep -qE 'waitForLoadState\(["'"'"']networkidle["'"'"']\)'; then
84
+ cat >&2 <<'EOF'
85
+ [hook] cbp-e2e-spec-patterns: waitForLoadState("networkidle") detected
86
+
87
+ This locator is banned by context/testing/e2e.md locator hygiene rules.
88
+ "networkidle" is deprecated in Playwright and causes flaky or hanging tests.
89
+
90
+ Use one of the stable alternatives:
91
+ await page.waitForLoadState("load"); // waits for load event
92
+ await page.waitForLoadState("domcontentloaded"); // waits for DOM ready
93
+ await page.waitForURL("..."); // waits for navigation
94
+ await expect(locator).toBeVisible(); // waits for element
95
+
96
+ EOF
97
+ exit 2
98
+ fi
99
+
100
+ exit 0
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # @hook: PostToolUse Edit|Write
3
4
  # Hook: Auto-format and auto-fix lint on edited source files
4
5
  # Purpose: Continuous Prettier + ESLint --fix after every Edit/Write.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # @scope: org-shared
2
3
  # @event: PreToolUse
3
4
  # @matcher: Edit|Write|MultiEdit
4
5
  # Validate maestro flow YAML against the installed CLI's accepted property set.
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # @hook: PreToolUse Bash
3
4
  # Hook: PreToolUse for Bash (git commit commands)
4
5
  # Purpose: Block git commits that violate greenfield-lint-zero-warnings,
File without changes
File without changes
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # @hook: PreToolUse Bash
3
4
  # Hook: PreToolUse for Bash (git commit commands)
4
5
  # Purpose: Block git commits when new source files lack test companions
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # @hook: NOT-A-HOOK (test suite for plugin hooks; invoked by cbp-auto-test-hooks.sh)
3
4
  # Purpose: Test suite for plugin's shipped hooks. Invoked by cbp-auto-test-hooks.sh whenever a
4
5
  # plugin hook file is edited. Not a PreToolUse/PostToolUse/Notification hook itself —
@@ -28,6 +28,10 @@
28
28
  {
29
29
  "type": "command",
30
30
  "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/cbp-maestro-yaml-validate.sh"
31
+ },
32
+ {
33
+ "type": "command",
34
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/cbp-e2e-spec-patterns.sh"
31
35
  }
32
36
  ]
33
37
  },
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # @scope: org-shared
3
+ # @event: SessionStart
4
+ # Scope + sibling-parity sweep at session start. Calls `codebyplan claude verify-parity --warn-only`.
5
+ # Fires ONLY in the templates source monorepo (self-guards on templates/ presence); no-op for consumers.
6
+ # Always exits 0 — warn-mode, non-blocking.
7
+
8
+ set -u
9
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
10
+ [ -z "$REPO_ROOT" ] && exit 0
11
+ [ -d "$REPO_ROOT/packages/codebyplan-package/templates" ] || exit 0
12
+
13
+ # Prefer the installed binary (fast); fall back to npx for fresh checkouts /
14
+ # environments where the workspace bin is not on PATH (matches cbp-mcp-round-sync.sh).
15
+ if command -v codebyplan >/dev/null 2>&1; then
16
+ codebyplan claude verify-parity --warn-only 2>&1 || true
17
+ else
18
+ npx codebyplan claude verify-parity --warn-only 2>&1 || true
19
+ fi
20
+ exit 0
@@ -12,7 +12,7 @@ Authoritative expansion of `cbp-task-planner` Phase 5.6. The planner reads this
12
12
 
13
13
  ## Wave Schema
14
14
 
15
- Each entry in `plan.waves[]` carries these fields (source: `.claude/agents/cbp-task-planner.md:540`):
15
+ Each entry in `plan.waves[]` carries these fields (source: `.claude/agents/cbp-task-planner.md` Phase 5.6 "Output" block):
16
16
 
17
17
  ```yaml
18
18
  - name: string # short identifier, e.g. "web-ui", "backend", "db"
@@ -34,7 +34,7 @@ Each entry in `plan.waves[]` carries these fields (source: `.claude/agents/cbp-t
34
34
  - Above 15: apply the proximity-split algorithm below.
35
35
  - Sole exception — trivially small plans are exempt from the lower bound: a plan with fewer than 3 total files uses one single wave, and a single-app plan with ≤5 total files MAY skip decomposition entirely (one wave, or `waves[]` omitted — see `cbp-task-planner` Phase 5.6). Zero waves (omitted `waves[]`) trivially satisfies this invariant.
36
36
 
37
- **(IV) UI skill preloads** — for each wave whose `files[]` contains UI-bearing paths (`*.tsx`, `*.jsx`, `*.scss`, etc.), add `"frontend-design"` and `"frontend-a11y"` to `skill_preloads[]` in that order (source: `.claude/agents/cbp-task-planner.md:532`).
37
+ **(IV) UI skill preloads** — for each wave whose `files[]` contains UI-bearing paths (`*.tsx`, `*.jsx`, `*.scss`, etc.), add `"frontend-design"` and `"frontend-a11y"` to `skill_preloads[]` in that order (source: `.claude/agents/cbp-task-planner.md` Phase 5.6 step "Populate `skill_preloads[]`").
38
38
 
39
39
  ## Proximity-Split Algorithm
40
40
 
@@ -52,8 +52,13 @@ When a wave would exceed 15 files, split it in priority order:
52
52
 
53
53
  When no cross-app or cross-concern independence is found, the planner emits one wave covering all files. The 15-file cap applies to this single wave — if the total exceeds 15, apply the proximity-split algorithm even though the original decomposition was a single wave.
54
54
 
55
+ ## Enforcement
56
+
57
+ Invariants I (disjoint files), II (acyclic `depends_on` DAG), and III (3–15 files per wave, with the small-plan lower-bound exemption) are enforced deterministically by `codebyplan validate-waves [--json]` (`packages/codebyplan-package/src/lib/validate-waves.ts`). The planner runs it at Phase 5.6 against the `{ "waves": [...] }` structure (file-path or stdin input); a non-zero exit lists the violated invariant + offending wave/file. Invariant IV (UI skill preloads) is out of the validator's scope and stays a planner manual step.
58
+
55
59
  ## Cross-References
56
60
 
57
- - `agents/cbp-task-planner.md` Phase 5.6 — consumer of this rule; steps 1–6 and verification checklist.
61
+ - `agents/cbp-task-planner.md` Phase 5.6 — consumer of this rule; steps 1–6 and the `validate-waves` verification call.
62
+ - `packages/codebyplan-package/src/lib/validate-waves.ts` — deterministic enforcement of invariants I–III.
58
63
  - `agents/cbp-round-executor.md` Step 2.6 — wave-mode skill preloads.
59
64
  - `skills/cbp-round-execute/SKILL.md` Step 3 — per-wave executor dispatch.
@@ -4,7 +4,7 @@ scope: org-shared
4
4
 
5
5
  # Scope Vocabulary
6
6
 
7
- Canonical scope-marker enum for `.claude/` files. Every CBP-managed agent, skill, rule, and hook script must declare exactly one `scope:` value from the enum below. The marker is enforced by three validators (cross-linked at the bottom of this file).
7
+ Canonical scope-marker enum for `.claude/` files. Every CBP-managed agent, skill, rule, and hook script must declare exactly one `scope:` value from the enum below. The marker is enforced by four validators (cross-linked at the bottom of this file).
8
8
 
9
9
  ## Enum values
10
10
 
@@ -57,8 +57,9 @@ Used by `.sh` hook scripts and `.sh` tooling under `.claude/`. Place the marker
57
57
 
58
58
  ## Enforcement
59
59
 
60
- Three validators enforce this enum. A file missing the marker (or carrying a non-enum value) fails validation:
60
+ Four validators enforce this enum. A file missing the marker (or carrying a non-enum value) fails validation:
61
61
 
62
- - `.claude/hooks/validate-structure-scope.sh` — warns on missing markers across all `.claude/` files at session start.
62
+ - `.claude/hooks/validate-structure-scope.sh` — warns when a `.claude/` file being edited (PreToolUse `Edit|Write`) lacks a scope marker; sourced by `validate-structure.sh`; it is NOT a session-start sweep.
63
+ - `codebyplan claude verify-parity` — sweeps all `.claude/{rules,skills,agents,hooks}` for missing/non-enum scope markers AND runs the sibling-parity check against `templates/`; invoked automatically at session start via `.claude/hooks/verify-parity.sh` (monorepo only); run manually with `--json` for CI.
63
64
  - `.claude/skills/cbp-build-cc-agent/scripts/validate-agent.sh` — blocks agent authoring when `scope:` is absent.
64
65
  - `.claude/skills/cbp-build-cc-skill/scripts/validate-skill.sh` — blocks skill authoring when `scope:` is absent.
@@ -84,6 +84,8 @@
84
84
  "Bash(npx codebyplan upgrade-auth:*)",
85
85
  "Bash(codebyplan config:*)",
86
86
  "Bash(npx codebyplan config:*)",
87
+ "Bash(codebyplan doctor:*)",
88
+ "Bash(npx codebyplan doctor:*)",
87
89
  "Bash(codebyplan branch:*)",
88
90
  "Bash(npx codebyplan branch:*)",
89
91
  "Bash(codebyplan ship:*)",
@@ -178,12 +180,20 @@
178
180
  "mcp__codebyplan__update_eslint_repo_config",
179
181
  "mcp__codebyplan__update_server_config",
180
182
  "mcp__codebyplan__update_task_template",
183
+ "Bash(codebyplan check:*)",
184
+ "Bash(npx codebyplan check:*)",
185
+ "Bash(codebyplan session:*)",
186
+ "Bash(npx codebyplan session:*)",
187
+ "Bash(codebyplan supabase:*)",
188
+ "Bash(npx codebyplan supabase:*)",
181
189
  "Bash(codebyplan whoami:*)",
182
190
  "Bash(npx codebyplan whoami:*)",
183
191
  "Bash(codebyplan resolve-worktree:*)",
184
192
  "Bash(npx codebyplan resolve-worktree:*)",
185
193
  "Bash(codebyplan version-status:*)",
186
194
  "Bash(npx codebyplan version-status:*)",
195
+ "Bash(codebyplan worktree:*)",
196
+ "Bash(npx codebyplan worktree:*)",
187
197
  "Bash(codebyplan statusline:*)",
188
198
  "Bash(npx codebyplan statusline:*)",
189
199
  "Bash(codebyplan ports:*)",
@@ -214,6 +224,18 @@
214
224
  "Bash(npx codebyplan bump:*)",
215
225
  "Bash(codebyplan scaffold-publish-workflow:*)",
216
226
  "Bash(npx codebyplan scaffold-publish-workflow:*)",
227
+ "Bash(codebyplan claude verify-parity:*)",
228
+ "Bash(npx codebyplan claude verify-parity:*)",
229
+ "Bash(codebyplan slug:*)",
230
+ "Bash(npx codebyplan slug:*)",
231
+ "Bash(codebyplan commit:*)",
232
+ "Bash(npx codebyplan commit:*)",
233
+ "Bash(codebyplan migration-collisions:*)",
234
+ "Bash(npx codebyplan migration-collisions:*)",
235
+ "Bash(codebyplan validate-waves:*)",
236
+ "Bash(npx codebyplan validate-waves:*)",
237
+ "Bash(codebyplan e2e:*)",
238
+ "Bash(npx codebyplan e2e:*)",
217
239
  "Bash(codebyplan arch-map:*)",
218
240
  "Bash(npx codebyplan arch-map:*)"
219
241
  ]
@@ -170,7 +170,17 @@ Other teams' CLAUDE.md files in the ancestor tree get picked up by default. Excl
170
170
 
171
171
  Managed policy CLAUDE.md cannot be excluded.
172
172
 
173
- ### Step 9 — Verify with `/memory`
173
+ ### Step 9 — Validate
174
+
175
+ Run the validator:
176
+
177
+ ```bash
178
+ bash "${CLAUDE_SKILL_DIR}/scripts/validate-claude-file.sh" "{path-to-claude-file}"
179
+ ```
180
+
181
+ It checks: at least one H1 heading present, `@path` imports all resolve relative to the file's directory (or repo root), line count (warns at 200, errors at 400), warns if `scope:` marker appears in frontmatter, warns if filename is neither `CLAUDE.md` nor `CLAUDE.local.md`.
182
+
183
+ ### Step 10 — Verify with `/memory`
174
184
 
175
185
  Run `/memory` to confirm the file is loaded. The list shows all CLAUDE.md, CLAUDE.local.md, and rules files in effect.
176
186
 
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ # @scope: org-shared
3
+ # Validate a CLAUDE.md or CLAUDE.local.md file.
4
+ # Usage: validate-claude-file.sh <path-to-claude-file>
5
+ # Exit 0 = valid, exit 1 = invalid (errors printed to stderr).
6
+
7
+ set -euo pipefail
8
+
9
+ FILE="${1:?Usage: validate-claude-file.sh <path-to-claude-file>}"
10
+
11
+ if [ ! -f "$FILE" ]; then
12
+ echo "ERROR: file not found: $FILE" >&2
13
+ exit 1
14
+ fi
15
+
16
+ errors=0
17
+ err() { echo " - $1" >&2; errors=$((errors + 1)); }
18
+
19
+ # Filename check — warn if not a known CLAUDE.md variant
20
+ fname=$(basename "$FILE")
21
+ if [ "$fname" != "CLAUDE.md" ] && [ "$fname" != "CLAUDE.local.md" ]; then
22
+ echo " WARN: unexpected filename '$fname' — expected CLAUDE.md or CLAUDE.local.md" >&2
23
+ fi
24
+
25
+ # At least one H1 heading must be present
26
+ if ! grep -qE '^# ' "$FILE"; then
27
+ err "missing H1 heading"
28
+ fi
29
+
30
+ # Line count thresholds
31
+ lines=$(wc -l < "$FILE")
32
+ if [ "$lines" -ge 400 ]; then
33
+ err "file is $lines lines (hard limit 400; split with @path imports or nested CLAUDE.md files)"
34
+ elif [ "$lines" -ge 200 ]; then
35
+ echo " WARN: file is $lines lines (recommended max 200; consider splitting)" >&2
36
+ fi
37
+
38
+ # @path import resolution — each @path token must resolve to an existing file.
39
+ # Tries both: relative to the file's directory, and relative to cwd (repo root).
40
+ filedir=$(dirname "$FILE")
41
+ while IFS= read -r imp; do
42
+ [ -z "$imp" ] && continue
43
+ ipath="${imp#@}"
44
+ if [ "${ipath:0:1}" = "/" ]; then
45
+ # absolute path
46
+ if [ ! -f "$ipath" ]; then
47
+ err "@import path '$ipath' does not exist"
48
+ fi
49
+ elif [ -f "$filedir/$ipath" ]; then
50
+ : # ok — resolves relative to the file's directory
51
+ elif [ -f "$ipath" ]; then
52
+ : # ok — resolves relative to cwd (repo root)
53
+ else
54
+ err "@import path '$ipath' does not exist (tried: $filedir/$ipath, $ipath)"
55
+ fi
56
+ done < <(grep -oE '^[[:space:]]*@[^[:space:]@]+' "$FILE" 2>/dev/null | sed 's/^[[:space:]]*//' 2>/dev/null || true)
57
+
58
+ # Warn if YAML frontmatter contains a scope: marker (CLAUDE.md is not a rule/skill/agent file)
59
+ if head -1 "$FILE" | grep -qE '^---[[:space:]]*$'; then
60
+ fm=$(awk '/^---[[:space:]]*$/{n++; next} n==1{print} n==2{exit}' "$FILE")
61
+ if grep -qE '^scope:[[:space:]]*' <<< "$fm"; then
62
+ echo " WARN: CLAUDE.md has a scope: frontmatter field — scope markers belong in rule/skill/agent files, not CLAUDE.md" >&2
63
+ fi
64
+ fi
65
+
66
+ if [ "$errors" -gt 0 ]; then
67
+ echo "validation FAILED ($errors issue(s)) for $FILE" >&2
68
+ exit 1
69
+ fi
70
+
71
+ echo "validation OK: $FILE"
72
+ exit 0