@thebassclef/lite 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +85 -4
  3. package/dist/cli.cjs +1610 -0
  4. package/dist/cli.js +1587 -0
  5. package/dist/index.cjs +4 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +4 -0
  8. package/package.json +51 -3
  9. package/substrate/.bassclef/lite-manifest.json +1794 -0
  10. package/substrate/.claude/agents/architect.md +70 -0
  11. package/substrate/.claude/agents/builder.md +114 -0
  12. package/substrate/.claude/agents/designer.md +156 -0
  13. package/substrate/.claude/agents/reviewer.md +88 -0
  14. package/substrate/.claude/hooks/artifact-ingestion-gate.sh +357 -0
  15. package/substrate/.claude/hooks/assert-verify-steering.sh +77 -0
  16. package/substrate/.claude/hooks/bassclef-source-config-validate.sh +215 -0
  17. package/substrate/.claude/hooks/bassclef-sync.sh +634 -0
  18. package/substrate/.claude/hooks/compound-noun-scrub.sh +292 -0
  19. package/substrate/.claude/hooks/kiss-expansion-inject.sh +69 -0
  20. package/substrate/.claude/hooks/plain-english-steering.sh +156 -0
  21. package/substrate/.claude/hooks/post-skill-friction-check.sh +177 -0
  22. package/substrate/.claude/hooks/post-skill-telemetry.sh +62 -0
  23. package/substrate/.claude/hooks/pre-build-gate.sh +511 -0
  24. package/substrate/.claude/hooks/pre-commit-gate.sh +452 -0
  25. package/substrate/.claude/hooks/session-end.sh +433 -0
  26. package/substrate/.claude/hooks/session-reflection.sh +303 -0
  27. package/substrate/.claude/hooks/skill-body-grade-gate.sh +219 -0
  28. package/substrate/.claude/hooks/skill-body-intent-drift.sh +107 -0
  29. package/substrate/.claude/hooks/skill-step-list-check.sh +171 -0
  30. package/substrate/.claude/hooks/state-validate.sh +271 -0
  31. package/substrate/.claude/hooks/substrate-clarity-gate.sh +1110 -0
  32. package/substrate/.claude/hooks/temperance-gate.sh +147 -0
  33. package/substrate/.claude/hooks/testing-tier-enforce.sh +233 -0
  34. package/substrate/.claude/hooks/turn-prose-grade-measure.sh +219 -0
  35. package/substrate/.claude/hooks/turn-prose-kiss-check.sh +434 -0
  36. package/substrate/.claude/hooks/vocabulary-migration-check.sh +171 -0
  37. package/substrate/.claude/hooks/whereami-utc-gate.sh +142 -0
  38. package/substrate/.claude/luminaries/alan-cooper.md +170 -0
  39. package/substrate/.claude/luminaries/alistair-cockburn.md +140 -0
  40. package/substrate/.claude/luminaries/amazon-pr-faq.md +34 -0
  41. package/substrate/.claude/luminaries/ash-maurya.md +121 -0
  42. package/substrate/.claude/luminaries/bill-buxton.md +210 -0
  43. package/substrate/.claude/luminaries/charles-sanders-peirce.md +150 -0
  44. package/substrate/.claude/luminaries/david-ogilvy.md +192 -0
  45. package/substrate/.claude/luminaries/don-norman.md +173 -0
  46. package/substrate/.claude/luminaries/edward-tufte.md +179 -0
  47. package/substrate/.claude/luminaries/eric-evans.md +160 -0
  48. package/substrate/.claude/luminaries/frederick-brooks.md +50 -0
  49. package/substrate/.claude/luminaries/gang-of-four.md +157 -0
  50. package/substrate/.claude/luminaries/jason-fried-dhh.md +46 -0
  51. package/substrate/.claude/luminaries/jesse-james-garrett.md +154 -0
  52. package/substrate/.claude/luminaries/john-ousterhout.md +94 -0
  53. package/substrate/.claude/luminaries/karl-popper.md +132 -0
  54. package/substrate/.claude/luminaries/kent-beck.md +168 -0
  55. package/substrate/.claude/luminaries/linus-torvalds.md +218 -0
  56. package/substrate/.claude/luminaries/martin-fowler.md +164 -0
  57. package/substrate/.claude/luminaries/michael-feathers.md +125 -0
  58. package/substrate/.claude/luminaries/michael-nygard.md +118 -0
  59. package/substrate/.claude/luminaries/robert-c-martin.md +164 -0
  60. package/substrate/.claude/luminaries/saltzer-schroeder.md +194 -0
  61. package/substrate/.claude/luminaries/sophia-prater.md +193 -0
  62. package/substrate/.claude/luminaries/stephen-toulmin.md +131 -0
  63. package/substrate/.claude/rules/artifact-ingestion.md +179 -0
  64. package/substrate/.claude/rules/assert-only-after-verify.md +137 -0
  65. package/substrate/.claude/rules/blocked-items.md +146 -0
  66. package/substrate/.claude/rules/branching.md +28 -0
  67. package/substrate/.claude/rules/commit-conventions.md +22 -0
  68. package/substrate/.claude/rules/context-engineering.md +202 -0
  69. package/substrate/.claude/rules/context-management.md +85 -0
  70. package/substrate/.claude/rules/destructive-operations.md +69 -0
  71. package/substrate/.claude/rules/diagnosis.md +38 -0
  72. package/substrate/.claude/rules/guardrails.md +73 -0
  73. package/substrate/.claude/rules/identifier-leak-prevention.md +104 -0
  74. package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +98 -0
  75. package/substrate/.claude/rules/oo-ad-entry-point.md +117 -0
  76. package/substrate/.claude/rules/operator-facing-prose.md +196 -0
  77. package/substrate/.claude/rules/plain-english-discipline.md +154 -0
  78. package/substrate/.claude/rules/pr-body-shape.md +317 -0
  79. package/substrate/.claude/rules/pr-strategy.md +167 -0
  80. package/substrate/.claude/rules/sdlc-gates.md +149 -0
  81. package/substrate/.claude/rules/session-artifacts.md +236 -0
  82. package/substrate/.claude/rules/state-schema-validation.md +162 -0
  83. package/substrate/.claude/rules/whereami-load-bearing.md +202 -0
  84. package/substrate/.claude/rules/writing-craft-discipline.md +92 -0
  85. package/substrate/.claude/skills/build/SKILL.md +640 -0
  86. package/substrate/.claude/skills/decompose/SKILL.md +280 -0
  87. package/substrate/.claude/skills/diagnose/SKILL.md +297 -0
  88. package/substrate/.claude/skills/howdoi/SKILL.md +105 -0
  89. package/substrate/.claude/skills/ia-model/SKILL.md +108 -0
  90. package/substrate/.claude/skills/interaction-design/SKILL.md +112 -0
  91. package/substrate/.claude/skills/interpret-input/SKILL.md +180 -0
  92. package/substrate/.claude/skills/kiss/SKILL.md +449 -0
  93. package/substrate/.claude/skills/launch/SKILL.md +924 -0
  94. package/substrate/.claude/skills/lean-canvas/SKILL.md +332 -0
  95. package/substrate/.claude/skills/longrun/SKILL.md +426 -0
  96. package/substrate/.claude/skills/luminary/SKILL.md +481 -0
  97. package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
  98. package/substrate/.claude/skills/onboard-repo/SKILL.md +2190 -0
  99. package/substrate/.claude/skills/pattern-review/SKILL.md +99 -0
  100. package/substrate/.claude/skills/personas/SKILL.md +207 -0
  101. package/substrate/.claude/skills/promote/SKILL.md +283 -0
  102. package/substrate/.claude/skills/requirement/SKILL.md +98 -0
  103. package/substrate/.claude/skills/retro/SKILL.md +117 -0
  104. package/substrate/.claude/skills/riff/SKILL.md +114 -0
  105. package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
  106. package/substrate/.claude/skills/session-end/SKILL.md +309 -0
  107. package/substrate/.claude/skills/session-log/SKILL.md +299 -0
  108. package/substrate/.claude/skills/skills/SKILL.md +228 -0
  109. package/substrate/.claude/skills/spec/SKILL.md +105 -0
  110. package/substrate/.claude/skills/sprint/SKILL.md +392 -0
  111. package/substrate/.claude/skills/stage/SKILL.md +384 -0
  112. package/substrate/.claude/skills/state-a-problem/SKILL.md +185 -0
  113. package/substrate/.claude/skills/temperance/SKILL.md +108 -0
  114. package/substrate/.claude/skills/use-case/SKILL.md +417 -0
  115. package/substrate/.claude/skills/user-stories/SKILL.md +268 -0
  116. package/substrate/.claude/skills/value-prop/SKILL.md +251 -0
  117. package/substrate/.claude/skills/verify/SKILL.md +160 -0
  118. package/substrate/.claude/skills/whereami/SKILL.md +307 -0
  119. package/substrate/AGENTS.md +79 -0
  120. package/substrate/CLAUDE-lite.md +85 -0
  121. package/substrate/CODE_OF_CONDUCT.md +28 -0
  122. package/substrate/CONTRIBUTING.md +177 -0
  123. package/substrate/README.md +173 -0
  124. package/substrate/SECURITY.md +19 -0
  125. package/substrate/architecture/decisions/ADR-029-release-pipeline.md +79 -0
  126. package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
  127. package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
  128. package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
  129. package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
  130. package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
  131. package/substrate/lib/clean-artifacts-sweep.sh +112 -0
  132. package/substrate/lib/code-comment-discipline.sh +144 -0
  133. package/substrate/lib/composer-preflight.sh +459 -0
  134. package/substrate/lib/hook-inject.sh +255 -0
  135. package/substrate/lib/luminary-pick.sh +96 -0
  136. package/substrate/lib/output-discipline.sh +143 -0
  137. package/substrate/lib/rewrite-check.sh +214 -0
  138. package/substrate/lib/state.sh +1372 -0
  139. package/substrate/lib/telemetry.sh +205 -0
  140. package/substrate/lib/tier-check.sh +138 -0
  141. package/substrate/lib/tier-dependency-audit.sh +933 -0
  142. package/substrate/presence/install/bassclef-hook-connect.sh +178 -0
  143. package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
  144. package/substrate/presence/install/bassclef-sync.template.sh +2076 -0
  145. package/substrate/presence/install/schedule-auto-save.cron.sh +88 -0
  146. package/substrate/presence/install/schedule-auto-save.taskscheduler.md +122 -0
  147. package/substrate/scripts/aggregate-telemetry.sh +217 -0
  148. package/substrate/scripts/analyze-tier-dependencies.sh +239 -0
  149. package/substrate/scripts/generate-lite-manifest.sh +489 -0
  150. package/substrate/scripts/generate-tier-manifest.sh +28 -0
  151. package/substrate/scripts/intent-drift-check.sh +456 -0
  152. package/substrate/scripts/lite-manifest-drift-check.sh +146 -0
  153. package/substrate/scripts/render-lite-manifest-doc.sh +150 -0
  154. package/substrate/standards/bassclef-configs-schema.md +232 -0
  155. package/substrate/standards/bassclef-evolution.md +143 -0
  156. package/substrate/standards/bassclef-internal-jargon.md +244 -0
  157. package/substrate/standards/bassclef-managed-sentinel.md +96 -0
  158. package/substrate/standards/bassclef-source-config.md +228 -0
  159. package/substrate/standards/deferred-actions-schema.md +204 -0
  160. package/substrate/standards/docs-sync-allowlist.md +76 -0
  161. package/substrate/standards/graceful-exit.md +227 -0
  162. package/substrate/standards/hook-idempotency.md +102 -0
  163. package/substrate/standards/lite-manifest-schema-changes.md +75 -0
  164. package/substrate/standards/luminary-problem-patterns.md +481 -0
  165. package/substrate/standards/ogilvy-writing-rules.md +225 -0
  166. package/substrate/standards/opener-discipline.md +96 -0
  167. package/substrate/standards/operator-facing-prose-discipline.md +201 -0
  168. package/substrate/standards/project-directory-layout.md +276 -0
  169. package/substrate/standards/release-tagging.md +137 -0
  170. package/substrate/standards/sdlc-compliance.md +286 -0
  171. package/substrate/standards/secrets-lifecycle.md +210 -0
  172. package/substrate/standards/security-scanner-adapter.md +145 -0
  173. package/substrate/standards/skill-output-discipline.md +90 -0
  174. package/substrate/standards/state-spine.md +511 -0
  175. package/substrate/standards/tech-stack-config.md +109 -0
  176. package/substrate/standards/tier-dependency-analysis.md +167 -0
  177. package/substrate/standards/tier-runtime-deps/lite.md +57 -0
  178. package/substrate/standards/tier-tag-schema.md +155 -0
  179. package/substrate/standards/whereami-schema.md +301 -0
  180. package/substrate/standards/writing-guide.md +213 -0
  181. package/substrate/templates/deferred-action-template.md +45 -0
  182. package/substrate/templates/whereami-template.md +101 -0
@@ -0,0 +1,2190 @@
1
+ ---
2
+ tier: lite
3
+ name: onboard-repo
4
+ description: "Bootstrap bassclef framework in a new repo. Installs sync hook + scaffolds config + whereami + CLAUDE.md. Modes: default / --with-deploy-host / --with-secrets / --full / --greenfield-from-intent (cold-adopter magic demo from a paragraph — uses /launch --local)."
5
+ problem: "Setting up bassclef in a new repo takes an hour of trial. Missing directories and wrong hook wiring."
6
+ value: "One command bootstraps everything \u2014 sync hook and config file and whereami scaffold."
7
+ inputs: [Optional flags — --with-deploy-host or --with-secrets or --full]
8
+ outputs: [Installed bassclef-sync hook, Config scaffold, Whereami stub]
9
+ user_invocable: true
10
+ disable_model_invocation: false
11
+ requires:
12
+ cli: [git, gh]
13
+ network: true
14
+ modes: [default, --with-deploy-host, --with-secrets, --full, --greenfield-from-intent]
15
+ ---
16
+
17
+ # /onboard-repo — bassclef substrate adoption for a new repo
18
+
19
+ Brings a repo from "zero bassclef wiring" to "inherits full substrate
20
+ on desktop + mobile" in a single operator-gated session. Distinct from:
21
+
22
+ - `scripts/boot-bassclef.sh` — **machine-level** one-time setup (gh
23
+ auth, service account, launchd). Run once per machine.
24
+ - `/autonomous start` — **session-level** scaffolding (iteration goal,
25
+ phase state, risk register) for a repo that's ALREADY wired into
26
+ bassclef.
27
+
28
+ This skill sits between them: it's **repo-level** substrate adoption.
29
+ Run once per new consumer repo.
30
+
31
+ ## When to invoke
32
+
33
+ - Adding a new repo that will use bassclef
34
+ - Re-onboarding a repo that pre-dates the bassclef substrate
35
+ - Refreshing substrate wiring in an existing consumer (idempotent)
36
+
37
+ ## Prerequisites (fail fast if missing)
38
+
39
+ Different prereqs apply to Path A (standard onboarding) vs Path B
40
+ (`--greenfield-from-intent` cold-adopter magic demo). Check the path
41
+ the operator picked before validating.
42
+
43
+ ### Path A — standard onboarding
44
+
45
+ - `gh` authenticated (`gh auth status`)
46
+ - `claude` resolvable in PATH (`command -v claude`) — required for the
47
+ acceptance test at the end. If missing from a Zellij/tmux subpane,
48
+ move `eval "$(/opt/homebrew/bin/brew shellenv)"` from `~/.zprofile`
49
+ to `~/.zshenv` so non-login shells pick up homebrew's PATH.
50
+ - Target repo exists on GitHub under your org
51
+ - Target repo has a default branch (`main` preferred)
52
+ - Bassclef is cloned as a peer at `$BASSCLEF_DIR` (default `~/src/<your-org>/bassclef`, for
53
+ desktop `additionalDirectories` resolution)
54
+ - You are currently `cd`'d into the target repo's working directory
55
+ - Claude Code session was started with `--add-dir ~/src/sunj-labs/bassclef`
56
+ so this skill file is accessible
57
+
58
+ ### Path B — `--greenfield-from-intent` (cold-adopter magic demo)
59
+
60
+ - `claude` resolvable in PATH
61
+ - `git` available
62
+ - `python3` available (for the localhost gallery server)
63
+ - `jq` available (JSON parsing for bassclef-sync + LLM-pick adapter)
64
+ - `curl` available (bassclef-sync fallback + LLM-pick API calls)
65
+ - No separate API key needed. The skill uses whichever LLM your Claude Code session provides. Per the tier reframe (bassclef-web#190 + #193), tier is about catalog size, not credentials.
66
+ - You are currently `cd`'d into an **empty target directory** (or one
67
+ with only files Path B is allowed to overwrite — see "Working
68
+ directory convention" below)
69
+ - `gh` is NOT required (no GitHub repo gets created)
70
+ - A peer bassclef checkout at `~/src/sunj-labs/bassclef` is preferred
71
+ but not required (the thin-pointer dispatcher falls back to the
72
+ GitHub tarball when the sibling is absent — Path B's `Phase B.3`
73
+ installs it)
74
+
75
+ ### When prereqs are missing — operator menu
76
+
77
+ If a prereq fails, surface it to the operator and present this menu
78
+ verbatim before stopping:
79
+
80
+ ```
81
+ Prerequisite missing for standard onboarding: <name of failed check>.
82
+
83
+ Pick one:
84
+
85
+ 1. Cold-adopter magic demo — runs /launch --local, no GitHub repo
86
+ created, no vendor accounts needed. Switches this dispatch to
87
+ --greenfield-from-intent (Path B below). No separate API key
88
+ needed — uses the LLM your Claude Code session provides.
89
+ 2. Fix the prereq and re-run standard onboarding (Path A).
90
+ 3. Cancel.
91
+ ```
92
+
93
+ Operator picks `1` → continue under Path B. Picks `2` → stop and wait
94
+ for the operator to fix + re-dispatch. Picks `3` → stop.
95
+
96
+ Silence is not deferral. Wait for an explicit choice.
97
+
98
+ ## Working directory convention
99
+
100
+ | Path | Working directory |
101
+ |---|---|
102
+ | Path A — Phases 1–3 | **target repo** (existing GitHub repo) |
103
+ | Path A — Phase 4 | retired (see Phase 4 note below) |
104
+ | Path B — all phases | **target directory** (typically empty; never leaves it) |
105
+
106
+ For Path A the `pwd` check at the start of each phase exists for a
107
+ reason — don't lose track. Path B runs entirely inside one directory
108
+ and never reaches out to GitHub.
109
+
110
+ ## Procedure
111
+
112
+ The Procedure branches at the top into **Path A** (standard
113
+ onboarding) or **Path B** (`--greenfield-from-intent` cold-adopter
114
+ magic demo). Decide first, then walk only the phases under the chosen
115
+ path. Do not mix.
116
+
117
+ ### Choosing the path
118
+
119
+ | Signal | Path |
120
+ |---|---|
121
+ | Operator dispatched `/onboard-repo --greenfield-from-intent ...` | B |
122
+ | Operator picked option `1` from the prereqs-missing menu | B |
123
+ | All Path A prereqs pass + no `--greenfield-from-intent` flag | A |
124
+ | Operator dispatched `/onboard-repo --with-deploy-host` / `--with-secrets` / `--full` | A (with Phase 3.5 chained) |
125
+
126
+ When in doubt, ask the operator which path; don't infer.
127
+
128
+ ---
129
+
130
+ ### Path B — `--greenfield-from-intent` (cold-adopter magic demo)
131
+
132
+ **Working directory: empty.** Never leaves it. No GitHub repo. No vendor accounts touched. The path runs in ≤5 steps using the LLM your Claude Code session provides.
133
+
134
+ The task list the agent generates for Path B MUST be exactly these
135
+ five tasks, in this order. Do not add Phase 1 GitHub work, Phase 1.3
136
+ prod environment, Phase 3.5 follow-on prompts, or Phase 4 matrix PRs.
137
+ Those belong to Path A.
138
+
139
+ #### Phase B.1 — Confirm intent
140
+
141
+ Operator pastes a paragraph or voice transcript describing what they
142
+ want to build (the recipe-test example: "a wiki app for sharing
143
+ recipes with family"). Agent echoes the intent back and asks for
144
+ operator confirmation before continuing. No edits hit disk yet.
145
+
146
+ #### Phase B.2 — Init local git
147
+
148
+ ```bash
149
+ git init
150
+ git commit --allow-empty -m "chore: init repo"
151
+ ```
152
+
153
+ First empty commit so subsequent file writes have a baseline.
154
+
155
+ #### Phase B.3 — Scaffold the bassclef wiring
156
+
157
+ Subset of Path A Phase 2 — only the files `/launch --local` actually
158
+ reads. Skip everything that needs a remote.
159
+
160
+ ```bash
161
+ mkdir -p .claude/hooks docs
162
+
163
+ # Install the thin-pointer dispatcher (same source as Path A Phase 2.1).
164
+ # Sibling-fast-path preferred; the curl fallback works for cold adopters.
165
+ cp ~/src/sunj-labs/bassclef/presence/install/bassclef-sync.dispatcher.template.sh \
166
+ .claude/hooks/bassclef-sync.sh 2>/dev/null \
167
+ || curl -sL https://raw.githubusercontent.com/sunj-labs/bassclef/main/presence/install/bassclef-sync.dispatcher.template.sh \
168
+ -o .claude/hooks/bassclef-sync.sh
169
+ chmod +x .claude/hooks/bassclef-sync.sh
170
+
171
+ # Minimal settings.json — only the SessionStart hook
172
+ cat > .claude/settings.json <<'JSON'
173
+ {
174
+ "permissions": {
175
+ "additionalDirectories": ["../bassclef"]
176
+ },
177
+ "hooks": {
178
+ "SessionStart": [
179
+ {
180
+ "matcher": "",
181
+ "hooks": [
182
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/bassclef-sync.sh", "timeout": 30 }
183
+ ]
184
+ }
185
+ ]
186
+ }
187
+ }
188
+ JSON
189
+
190
+ # Minimal substrate.config.md
191
+ cat > substrate.config.md <<'MD'
192
+ # substrate.config.md
193
+
194
+ hosting_platform: none
195
+ deploy_targets:
196
+ prod: false
197
+ budget:
198
+ session_ceiling: $0
199
+ iteration_ceiling: $0
200
+ MD
201
+
202
+ # Minimal .claude/bassclef-configs.jsonc — adopter-default seed (bassclef#1135 WU-4)
203
+ cat > .claude/bassclef-configs.jsonc <<'JSONC'
204
+ // bassclef-configs.jsonc — unified adopter configuration discovery surface.
205
+ // JSON Schema: standards/state-spine/schemas/bassclef-configs.schema.json
206
+ {
207
+ "sync": { "enabled": false },
208
+ "model_tier": {
209
+ "Builder": "balanced",
210
+ "Closer": "cheap-fast",
211
+ "Deployer": "cheap-fast",
212
+ "DocsWriter": "cheap-fast",
213
+ "Explorer": "cheap-fast",
214
+ "Orchestrator": "cheap-fast",
215
+ "Reviewer": "cheap-fast",
216
+ "Shaper": "capable"
217
+ },
218
+ "plan_tier": "tier-1",
219
+ "prose_discipline": { "kiss_words_turn_prose": true },
220
+ "testing": { "global_floor": 1, "path_matchers": [] }
221
+ }
222
+ JSONC
223
+
224
+ # Minimal CLAUDE.md — operator owns this going forward
225
+ cat > CLAUDE.md <<'MD'
226
+ # <repo-name>
227
+
228
+ ## What this repo is
229
+
230
+ Cold-adopter magic demo from a stated intent. Substrate inherited from
231
+ bassclef via `--add-dir ../bassclef` or thin-pointer fetch.
232
+
233
+ ## Stack
234
+
235
+ To be determined by `/launch --local` output.
236
+ MD
237
+ ```
238
+
239
+ Skip in Path B:
240
+
241
+ - `gh repo create` (no remote)
242
+ - Phase 1.1 standard labels (no remote to apply them to)
243
+ - Phase 1.2 branch protection (same)
244
+ - Phase 1.3 prod environment (same)
245
+ - `.bassclef-source.json` (only needed for cloud/mobile sessions
246
+ where the sibling sync isn't available; cold adopters running
247
+ locally don't need it for the magic demo)
248
+ - Phase 3.5 interactive follow-on prompts (no `/provision-deploy-host`,
249
+ no `/secrets-bootstrap` — Path B has no vendor surfaces to bind)
250
+
251
+ #### Phase B.4 — Dispatch `/launch --local --voice-input`
252
+
253
+ Hand the intent paragraph straight to `/launch`:
254
+
255
+ ```
256
+ /launch medium --local --voice-input "<intent paragraph from Phase B.1>"
257
+ ```
258
+
259
+ Path B's last action is this dispatch. Do not chain `/build` or
260
+ `/launch-preview` afterward — those belong to the extended path for
261
+ operators with vendor bindings.
262
+
263
+ #### Phase B.5 — `/launch --local` runs
264
+
265
+ `/launch` writes the full artifact chain to disk (input-artifact, spec,
266
+ prototypes gallery, user stories, decomposition, migration plan) and
267
+ serves the gallery on `localhost:8000+`. The localhost URL is the
268
+ deliverable. Operator inspects, picks a direction, and either keeps
269
+ iterating locally or moves to standard onboarding (Path A) when they're
270
+ ready to push to a GitHub repo.
271
+
272
+ When `/launch --local` returns, Path B is done. Skip everything below
273
+ this divider.
274
+
275
+ ---
276
+
277
+ ### Path A — standard onboarding
278
+
279
+ Run each phase in order. Operator confirms before destructive or
280
+ cross-repo actions. Each phase is independently re-runnable.
281
+
282
+ #### Phase 0 — Refuse to run from main (bassclef#956 V0.5)
283
+
284
+ Substrate changes go through PRs, same as feature work. If `/onboard-repo`
285
+ runs from `main`, every scaffold (`.claude/`, `CLAUDE.md`,
286
+ `docs/whereami.md`, `substrate.config.md`, `.bassclef-source.json`,
287
+ `.gitignore`) lands as a direct-to-main commit by surprise. Path A
288
+ refuses that shape and prints the remediation:
289
+
290
+ ```bash
291
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
292
+
293
+ case "$CURRENT_BRANCH" in
294
+ main|master)
295
+ cat >&2 <<'EOF'
296
+
297
+ 🛑 /onboard-repo refuses to run from main.
298
+
299
+ Why: substrate scaffolding (`.claude/`, `CLAUDE.md`, `docs/whereami.md`,
300
+ `substrate.config.md`, `.bassclef-source.json`, `.gitignore`) is a
301
+ substrate change — it goes through a PR, not a direct commit to main.
302
+
303
+ Remediation:
304
+
305
+ git checkout -b chore/bassclef-bootstrap
306
+ # re-run /onboard-repo
307
+
308
+ When you reach the end of Phase 3, /onboard-repo will guide you to
309
+ open the bootstrap PR; merging it lands the substrate on main with
310
+ audit history (this is flywheel turn 1 from the first-use sequence —
311
+ see docs/first-use-sequence.md).
312
+
313
+ V1 (bassclef#956 follow-up) will auto-create the branch + PR. V0.5
314
+ keeps the operator in the loop on the branching decision.
315
+
316
+ EOF
317
+ exit 1
318
+ ;;
319
+ "")
320
+ cat >&2 <<'EOF'
321
+
322
+ 🛑 /onboard-repo cannot detect the current git branch.
323
+
324
+ The cwd may not be a git repository, OR git is in a detached-HEAD state.
325
+
326
+ Remediation:
327
+
328
+ git init # if not a git repo
329
+ git checkout -b chore/bassclef-bootstrap # from a fresh branch
330
+ # re-run /onboard-repo
331
+
332
+ EOF
333
+ exit 1
334
+ ;;
335
+ *)
336
+ # Any other branch — proceed
337
+ ;;
338
+ esac
339
+ ```
340
+
341
+ The remediation message is the contract. If a cold adopter dispatches
342
+ `/onboard-repo` from `main`, they see exactly what to do — no operator
343
+ coaching, no second-guess. V0.5 ships in bassclef#956; V1
344
+ (auto-create branch + PR) is post-launch hardening per the goal's
345
+ operator-decision items.
346
+
347
+ #### Phase 1 — Set up the GitHub repo (labels, branch protection)
348
+
349
+ **Working directory: target repo** (`pwd` should match `gh repo view --json name -q .name`).
350
+
351
+ Idempotent API calls against the target repo via `gh`.
352
+
353
+ ##### 1.1 Create the 11 standard labels
354
+
355
+ ```bash
356
+ # Read from git remote — don't infer from cwd basename (cwd can be renamed)
357
+ REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
358
+
359
+ declare -A LABELS=(
360
+ [bassclef-evolution]="A6E0F2:Candidate for promotion to bassclef"
361
+ [agent-user-proposed]="D4A5F5:Proposed by agent in an app repo"
362
+ [bassclef-sync]="8FD4A4:Related to bassclef substrate syncing"
363
+ [review-required]="F5B94A:Blocks — human review required"
364
+ [bug]="E4443A:Defect"
365
+ [enhancement]="A2EEEF:New or improved functionality"
366
+ [security]="FF0000:Security-relevant"
367
+ [infra]="C9D1D9:Infrastructure / workflows / tooling"
368
+ [priority-high]="E4443A:High priority"
369
+ [priority-medium]="F5B94A:Medium priority"
370
+ [question]="D876E3:Needs operator input or design discussion"
371
+ )
372
+
373
+ for NAME in "${!LABELS[@]}"; do
374
+ COLOR="${LABELS[$NAME]%%:*}"
375
+ DESC="${LABELS[$NAME]#*:}"
376
+ gh label create "$NAME" --repo "$REPO" --color "$COLOR" --description "$DESC" --force >/dev/null
377
+ done
378
+ ```
379
+
380
+ Uses `--force` so the call is idempotent (updates existing label or
381
+ creates new). This matches the bootstrap logic shipped in bassclef
382
+ #160 (sync hook does the same for `bassclef-evolution` + `agent-user-proposed`);
383
+ this phase adds the broader set up-front so the operator can label issues
384
+ immediately without waiting for a sync cycle.
385
+
386
+ ##### 1.2 Protect main from force-push and deletion
387
+
388
+ ```bash
389
+ DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef -q '.defaultBranchRef.name')
390
+
391
+ gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" \
392
+ --method PUT \
393
+ --input - <<EOF
394
+ {
395
+ "required_status_checks": null,
396
+ "enforce_admins": false,
397
+ "required_pull_request_reviews": null,
398
+ "restrictions": null,
399
+ "allow_force_pushes": false,
400
+ "allow_deletions": false
401
+ }
402
+ EOF
403
+ ```
404
+
405
+ Minimum defense: no force-push or deletion of main. PR reviews NOT
406
+ required here — agent-created PRs (bassclef-sync) need to merge
407
+ without human intervention for the mobile-sync story. Add stricter
408
+ rules per-repo in `.github/branch-protection.yml` if the repo warrants.
409
+
410
+ ##### 1.3 Add the production approval gate (if this repo deploys to prod)
411
+
412
+ If the repo will deploy to production, per `manual-prod-approval.md`:
413
+
414
+ ```bash
415
+ # Prompt operator: "Does this repo deploy to prod? [y/N]"
416
+ # If yes:
417
+ USER_ID=$(gh api user --jq '.id')
418
+ gh api "repos/$REPO/environments/prod" --method PUT \
419
+ -f "wait_timer=0" \
420
+ -f "deployment_branch_policy[protected_branches]=false" \
421
+ -f "deployment_branch_policy[custom_branch_policies]=true"
422
+
423
+ gh api "repos/$REPO/environments/prod" --method PUT \
424
+ -F "reviewers[][type]=User" \
425
+ -F "reviewers[][id]=$USER_ID"
426
+ ```
427
+
428
+ Skip if repo is docs-only (like bassclef itself) or doesn't have a
429
+ production deploy target.
430
+
431
+ #### Phase 2 — Install bassclef wiring (settings, sync script, config)
432
+
433
+ **Working directory: target repo.** All file writes happen here.
434
+
435
+ ##### 2.1 Install the auto-update script (off by default per bassclef#939)
436
+
437
+ ```bash
438
+ mkdir -p .claude/hooks
439
+
440
+ # Install thin-pointer dispatcher from bassclef's own template directory.
441
+ # This is the source-of-truth file consumers vendor.
442
+ cp ~/src/sunj-labs/bassclef/presence/install/bassclef-sync.dispatcher.template.sh \
443
+ .claude/hooks/bassclef-sync.sh
444
+
445
+ # OR — if bassclef isn't checked out alongside this repo, fetch from
446
+ # bassclef's main branch on GitHub (public tarball release works too
447
+ # once bassclef itself goes public):
448
+ # curl -sL https://raw.githubusercontent.com/sunj-labs/bassclef/main/presence/install/bassclef-sync.dispatcher.template.sh \
449
+ # -o .claude/hooks/bassclef-sync.sh
450
+
451
+ chmod +x .claude/hooks/bassclef-sync.sh
452
+
453
+ # Verify the version header reads `thin-pointer-2026-05-15` or later:
454
+ head -2 .claude/hooks/bassclef-sync.sh
455
+ ```
456
+
457
+ The thin-pointer dispatcher does NOT vendor bassclef's sync logic.
458
+ It reads `.bassclef-source.json` (Phase 2.4) for the upstream tarball
459
+ URL + auth, fetches it, and execs the included template — always-current.
460
+ Eliminates two prior patching layers: hook content drift (vendored
461
+ template diverging from bassclef's) and URL refresh ceremony.
462
+
463
+ When the dispatcher can't load the substrate (sibling missing AND curl
464
+ fallback unconfigured / unauthenticated), it prints a plain-language
465
+ explanation with three resolution paths (public adopter, local dev,
466
+ private fork). External adopters get actionable guidance instead of
467
+ internal-vocabulary errors. Closes the adoption-friendliness gap noted
468
+ in bassclef#715.
469
+
470
+ ##### 2.2 Configure Claude Code for this repo
471
+
472
+ Standard bassclef settings — SessionStart → `bassclef-sync.sh`, +
473
+ other hooks per-agent configuration. Copy from a reference consumer
474
+ (POA or ops) and scrub for repo-specific paths:
475
+
476
+ ```bash
477
+ cat > .claude/settings.json <<'JSON'
478
+ {
479
+ "permissions": {
480
+ "additionalDirectories": ["../bassclef"]
481
+ },
482
+ "hooks": {
483
+ "SessionStart": [
484
+ {
485
+ "matcher": "",
486
+ "hooks": [
487
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/bassclef-sync.sh", "timeout": 30 }
488
+ ]
489
+ }
490
+ ]
491
+ }
492
+ }
493
+ JSON
494
+ ```
495
+
496
+ Operator confirms — may want to add app-specific hooks (pre-build-gate,
497
+ pre-commit-gate) if the repo has source code.
498
+
499
+ ##### 2.2.1 Wire turn-prose-kiss-check Stop hook (bassclef#1138 T5)
500
+
501
+ Symlink the bassclef Stop hook that scans the most recent assistant
502
+ message for bassclef-internal jargon (the `/kiss words` BLOCK list
503
+ from `standards/bassclef-internal-jargon.md`). When
504
+ `prose_discipline.kiss_words_turn_prose` is `true` in
505
+ `.claude/bassclef-configs.jsonc` (the adopter-default Phase 2.3.5
506
+ writes), the hook fires after every assistant turn and surfaces
507
+ findings; when `false`, the hook short-circuits silently.
508
+
509
+ Symlink the hook (bassclef lives one directory up per the
510
+ `additionalDirectories` convention):
511
+
512
+ ```bash
513
+ mkdir -p .claude/hooks
514
+ ln -sf "../../../bassclef/.claude/hooks/turn-prose-kiss-check.sh" \
515
+ ".claude/hooks/turn-prose-kiss-check.sh"
516
+ ```
517
+
518
+ Add the Stop hook block to `.claude/settings.json`. If the file already
519
+ has a Stop block (for example, `session-end.sh`), append this hook
520
+ alongside — both run, turn-prose-kiss-check first, session-end second:
521
+
522
+ ```jsonc
523
+ {
524
+ "hooks": {
525
+ "Stop": [
526
+ {
527
+ "matcher": "",
528
+ "hooks": [
529
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/turn-prose-kiss-check.sh", "timeout": 10 }
530
+ ]
531
+ }
532
+ ]
533
+ }
534
+ }
535
+ ```
536
+
537
+ **Composition rule** — when an existing Stop hook is present, leave it
538
+ in place and append this one. The turn-prose-kiss-check hook exits 0
539
+ even when findings surface (default toggle: advisory), so a downstream
540
+ session-end hook still runs. When the adopter opts in to strict mode
541
+ (`prose_discipline.kiss_words_turn_prose: "strict"` in
542
+ bassclef-configs.jsonc), the hook exits 2 and blocks the stop — that's
543
+ the conscious-strict trade-off.
544
+
545
+ **Conditional fire** — the hook reads
546
+ `.claude/bassclef-configs.jsonc` `prose_discipline.kiss_words_turn_prose`
547
+ at runtime and short-circuits when `false`. Adopters who never want the
548
+ check can flip the toggle without unwiring the hook.
549
+
550
+ **Verify the wiring:**
551
+
552
+ ```bash
553
+ # Sanity: hook is executable + reachable
554
+ ls -l .claude/hooks/turn-prose-kiss-check.sh
555
+
556
+ # Sanity: settings.json parses
557
+ python3 -c "import json; json.load(open('.claude/settings.json'))"
558
+ ```
559
+
560
+ **Composes with downstream Phase 2.x steps:**
561
+
562
+ - Phase 2.3.5 writes `prose_discipline.kiss_words_turn_prose: true` to
563
+ `.claude/bassclef-configs.jsonc` as the adopter default — this hook
564
+ fires when that toggle is true.
565
+ - Phase 2.5 (security scanning) is a separate hook wiring path; both
566
+ hooks can co-exist in the Stop block.
567
+
568
+ Source: bassclef#1051 (hook origin), bassclef#1138 (this wiring step).
569
+
570
+ ##### 2.2.2 Wire substrate-clarity-gate PreToolUse hook (bassclef#1137 T4)
571
+
572
+ Symlink the bassclef PreToolUse hook that BLOCKs writes to
573
+ `.claude/skills/`, `.claude/rules/`, and `.claude/luminaries/` when
574
+ they violate the clarity discipline bassclef applies to its own
575
+ substrate — skill descriptions over 280 chars, rule + luminary
576
+ "NEVER" without paired "INSTEAD:" block, jargon usage where plain
577
+ language exists. Adopter writes to substrate paths trigger the same
578
+ gate bassclef does.
579
+
580
+ Symlink the hook + the bassclef baseline allowlist (the file that
581
+ grandfathers pre-existing violators during a grace window):
582
+
583
+ ```bash
584
+ mkdir -p .claude/hooks
585
+ ln -sf "../../../bassclef/.claude/hooks/substrate-clarity-gate.sh" \
586
+ ".claude/hooks/substrate-clarity-gate.sh"
587
+ ln -sf "../../../bassclef/.claude/hooks/substrate-clarity-allowlist.txt" \
588
+ ".claude/hooks/substrate-clarity-allowlist.txt"
589
+ ```
590
+
591
+ Add the PreToolUse Edit|Write block to `.claude/settings.json`. If an
592
+ `Edit|Write` matcher already exists from a prior phase, append this
593
+ hook to its `hooks` array instead of duplicating the matcher entry:
594
+
595
+ ```jsonc
596
+ {
597
+ "hooks": {
598
+ "PreToolUse": [
599
+ {
600
+ "matcher": "Edit|Write",
601
+ "hooks": [
602
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/substrate-clarity-gate.sh" }
603
+ ]
604
+ }
605
+ ]
606
+ }
607
+ }
608
+ ```
609
+
610
+ **Adopter allowlist convention** — the symlinked
611
+ `substrate-clarity-allowlist.txt` carries bassclef's baseline
612
+ grandfather list (pre-existing violators with a documented grace
613
+ window). When the adopter has their own pre-existing substrate
614
+ violations they want to grandfather during their own migration,
615
+ they create a sibling file:
616
+
617
+ ```bash
618
+ touch .claude/hooks/substrate-clarity-allowlist-adopter.txt
619
+ ```
620
+
621
+ The hook reads BOTH files in series — bassclef's baseline first,
622
+ then the adopter-side append. Entries in the adopter file follow
623
+ the same one-path-per-line format as bassclef's baseline. The
624
+ adopter file is git-tracked in the adopter repo (not symlinked
625
+ into bassclef), so each adopter manages their own grandfather
626
+ list independently.
627
+
628
+ **Composition rule** — when an existing PreToolUse Edit|Write
629
+ matcher block is present (for example, `artifact-ingestion-gate.sh`
630
+ from Phase 2.x), append this hook's command entry to the same
631
+ matcher's `hooks` array. The matcher entry must not be duplicated;
632
+ the array of hooks under one matcher can hold multiple commands
633
+ that fire in series.
634
+
635
+ **Override path** — adopter can bypass per-call with
636
+ `SKIP_SUBSTRATE_CLARITY=1 <command>` (logged via trace-helper per
637
+ bassclef override idiom). The override is for migration scripts
638
+ and emergency rescue; routine use should fix the violation, not
639
+ bypass.
640
+
641
+ **Verify the wiring:**
642
+
643
+ ```bash
644
+ # Sanity: hook executable + reachable
645
+ ls -l .claude/hooks/substrate-clarity-gate.sh
646
+ ls -l .claude/hooks/substrate-clarity-allowlist.txt
647
+
648
+ # Sanity: settings.json parses + has the matcher
649
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
650
+ print('substrate-clarity-gate wired' if any( \
651
+ 'substrate-clarity-gate' in h.get('command','') \
652
+ for entry in d.get('hooks',{}).get('PreToolUse',[]) \
653
+ for h in entry.get('hooks',[])) else 'NOT wired')"
654
+ ```
655
+
656
+ Source: bassclef#382 (hook origin), bassclef#1137 (this wiring step), bassclef#1135 (T2 config default — separate concern).
657
+
658
+ ##### 2.2.3 Wire artifact-ingestion-gate PreToolUse hook (bassclef#1140 T7)
659
+
660
+ Symlink the bassclef PreToolUse hook that BLOCKs writes to adopter
661
+ substantive artifacts (specs, decompositions, prototypes, ADRs,
662
+ proposals, iteration goals) when the artifact body lacks a
663
+ `## Sources read` section. The discipline catches the
664
+ "pattern-matched on generic X" failure mode that
665
+ `.claude/rules/artifact-ingestion.md` was built to close — applied at
666
+ adopter writes, not just at bassclef's own writes.
667
+
668
+ Symlink the hook:
669
+
670
+ ```bash
671
+ mkdir -p .claude/hooks
672
+ ln -sf "../../../bassclef/.claude/hooks/artifact-ingestion-gate.sh" \
673
+ ".claude/hooks/artifact-ingestion-gate.sh"
674
+ ```
675
+
676
+ Add the PreToolUse Write block to `.claude/settings.json`. The hook
677
+ matches on `Write` (not `Edit`) — edits to existing artifacts pass
678
+ through; only new artifact writes trigger the gate. If a Write
679
+ matcher already exists from a prior phase, append this hook to its
680
+ `hooks` array:
681
+
682
+ ```jsonc
683
+ {
684
+ "hooks": {
685
+ "PreToolUse": [
686
+ {
687
+ "matcher": "Write",
688
+ "hooks": [
689
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/artifact-ingestion-gate.sh" }
690
+ ]
691
+ }
692
+ ]
693
+ }
694
+ }
695
+ ```
696
+
697
+ **Adopter-default path matchers** — the hook reads
698
+ `.claude/bassclef-configs.jsonc` `artifact_ingestion.paths` to
699
+ decide which paths fire the gate. Phase 2.3.5 (bassclef-configs
700
+ adopter-default seed) appends this block with the bassclef default
701
+ list. Adopters edit the list per repo — adding paths their substrate
702
+ considers substantive, or removing paths that do not apply:
703
+
704
+ ```jsonc
705
+ "artifact_ingestion": {
706
+ "paths": [
707
+ "docs/specs/**/*.md",
708
+ "docs/decompositions/**/*.md",
709
+ "docs/prototypes/**/*.html",
710
+ "docs/proposals/**/*.md",
711
+ "docs/adrs/**/*.md",
712
+ "docs/use-cases/**/*.md",
713
+ "docs/user-stories/**/*.md",
714
+ "docs/ia-models/**/*.md",
715
+ "docs/interaction-design/**/*",
716
+ "docs/personas/**/*.md",
717
+ "docs/jtbd-tasks/**/*.md",
718
+ "docs/value-props/**/*.md",
719
+ "docs/iteration-bets/**/*.md"
720
+ ]
721
+ }
722
+ ```
723
+
724
+ When the `artifact_ingestion.paths` field is absent (older adopters
725
+ predating this Phase), the hook falls back to its built-in default
726
+ matcher list — the wiring stays safe and quiet.
727
+
728
+ **Composition rule** — when an existing PreToolUse Write matcher is
729
+ present (for example, from a sibling hook in another Phase 2.x step),
730
+ append this hook's command entry to the same matcher's `hooks` array.
731
+ The matcher entry must not be duplicated.
732
+
733
+ **Override path** — adopter can bypass per-call with
734
+ `SKIP_ARTIFACT_INGESTION=1 <command>` (logged via trace-helper). Use
735
+ for migration scripts and emergency rescue.
736
+
737
+ **Verify the wiring:**
738
+
739
+ ```bash
740
+ # Sanity: hook executable + reachable
741
+ ls -l .claude/hooks/artifact-ingestion-gate.sh
742
+
743
+ # Sanity: settings.json has the matcher
744
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
745
+ print('artifact-ingestion-gate wired' if any( \
746
+ 'artifact-ingestion-gate' in h.get('command','') \
747
+ for entry in d.get('hooks',{}).get('PreToolUse',[]) \
748
+ for h in entry.get('hooks',[])) else 'NOT wired')"
749
+ ```
750
+
751
+ Source: bassclef#116 (hook origin), bassclef#1140 (this wiring step).
752
+
753
+ ##### 2.2.4 Wire pattern-annotation-validate PreToolUse hook (bassclef#1139 T6)
754
+
755
+ **Lite adopter note (bassclef#740 Task 1.2).** This hook ships at `tier: standard`. If your adopter tier is lite (resolved via `lib/tier-check.sh`), skip this Phase step and continue with 2.2.5. The bassclef-lite tarball does not carry this hook.
756
+
757
+ Symlink the bassclef PreToolUse hook that validates `@pattern <catalog-path>` annotations in adopter source code. Layer 3 of bassclef#478 traceability — code that instantiates a named pattern from bassclef's catalog (`patterns/<shelf>/<source>/<slug>.md`) must mark itself with the annotation so forward (which code uses this pattern) and reverse (which patterns is this code using) lookups work across the adopter portfolio.
758
+
759
+ Symlink the hook:
760
+
761
+ ```bash
762
+ mkdir -p .claude/hooks
763
+ ln -sf "../../../bassclef/.claude/hooks/pattern-annotation-validate.sh" \
764
+ ".claude/hooks/pattern-annotation-validate.sh"
765
+ ```
766
+
767
+ Add the PreToolUse Edit|Write|MultiEdit block to `.claude/settings.json`. If an `Edit|Write` matcher already exists from a prior phase, append this hook to its `hooks` array (the bassclef settings.json convention is to widen matchers when adding new hooks that share a tool set):
768
+
769
+ ```jsonc
770
+ {
771
+ "hooks": {
772
+ "PreToolUse": [
773
+ {
774
+ "matcher": "Edit|Write|MultiEdit",
775
+ "hooks": [
776
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pattern-annotation-validate.sh" }
777
+ ]
778
+ }
779
+ ]
780
+ }
781
+ }
782
+ ```
783
+
784
+ **Rule propagation** — the `pattern-annotation` rule lives at bassclef's `.claude/rules/pattern-annotation.md` and loads into adopter agent context via the `additionalDirectories: ["../bassclef"]` entry Phase 2.2 wrote. No copy needed; bassclef's rule body is the source of truth.
785
+
786
+ **Tech-stack-aware scoping** — the hook reads adopter's `.claude/bassclef-configs.jsonc` `tech_stack.lang` (Phase 2.3.6 writes) and fires only on source files matching the declared language: `.ts/.tsx` for `typescript`, `.py` for `python`, `.rb` for `ruby`, `.go` for `go`, `.rs` for `rust`. Non-source files silent. When `tech_stack.lang` is absent (older adopters or skip path), the hook fires on the union of language matchers (safe default).
787
+
788
+ **Composition rule** — when an existing PreToolUse Edit|Write matcher block is present (substrate-clarity-gate from 2.2.2 uses `Edit|Write`), the matcher set widens to `Edit|Write|MultiEdit` to cover both hooks. Adopters can keep the matchers separate if they prefer narrower scoping per hook.
789
+
790
+ **Override path** — `SKIP_PATTERN_ANNOTATION=1 <command>` (logged via trace-helper). Use for migration scripts when annotating retroactively is in flight.
791
+
792
+ **Verify the wiring:**
793
+
794
+ ```bash
795
+ # Sanity: hook executable + reachable
796
+ ls -l .claude/hooks/pattern-annotation-validate.sh
797
+
798
+ # Sanity: rule loads via additionalDirectories
799
+ test -f ../bassclef/.claude/rules/pattern-annotation.md && echo "rule reachable" || echo "rule NOT reachable"
800
+
801
+ # Sanity: settings.json has the matcher
802
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
803
+ print('pattern-annotation-validate wired' if any( \
804
+ 'pattern-annotation-validate' in h.get('command','') \
805
+ for entry in d.get('hooks',{}).get('PreToolUse',[]) \
806
+ for h in entry.get('hooks',[])) else 'NOT wired')"
807
+ ```
808
+
809
+ Source: bassclef#478 (parent traceability goal), bassclef#1139 (this wiring step), bassclef#1127 (tech_stack vocabulary the scoping reads).
810
+
811
+ ##### 2.2.5 Wire pre-commit-gate + state-validate PreToolUse hooks (bassclef#1136 T3)
812
+
813
+ Symlink the two bassclef PreToolUse hooks that gate adopter writes.
814
+
815
+ `pre-commit-gate.sh` runs the SDLC pre-build checks per `.claude/rules/sdlc-gates.md`. Those checks are temperance + diagnosis + scope drift.
816
+
817
+ `state-validate.sh` validates state-spine entity writes against JSON Schema per `.claude/rules/state-schema-validation.md`.
818
+
819
+ Together the hooks catch two failure classes before any source edit lands: skipped gates (silent SDLC bypass) and malformed state writes (silent schema drift).
820
+
821
+ Symlink both hooks:
822
+
823
+ ```bash
824
+ mkdir -p .claude/hooks
825
+ ln -sf "../../../bassclef/.claude/hooks/pre-commit-gate.sh" \
826
+ ".claude/hooks/pre-commit-gate.sh"
827
+ ln -sf "../../../bassclef/.claude/hooks/state-validate.sh" \
828
+ ".claude/hooks/state-validate.sh"
829
+ ```
830
+
831
+ Add the PreToolUse Edit|Write|MultiEdit block to `.claude/settings.json`. If an `Edit|Write` or `Edit|Write|MultiEdit` matcher already exists from a prior phase (substrate-clarity-gate from 2.2.2, pattern-annotation-validate from 2.2.4), append both hooks to its `hooks` array — the matcher set widens to `Edit|Write|MultiEdit` when MultiEdit is not already covered:
832
+
833
+ ```jsonc
834
+ {
835
+ "hooks": {
836
+ "PreToolUse": [
837
+ {
838
+ "matcher": "Edit|Write|MultiEdit",
839
+ "hooks": [
840
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-commit-gate.sh" },
841
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/state-validate.sh" }
842
+ ]
843
+ }
844
+ ]
845
+ }
846
+ }
847
+ ```
848
+
849
+ **Order matters** — `pre-commit-gate.sh` runs first (cheap SDLC checks), `state-validate.sh` runs second (heavier ajv-cli schema validation). The matcher block lists them in that order; a BLOCK from the first stops the chain before the second fires.
850
+
851
+ **Rule propagation** — both rules (`sdlc-gates.md` + `state-schema-validation.md`) live at bassclef's `.claude/rules/` and load into adopter agent context via the `additionalDirectories: ["../bassclef"]` entry Phase 2.2 wrote. No copy needed; bassclef's rule body is the source of truth.
852
+
853
+ **Composition rule** — when an existing PreToolUse Edit|Write matcher block is present from earlier Phase 2.x steps, append these two hooks to its `hooks` array (do not duplicate the matcher entry). Hook order within the array determines execution order; place `pre-commit-gate.sh` before `state-validate.sh` per the order rule above.
854
+
855
+ **Override paths** — `SKIP_PRE_COMMIT_GATE=1 <command>` and `SKIP_STATE_VALIDATE=1 <command>` (both logged via trace-helper). Use only for one-shot migration scripts (state bootstrapping) or emergency rescue when the hook itself is broken.
856
+
857
+ **Verify the wiring:**
858
+
859
+ ```bash
860
+ # Sanity: both hooks executable + reachable
861
+ ls -l .claude/hooks/pre-commit-gate.sh .claude/hooks/state-validate.sh
862
+
863
+ # Sanity: both rules reachable via additionalDirectories
864
+ test -f ../bassclef/.claude/rules/sdlc-gates.md && echo "sdlc-gates reachable" || echo "sdlc-gates NOT reachable"
865
+ test -f ../bassclef/.claude/rules/state-schema-validation.md && echo "state-schema-validation reachable" || echo "state-schema-validation NOT reachable"
866
+
867
+ # Sanity: settings.json has both hooks wired in matcher block
868
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
869
+ hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PreToolUse',[]) for h in entry.get('hooks',[])]; \
870
+ print('pre-commit-gate wired' if any('pre-commit-gate' in c for c in hooks) else 'NOT wired'); \
871
+ print('state-validate wired' if any('state-validate' in c for c in hooks) else 'NOT wired')"
872
+ ```
873
+
874
+ Source: bassclef#1136 (this wiring step — T3, iteration-2 BLOCKER). Sister rules: `.claude/rules/sdlc-gates.md` + `.claude/rules/state-schema-validation.md`.
875
+
876
+ ##### 2.2.6 Wire destructive-command-guard + multi-tenancy-check PreToolUse hooks (bassclef#1141 T8 — partial)
877
+
878
+ **Lite adopter note (bassclef#740 Task 1.2).** Both hooks ship at `tier: standard`. Skip this step if your tier is lite. Resolve tier via `lib/tier-check.sh`. Continue with 2.2.7.
879
+
880
+ Symlink two bassclef PreToolUse hooks that catch runtime-safety problems before they reach the operator's filesystem or database. `destructive-command-guard.sh` fires on Bash and detects destructive verbs + force flags + amplifiers (catches "prisma migrate reset", "rm -rf", "FLUSHALL" patterns). `multi-tenancy-check.sh` fires on Edit/Write of source files and warns when tenant-scoped patterns are missing (queries without tenant context).
881
+
882
+ Bet 2026-06-18b scopes T8 to these two hooks; the ticket's other two (`operator-private-path-check.sh` + `type-check.sh`) are deferred to a follow-on per goal narrowing.
883
+
884
+ Symlink both hooks:
885
+
886
+ ```bash
887
+ mkdir -p .claude/hooks
888
+ ln -sf "../../../bassclef/.claude/hooks/destructive-command-guard.sh" \
889
+ ".claude/hooks/destructive-command-guard.sh"
890
+ ln -sf "../../../bassclef/.claude/hooks/multi-tenancy-check.sh" \
891
+ ".claude/hooks/multi-tenancy-check.sh"
892
+ ```
893
+
894
+ Add both PreToolUse blocks to `.claude/settings.json`. The matchers differ — `destructive-command-guard` watches Bash; `multi-tenancy-check` watches Edit|Write|MultiEdit. The Edit|Write|MultiEdit block likely already exists from Phase 2.2.2 / 2.2.3 / 2.2.4 / 2.2.5 — append `multi-tenancy-check` to its `hooks` array (do not duplicate the matcher entry). The Bash block is new for most adopters; create it if absent.
895
+
896
+ ```jsonc
897
+ {
898
+ "hooks": {
899
+ "PreToolUse": [
900
+ {
901
+ "matcher": "Bash",
902
+ "hooks": [
903
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/destructive-command-guard.sh" }
904
+ ]
905
+ },
906
+ {
907
+ "matcher": "Edit|Write|MultiEdit",
908
+ "hooks": [
909
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/multi-tenancy-check.sh" }
910
+ ]
911
+ }
912
+ ]
913
+ }
914
+ }
915
+ ```
916
+
917
+ **Customization** — `multi-tenancy-check.sh` is a template. Adopters with multi-tenant codebases override `TENANT_MODELS` + `TENANT_CONTEXT_FN` + `TENANT_PARAMS_FN` via env vars OR drop a custom `multi-tenancy-check.sh` in `.claude/hooks/` (which wins over the symlink). Single-tenant adopters can disable per-call with `SKIP_MULTI_TENANCY_CHECK=1`.
918
+
919
+ **Rule propagation** — `.claude/rules/destructive-operations.md` loads into adopter agent context via the `additionalDirectories: ["../bassclef"]` entry Phase 2.2 wrote. No copy needed.
920
+
921
+ **Composition rule** — Bash and Edit|Write|MultiEdit matchers stay separate; never collapse them. When other Phase 2.x steps add Bash hooks (Phase 2.2.7 ships atomic-pr-check on Bash; type-check on Bash if T8 follow-on ships), append to the Bash matcher's `hooks` array — do not duplicate the matcher entry.
922
+
923
+ **Override paths** — `SKIP_DESTRUCTIVE_GUARD=1 <command>` (logged) and `SKIP_MULTI_TENANCY_CHECK=1 <command>` (logged). Both bypass logged via trace-helper. Use only for genuine cases (one-shot migrations, single-tenant repos where the check is noise).
924
+
925
+ **Verify the wiring:**
926
+
927
+ ```bash
928
+ # Sanity: both hooks executable + reachable
929
+ ls -l .claude/hooks/destructive-command-guard.sh .claude/hooks/multi-tenancy-check.sh
930
+
931
+ # Sanity: destructive-operations rule reachable via additionalDirectories
932
+ test -f ../bassclef/.claude/rules/destructive-operations.md && echo "destructive-operations reachable" || echo "NOT reachable"
933
+
934
+ # Sanity: settings.json has both hooks wired with correct matchers
935
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
936
+ bash_hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PreToolUse',[]) if entry.get('matcher')=='Bash' for h in entry.get('hooks',[])]; \
937
+ edit_hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PreToolUse',[]) if 'Edit' in entry.get('matcher','') for h in entry.get('hooks',[])]; \
938
+ print('destructive-command-guard wired (Bash)' if any('destructive-command-guard' in c for c in bash_hooks) else 'NOT wired (Bash)'); \
939
+ print('multi-tenancy-check wired (Edit|Write|MultiEdit)' if any('multi-tenancy-check' in c for c in edit_hooks) else 'NOT wired (Edit|Write|MultiEdit)')"
940
+ ```
941
+
942
+ Source: bassclef#1141 (this wiring step — T8, runtime-safety chain). Sister rule: `.claude/rules/destructive-operations.md`. Follow-on: `operator-private-path-check.sh` + `type-check.sh` wires deferred from T8 goal scope.
943
+
944
+ ##### 2.2.7 Wire atomic-pr-check + post-write-trace workflow hooks (bassclef#1142 T9)
945
+
946
+ **Lite adopter note (bassclef#740 Task 1.2).** Both hooks ship at `tier: standard`. Skip this step if your tier is lite. Resolve tier via `lib/tier-check.sh`. Continue with the next step.
947
+
948
+ Symlink two bassclef workflow hooks. `atomic-pr-check.sh` fires on Bash calls matching `git push` patterns and BLOCKs multi-step branches that lack `bundle: true` in the iteration goal's frontmatter — closes the silent atomic-PR-discipline gap that `.claude/rules/pr-strategy.md` describes. `post-write-trace.sh` fires on PostToolUse Write/Edit and records traceability rows so the agent can audit what wrote what.
949
+
950
+ Symlink both hooks:
951
+
952
+ ```bash
953
+ mkdir -p .claude/hooks
954
+ ln -sf "../../../bassclef/.claude/hooks/atomic-pr-check.sh" \
955
+ ".claude/hooks/atomic-pr-check.sh"
956
+ ln -sf "../../../bassclef/.claude/hooks/post-write-trace.sh" \
957
+ ".claude/hooks/post-write-trace.sh"
958
+ ```
959
+
960
+ Add a PreToolUse Bash block + a PostToolUse Write|Edit block to `.claude/settings.json`. The Bash matcher likely already exists from Phase 2.2.6 (destructive-command-guard) — append `atomic-pr-check` to its `hooks` array. The PostToolUse block is new for most adopters.
961
+
962
+ ```jsonc
963
+ {
964
+ "hooks": {
965
+ "PreToolUse": [
966
+ {
967
+ "matcher": "Bash",
968
+ "hooks": [
969
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/atomic-pr-check.sh" }
970
+ ]
971
+ }
972
+ ],
973
+ "PostToolUse": [
974
+ {
975
+ "matcher": "Write|Edit",
976
+ "hooks": [
977
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-write-trace.sh" }
978
+ ]
979
+ }
980
+ ]
981
+ }
982
+ }
983
+ ```
984
+
985
+ **Bet narrowing** — ticket #1142 (T9) also lists `post-skill-friction-check.sh`. The goal scopes this step to two hooks; the third deferred to a follow-on per goal shape.
986
+
987
+ **Composition rule** — when the Bash matcher already exists (Phase 2.2.6 wires destructive-command-guard on the same matcher), append `atomic-pr-check` to its `hooks` array; do not duplicate the matcher entry. Hook order within the Bash array does not matter for these two — destructive-command-guard and atomic-pr-check check different signals (destructive verbs vs git-push patterns) and never conflict on the same command.
988
+
989
+ **Rule propagation** — `.claude/rules/pr-strategy.md` (the methodology atomic-pr-check enforces) loads via `additionalDirectories`. No copy needed.
990
+
991
+ **Override paths** — `SKIP_ATOMIC_PR_CHECK=1 <command>` (logged via trace-helper) bypasses the atomic-PR gate for genuine bundle cases that the goal's `bundle: true` flag did not capture. `post-write-trace.sh` has no override — the trace is append-only and never blocks.
992
+
993
+ **Verify the wiring:**
994
+
995
+ ```bash
996
+ # Sanity: both hooks executable + reachable
997
+ ls -l .claude/hooks/atomic-pr-check.sh .claude/hooks/post-write-trace.sh
998
+
999
+ # Sanity: pr-strategy rule reachable via additionalDirectories
1000
+ test -f ../bassclef/.claude/rules/pr-strategy.md && echo "pr-strategy reachable" || echo "NOT reachable"
1001
+
1002
+ # Sanity: settings.json has both hooks wired with correct matchers
1003
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
1004
+ bash_hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PreToolUse',[]) if entry.get('matcher')=='Bash' for h in entry.get('hooks',[])]; \
1005
+ post_hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PostToolUse',[]) if 'Write' in entry.get('matcher','') or 'Edit' in entry.get('matcher','') for h in entry.get('hooks',[])]; \
1006
+ print('atomic-pr-check wired (PreToolUse Bash)' if any('atomic-pr-check' in c for c in bash_hooks) else 'NOT wired'); \
1007
+ print('post-write-trace wired (PostToolUse Write|Edit)' if any('post-write-trace' in c for c in post_hooks) else 'NOT wired')"
1008
+ ```
1009
+
1010
+ Source: bassclef#1142 (this wiring step — T9, workflow chain). Sister rule: `.claude/rules/pr-strategy.md`. Follow-on: `post-skill-friction-check.sh` wire deferred from T9 goal scope.
1011
+
1012
+ ##### 2.2.8 Wire testing-tier-enforce + allowlist (bassclef#1143 T10)
1013
+
1014
+ Symlink the bassclef testing-tier enforcer into adopter's `.claude/hooks/`. The hook covers Tier 0 through Tier 3. It does not register as its own PreToolUse entry. Instead it runs inside `pre-commit-gate.sh` from Phase 2.2.5. When an adopter edits a Tier 0 source file without touching its matching test file, `pre-commit-gate.sh` calls `testing-tier-enforce.sh`. The commit BLOCKs with a structured stderr message.
1015
+
1016
+ Symlink both the hook and the allowlist baseline:
1017
+
1018
+ ```bash
1019
+ mkdir -p .claude/hooks
1020
+ ln -sf "../../../bassclef/.claude/hooks/testing-tier-enforce.sh" \
1021
+ ".claude/hooks/testing-tier-enforce.sh"
1022
+ ln -sf "../../../bassclef/.claude/hooks/testing-tier-enforce-allowlist.txt" \
1023
+ ".claude/hooks/testing-tier-enforce-allowlist.txt"
1024
+ ```
1025
+
1026
+ The allowlist baseline grandfathers existing untested adopter source files for 60 days (the default grace window). Adopter operators can backfill tests as files are next touched, OR adjust the grace window per `testing-tier-config.md` § Allowlist.
1027
+
1028
+ **Composition chain** — `pre-commit-gate.sh` (Phase 2.2.5) calls `testing-tier-enforce.sh` as one of its check steps. No new PreToolUse settings.json entry is needed; the hook fires whenever pre-commit-gate fires (PreToolUse Edit|Write|MultiEdit). This is the chained-composition pattern: Phase 2.2.5 wired the parent gate; Phase 2.2.8 plugs the tier-enforce check into the existing chain.
1029
+
1030
+ **Goal-vs-implementation note** — the goal 2026-06-18b WU-4 acceptance describes `testing-tier-enforce.sh` as "PreToolUse Edit|Write" wiring. The actual wiring is via pre-commit-gate composition (no separate matcher entry). Documented per the actual implementation.
1031
+
1032
+ **Rule propagation** — `.claude/rules/testing-tier-config.md` + `.claude/rules/test-list-discipline.md` + `.claude/rules/test-sufficiency.md` load via `additionalDirectories`. No copy needed.
1033
+
1034
+ **Override path** — `SKIP_TESTING_TIER_ENFORCE=1 <command>` (logged via trace-helper). Use only for one-shot migration scripts that bootstrap test files into the spine.
1035
+
1036
+ **Verify the wiring:**
1037
+
1038
+ ```bash
1039
+ # Sanity: hook + allowlist reachable
1040
+ ls -l .claude/hooks/testing-tier-enforce.sh .claude/hooks/testing-tier-enforce-allowlist.txt
1041
+
1042
+ # Sanity: pre-commit-gate composes testing-tier-enforce
1043
+ grep -q "testing-tier-enforce.sh" .claude/hooks/pre-commit-gate.sh \
1044
+ && echo "tier-enforce composed in pre-commit-gate" \
1045
+ || echo "NOT composed (pre-commit-gate may be stale; resync bassclef)"
1046
+
1047
+ # Sanity: testing-tier-config rule reachable
1048
+ test -f ../bassclef/.claude/rules/testing-tier-config.md && echo "tier-config rule reachable" || echo "NOT reachable"
1049
+ ```
1050
+
1051
+ The `testing.path_matchers` seed (per-tech-stack defaults) lands in Phase 2.3.7 below — that is the step the goal flagged for operator-eyes design choices.
1052
+
1053
+ Source: bassclef#1143 (this wiring step — T10, iteration-2 BLOCKER). Sister rules: `.claude/rules/testing-tier-config.md` + `.claude/rules/test-list-discipline.md` + `.claude/rules/test-sufficiency.md`. Pairs with Phase 2.2.5 (pre-commit-gate parent chain).
1054
+
1055
+ ##### 2.2.9 Verify test-discipline rules propagate to the adopter session (bassclef#1144 T11)
1056
+
1057
+ The two test-discipline rules propagate to the adopter session on their own. Those rules are `.claude/rules/test-list-discipline.md` (Beck's test list as planning artifact) and `.claude/rules/test-sufficiency.md` (10-criterion rubric per Myers + Hoare).
1058
+
1059
+ The `additionalDirectories: ["../bassclef"]` entry that Phase 2.2 wrote does the work. It tells Claude Code to load every file under bassclef's `.claude/rules/` into the adopter's session context at session-start.
1060
+
1061
+ No copy needed. No per-rule symlink needed.
1062
+
1063
+ This step is the explicit verification — without it, the install is silent and the adopter does not know the rules are live.
1064
+
1065
+ Verify the rules are reachable + loaded:
1066
+
1067
+ ```bash
1068
+ # Sanity: both rule files reachable via additionalDirectories
1069
+ test -f ../bassclef/.claude/rules/test-list-discipline.md \
1070
+ && echo "test-list-discipline reachable" \
1071
+ || echo "test-list-discipline NOT reachable"
1072
+ test -f ../bassclef/.claude/rules/test-sufficiency.md \
1073
+ && echo "test-sufficiency reachable" \
1074
+ || echo "test-sufficiency NOT reachable"
1075
+
1076
+ # Sanity: settings.json carries the additionalDirectories entry
1077
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
1078
+ dirs=d.get('permissions',{}).get('additionalDirectories',[]); \
1079
+ print('bassclef loaded via additionalDirectories' if '../bassclef' in dirs else 'NOT loaded — check Phase 2.2 wiring')"
1080
+ ```
1081
+
1082
+ **What this gives the adopter** — every adopter session that touches a test file gets the Beck test-list discipline (open with `# test-list:` block; mark `[x]` as each assertion ships; pending `[ ]` lines are honest in-flight signal) AND the 10-criterion sufficiency rubric (branch coverage / exit-code matrix / matcher coverage / override paths / stdin contract / external-binary handling / trap cleanup / stderr format / state-file interactions). Together they catch tests that pass but miss the intended assertion — the failure class eugene-supplements hit when 26 tests landed after source with zero asserted intent visible.
1083
+
1084
+ **Composition** — the rules pair with Phase 2.2.5 (`pre-commit-gate.sh` parent chain) + Phase 2.2.8 (`testing-tier-enforce.sh` Tier 0/1/2/3 mechanical enforcer). Methodology layer (rules) supplies the why; mechanical layer (hooks) fires at the moment of failure.
1085
+
1086
+ **No override needed** — rules are read-only context; they neither block writes nor produce stderr. Adopters can choose to ignore the rules per session, but the agent's own self-check during artifact-write keeps them honest.
1087
+
1088
+ Source: bassclef#1144 (this propagation step — T11, iteration-2 BLOCKER). Sister rules: `.claude/rules/test-list-discipline.md` + `.claude/rules/test-sufficiency.md`. Pairs with Phase 2.2.8 (the enforcer the rules guide).
1089
+
1090
+ ##### 2.2.10 Wire role-entry-gate PreToolUse hook (bassclef#1145 T13)
1091
+
1092
+ **Lite adopter note (bassclef#740 Task 1.2).** This hook ships at `tier: standard`. Skip this step if your tier is lite. Resolve tier via `lib/tier-check.sh`.
1093
+
1094
+ Symlink the bassclef PreToolUse hook that catches source-code edits when the matching agent role has not been entered for the current session. MVP scope: Builder role only (matches source files in common languages). Other roles (Architect, Reviewer, Designer) deferred to a follow-on per goal narrowing — the multi-role matching matrix deserves its own design pass.
1095
+
1096
+ The hook reads `$CWD/state/markers/role/<role>/<session_id>.marker`. The marker is written by the agent once the role's persona has been loaded (sequential mode: Claude reads `~/.claude/agents/builder.md`, writes the marker, proceeds with the role's discipline loaded; orchestrator-gated mode: Orchestrator routes the dispatch to the Builder agent which writes its own marker before proceeding).
1097
+
1098
+ Symlink the hook:
1099
+
1100
+ ```bash
1101
+ mkdir -p .claude/hooks
1102
+ ln -sf "../../../bassclef/.claude/hooks/role-entry-gate.sh" \
1103
+ ".claude/hooks/role-entry-gate.sh"
1104
+ ```
1105
+
1106
+ Add to the PreToolUse Edit|Write|MultiEdit matcher block in `.claude/settings.json`. The matcher likely exists from earlier Phase 2.x steps (substrate-clarity-gate from 2.2.2, pattern-annotation-validate from 2.2.4, the parent gate chain from 2.2.5) — append `role-entry-gate` to its `hooks` array; do not duplicate the matcher entry.
1107
+
1108
+ ```jsonc
1109
+ {
1110
+ "hooks": {
1111
+ "PreToolUse": [
1112
+ {
1113
+ "matcher": "Edit|Write|MultiEdit",
1114
+ "hooks": [
1115
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/role-entry-gate.sh" }
1116
+ ]
1117
+ }
1118
+ ]
1119
+ }
1120
+ }
1121
+ ```
1122
+
1123
+ **MVP scope** — the hook fires on source-code file extensions only (`.ts`, `.tsx`, `.js`, `.jsx`, `.py`, `.rb`, `.go`, `.rs`, `.java`, `.kt`, `.swift`, `.cs`, `.cpp`, `.c`, `.h`). Non-source paths pass through. Tightening per `tech_stack.lang` reading (so a Python adopter only fires on `.py`) is a follow-on; the safe default union catches the common cases.
1124
+
1125
+ **Marker writing** — the agent writes the marker once it enters the role. Marker shape:
1126
+
1127
+ ```json
1128
+ {
1129
+ "role": "builder",
1130
+ "session_id": "<uuid from harness>",
1131
+ "entered_at": "<ISO 8601 UTC>"
1132
+ }
1133
+ ```
1134
+
1135
+ A future `/role-entry` skill automates the marker write + persona load; until then, the agent writes the marker by hand after reading the persona file. The BLOCK message documents the exact one-liner.
1136
+
1137
+ **Composition rule** — when the Edit|Write|MultiEdit matcher block already exists, append `role-entry-gate` to its `hooks` array. Order within the array: place `role-entry-gate` AFTER substrate-clarity-gate + artifact-ingestion-gate + pattern-annotation-validate + pre-commit-gate + state-validate + multi-tenancy-check, so structural / format / state validation fires first; role-discipline check fires last. Role-entry is the methodology gate, not the structural gate — it should not run before the cheap structural checks pass.
1138
+
1139
+ **Override path** — `SKIP_ROLE_ENTRY=1 <command>` (logged via trace-helper). Use for one-shot scripts the operator runs without entering an agent role, OR when the adopter is on a workflow that does not map to bassclef's role taxonomy.
1140
+
1141
+ **Verify the wiring:**
1142
+
1143
+ ```bash
1144
+ # Sanity: hook executable + reachable
1145
+ ls -l .claude/hooks/role-entry-gate.sh
1146
+
1147
+ # Sanity: agent personas reachable via additionalDirectories (the BLOCK message refers to them)
1148
+ test -f ~/.claude/agents/builder.md && echo "builder persona reachable" || echo "builder persona NOT reachable (user-level agents missing)"
1149
+
1150
+ # Sanity: settings.json has the hook wired under the Edit matcher
1151
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
1152
+ hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PreToolUse',[]) if 'Edit' in entry.get('matcher','') for h in entry.get('hooks',[])]; \
1153
+ print('role-entry-gate wired' if any('role-entry-gate' in c for c in hooks) else 'NOT wired')"
1154
+ ```
1155
+
1156
+ **Not yet wired in bassclef** — this hook ships to the adopter via `/onboard-repo` first. Bassclef's own settings.json adds the hook in a follow-on once the marker convention has run in the wild for a few sessions and the multi-role matching follow-on lands.
1157
+
1158
+ Source: bassclef#1145 (this hook step — T13). Sister agent personas: `~/.claude/agents/builder.md` (MVP target) + `~/.claude/agents/architect.md` + `~/.claude/agents/reviewer.md` + `~/.claude/agents/designer.md` (follow-on coverage). Follow-on tickets: multi-role matching matrix + `/role-entry` skill + `state/markers/role/` schema authoring under `standards/state-spine.md`.
1159
+
1160
+ ##### 2.2.11 Wire temperance-gate PreToolUse hook (bassclef#1147 T15)
1161
+
1162
+ Symlink the bassclef hook that fires the /temperance question at decision boundaries. MVP scope: one active trigger — "high-impact path edit". It catches edits to `.claude/`, `standards/`, `lib/state.sh`, and SKILL.md files when the session has no fresh temperance marker. Other triggers stay as stubs. Follow-on tickets will design them.
1163
+
1164
+ <!-- FKGL-EXEMPT: source ticket enumeration -->
1165
+ Sources: `2026-06-18b` WU-7 goal (explain-mode entry, chain-anchor entry, non-stacked-branch first edit, post-compaction recovery); ticket #1147 (N consecutive edits, substrate proposal without catalog browse, diagnose-repro marker absence).
1166
+ <!-- /FKGL-EXEMPT -->
1167
+
1168
+ The hook pairs with `pre-commit-gate.sh` from Phase 2.2.5. That hook handles the first edit on a branch that stands alone. This hook adds one more check. It catches edits to high-impact paths that the other hook misses.
1169
+
1170
+ Symlink the hook:
1171
+
1172
+ ```bash
1173
+ mkdir -p .claude/hooks
1174
+ ln -sf "../../../bassclef/.claude/hooks/temperance-gate.sh" \
1175
+ ".claude/hooks/temperance-gate.sh"
1176
+ ```
1177
+
1178
+ Add to the PreToolUse Edit|Write|MultiEdit matcher block in `.claude/settings.json`. The matcher likely already exists; append `temperance-gate` to its `hooks` array. Order: place `temperance-gate` AFTER structural checks (substrate-clarity-gate, artifact-ingestion-gate, pattern-annotation-validate, pre-commit-gate, state-validate, multi-tenancy-check) and AFTER role-entry-gate. Methodology gate fires last; structural + role checks fire first.
1179
+
1180
+ ```jsonc
1181
+ {
1182
+ "hooks": {
1183
+ "PreToolUse": [
1184
+ {
1185
+ "matcher": "Edit|Write|MultiEdit",
1186
+ "hooks": [
1187
+ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/temperance-gate.sh" }
1188
+ ]
1189
+ }
1190
+ ]
1191
+ }
1192
+ }
1193
+ ```
1194
+
1195
+ **Marker convention** — the hook reads `$CWD/state/markers/temperance/<session_id>.marker` (session-scoped) AND falls back to any existing marker under `state/markers/temperance/` (loose branch-scoped check; tighter matching-branch check is a follow-on). The temperance marker is written by the agent (or by `/temperance` skill invocation) once the right-thing / right-way questions have been answered.
1196
+
1197
+ **Marker shape:**
1198
+
1199
+ ```json
1200
+ {
1201
+ "session_id": "<uuid from harness>",
1202
+ "entered_at": "<ISO 8601 UTC>",
1203
+ "question_answered": "yes"
1204
+ }
1205
+ ```
1206
+
1207
+ **Override path** — `SKIP_TEMPERANCE_GATE=1 <command>` (logged via trace-helper). Use sparingly; the trigger is the point of the discipline.
1208
+
1209
+ **Verify the wiring:**
1210
+
1211
+ ```bash
1212
+ # Sanity: hook executable + reachable
1213
+ ls -l .claude/hooks/temperance-gate.sh
1214
+
1215
+ # Sanity: temperance skill reachable via additionalDirectories
1216
+ test -f ../bassclef/.claude/skills/temperance/SKILL.md && echo "temperance skill reachable" || echo "NOT reachable"
1217
+
1218
+ # Sanity: settings.json has the hook wired under the Edit matcher
1219
+ python3 -c "import json; d=json.load(open('.claude/settings.json')); \
1220
+ hooks=[h.get('command','') for entry in d.get('hooks',{}).get('PreToolUse',[]) if 'Edit' in entry.get('matcher','') for h in entry.get('hooks',[])]; \
1221
+ print('temperance-gate wired' if any('temperance-gate' in c for c in hooks) else 'NOT wired')"
1222
+ ```
1223
+
1224
+ **Not yet wired in bassclef** — adopters first; bassclef's own settings.json adds the hook in a follow-on once the MVP trigger has run in the wild for a few sessions and the trigger-taxonomy follow-on lands.
1225
+
1226
+ Source: bassclef#1147 (this hook step — T15). Sister skill: `.claude/skills/temperance/SKILL.md` (the methodology this gate enforces mechanically). Follow-on tickets: explain-mode + chain-anchor + post-compaction + non-stacked first edit + N consecutive edits + substrate proposal + diagnose-repro triggers + `standards/temperance-discipline.md` documenting the full trigger taxonomy.
1227
+
1228
+ ##### 2.3 Create the project config file
1229
+
1230
+ Copy from `presence/install/substrate-config.md`, then prompt operator
1231
+ for:
1232
+
1233
+ - `journal_doc_id` (optional — create Google Doc now or later)
1234
+ - `changelog_doc_id` (optional)
1235
+ - `deploy_targets` (local/staging/prod booleans)
1236
+ - `budget.session_ceiling` / `iteration_ceiling` (sequential mode → `$0`)
1237
+ - `hosting_platform` (REQUIRED for any consumer that ships a UI). Default
1238
+ to `amplify` per ADR-008 + app-consumer precedent. Set `none` for
1239
+ docs-only / no-UI repos. Enum:
1240
+ `amplify | ec2-tailscale | vercel | netlify | cloudflare-pages | none`.
1241
+ `/bind-subdomain` + `/launch-preview` refuse on `none` unless
1242
+ `BIND_SUBDOMAIN_OVERRIDE=1` is set. Closes bassclef#449.
1243
+
1244
+ If operator says "defer", scaffold with placeholders and `TODO:` markers
1245
+ so next session surfaces them.
1246
+
1247
+ ##### 2.3.5 Write the bassclef-configs adopter-default seed (bassclef#1135 WU-4)
1248
+
1249
+ Write `.claude/bassclef-configs.jsonc` with the adopter-default seed.
1250
+ Every downstream hook + skill reads from this file; without it, defaults
1251
+ are invisible to the adopter. Phase 2.5 (security_scanning) appends to
1252
+ this file later in the same Procedure run, so the foundation MUST land
1253
+ first.
1254
+
1255
+ ```bash
1256
+ cat > .claude/bassclef-configs.jsonc <<'JSONC'
1257
+ // bassclef-configs.jsonc — unified adopter configuration discovery surface
1258
+ //
1259
+ // Edit the values below. Each block has a comment explaining what it does,
1260
+ // what the default is, and which bassclef substrate component reads it.
1261
+ //
1262
+ // JSON Schema: standards/state-spine/schemas/bassclef-configs.schema.json
1263
+ // Full reference: standards/bassclef-configs-schema.md
1264
+ {
1265
+ // Whether bassclef's substrate syncs from upstream at SessionStart.
1266
+ // Adopter default: false (opt-in trust posture). Turn on when you want
1267
+ // bassclef to keep itself current automatically.
1268
+ // Pairs with: .claude/hooks/bassclef-sync.sh
1269
+ "sync": { "enabled": false },
1270
+
1271
+ // Per-agent LLM model routing. Most agents stay cheap-fast; Builder
1272
+ // gets balanced for code quality; Shaper gets capable for problem framing.
1273
+ // Pairs with: .claude/agents/<role>.md frontmatter
1274
+ "model_tier": {
1275
+ "Builder": "balanced",
1276
+ "Closer": "cheap-fast",
1277
+ "Deployer": "cheap-fast",
1278
+ "DocsWriter": "cheap-fast",
1279
+ "Explorer": "cheap-fast",
1280
+ "Orchestrator": "cheap-fast",
1281
+ "Reviewer": "cheap-fast",
1282
+ "Shaper": "capable"
1283
+ },
1284
+
1285
+ // Adopter plan tier. Caps parallel agent dispatch per Anthropic plan limits.
1286
+ // tier-1 = single-agent serial (safest); tier-2 = 2-3 parallel after upgrade;
1287
+ // tier-4 = 5+ parallel for operator-scale workflows.
1288
+ // Pairs with: .claude/skills/longrun/SKILL.md parallel-dispatch logic
1289
+ "plan_tier": "tier-1",
1290
+
1291
+ // Plain-English discipline for agent reply prose to operator. When true,
1292
+ // the Stop hook scans the agent's last message for bassclef-internal
1293
+ // jargon and surfaces findings to stderr (advisory). 'strict' makes the
1294
+ // hook block the stop until prose is clean. false disables the scan.
1295
+ //
1296
+ // Optional: kiss_words_strict_places escalates specific places to strict
1297
+ // even when the global toggle stays advisory. V1 places (bassclef#1171):
1298
+ // - closeout_blocks — matches a "## Closeout audit" heading
1299
+ // - summary_tables — matches a "## Status" heading
1300
+ // - next_pickup — matches a "## Next pickup" heading
1301
+ // Adopter default: advisory everywhere; no strict places. Flip to strict
1302
+ // or add places when you want the rule to actually catch you.
1303
+ //
1304
+ // Pairs with: .claude/rules/plain-english-discipline.md
1305
+ // .claude/hooks/turn-prose-kiss-check.sh
1306
+ "prose_discipline": {
1307
+ "kiss_words_turn_prose": true
1308
+ // "kiss_words_strict_places": ["closeout_blocks"] // uncomment when ready
1309
+ },
1310
+
1311
+ // Per-path testing-tier enforcement (4 tiers: 0 strict TDD / 1 test-with /
1312
+ // 2 smoke / 3 manual verify). global_floor applies when no path_matcher matches.
1313
+ // Adopter default: floor=1 (WARN on missing test); empty matchers (add your own).
1314
+ // Pairs with: .claude/rules/testing-tier-config.md
1315
+ "testing": {
1316
+ "global_floor": 1,
1317
+ "path_matchers": []
1318
+ }
1319
+
1320
+ // Phase 2.5 below appends "security_scanning" here.
1321
+ // /onboard-repo Phase 2.x or future tickets append "tech_stack", "hook_chain",
1322
+ // "discipline_layers" as those vocabularies stabilize. Per bassclef#1135.
1323
+ }
1324
+ JSONC
1325
+ ```
1326
+
1327
+ Validate the written file against the schema:
1328
+
1329
+ ```bash
1330
+ perl -pe 's,^\s*//.*$,,; s,/\*.*?\*/,,g' .claude/bassclef-configs.jsonc \
1331
+ | ajv validate -s ~/src/sunj-labs/bassclef/standards/state-spine/schemas/bassclef-configs.schema.json \
1332
+ -d - --spec=draft2020 -c ajv-formats
1333
+ ```
1334
+
1335
+ Expect a single `valid` line. The file is now the single source of truth for adopter configuration; subsequent Phase 2.x steps APPEND to it (Phase 2.3.6 tech_stack, Phase 2.5 security_scanning, future T-tickets for hook_chain etc.) rather than rewriting from empty.
1336
+
1337
+ If the adopter has a pre-existing `.claude/bassclef-configs.jsonc` from a prior SENTINEL_bassclef_version, prompt before overwriting; offer a `--merge` flow (operator-driven; not automated in V1).
1338
+
1339
+ ##### 2.3.6 Discover and write the tech_stack block (bassclef#1127 WU-2)
1340
+
1341
+ Stack-bound rules + standards in bassclef (`api-conventions.md`, `sdlc-gates.md`, `schema-management.md`, `new-dependency-check.md`, `frontend-stack.md`, `deployment-topology.md`) read `tech_stack:` from `.claude/bassclef-configs.jsonc` to route per-stack discipline. Without the block, non-matching adopters see false-fires (Prisma rules at SQLAlchemy adopters, Next.js API conventions at FastAPI adopters, etc.). This step writes the block so the bleed stops at install time.
1342
+
1343
+ Schema reference: `standards/state-spine/schemas/tech-stack.schema.json`. Standard: `standards/tech-stack-config.md`.
1344
+
1345
+ **Auto-detection signals** (per `standards/tech-stack-config.md` § Auto-detection at `/onboard-repo`):
1346
+
1347
+ | Build file present | Suggests |
1348
+ |---|---|
1349
+ | `package.json` with `"next"` dep | `frontend: nextjs`, `lang: typescript` (if `tsconfig.json` also present) |
1350
+ | `package.json` with `"react"` dep (no `next`) | `frontend: react-vite` (if `vite.config.*` present) |
1351
+ | `package.json` with `"prisma"` dep | `orm: prisma` |
1352
+ | `package.json` with `"drizzle-orm"` dep | `orm: drizzle` |
1353
+ | `package.json` + `pnpm-lock.yaml` | `pkg_manager: pnpm` |
1354
+ | `package.json` + `yarn.lock` | `pkg_manager: yarn` |
1355
+ | `package.json` + `package-lock.json` | `pkg_manager: npm` |
1356
+ | `pyproject.toml` or `requirements.txt` | `lang: python` |
1357
+ | `pyproject.toml` with `sqlalchemy` | `orm: sqlalchemy` |
1358
+ | `pyproject.toml` with `alembic` | `orm: alembic` |
1359
+ | `pyproject.toml` with `fastapi` | `frontend: fastapi`, `backend: python` |
1360
+ | `pyproject.toml` with `django` | `frontend: django`, `backend: python` |
1361
+ | `Gemfile` | `lang: ruby`, `pkg_manager: gem` |
1362
+ | `Gemfile` with `rails` | `frontend: rails`, `backend: ruby` |
1363
+ | `go.mod` | `lang: go`, `pkg_manager: go-mod`, `backend: go` |
1364
+ | `Cargo.toml` | `lang: rust`, `pkg_manager: cargo`, `backend: rust` |
1365
+ | `vercel.json` or `.vercelrc` | `deploy: vercel` |
1366
+ | `fly.toml` | `deploy: fly` |
1367
+ | `netlify.toml` | `deploy: netlify` |
1368
+ | `render.yaml` | `deploy: render` |
1369
+
1370
+ No signal → field stays `none` and waits for operator override.
1371
+
1372
+ **Operator prompt** (surface this exact text):
1373
+
1374
+ ```
1375
+ Detected stack signals:
1376
+
1377
+ lang: <detected or "(no signal)">
1378
+ frontend: <detected or "(no signal)">
1379
+ backend: <detected or "(no signal)">
1380
+ orm: <detected or "(no signal)">
1381
+ pkg_manager: <detected or "(no signal)">
1382
+ deploy: <detected or "(no signal)">
1383
+
1384
+ Stack-bound rules route per-adopter from this block. Without it,
1385
+ non-matching adopters see false-fires from rules written for the
1386
+ default stack (TypeScript + Next.js + Prisma + EC2-Tailscale).
1387
+
1388
+ Three paths:
1389
+
1390
+ [a] Accept detected values
1391
+ [b] Override per field (5-7 short prompts)
1392
+ [c] Skip — write declared_via: deferred and revisit later
1393
+ (rules fall back to operator defaults; expect some false-fires
1394
+ until you set the block)
1395
+
1396
+ Pick a/b/c:
1397
+ ```
1398
+
1399
+ **On accept (a):** write the block with detected values + `declared_via: onboard-repo` + `declared_at: <ISO date>`.
1400
+
1401
+ **On per-field override (b):** prompt each field in order with detected value as the default. Field-level enums per `standards/tech-stack-config.md` § Fields.
1402
+
1403
+ **On skip (c):** write `tech_stack: { declared_via: "deferred", declared_at: <ISO date> }` only. The schema admits all fields as optional; rules fall back gracefully. Operator can revisit by re-running `/onboard-repo` or editing the block directly.
1404
+
1405
+ **Append to `.claude/bassclef-configs.jsonc`** using `jq` or in-place edit; the block goes immediately before the closing brace + after `testing`:
1406
+
1407
+ ```bash
1408
+ # Pseudo-procedure; actual implementation reads the operator's path choice
1409
+ # and constructs the block from detected + override values.
1410
+ cat >> .claude/bassclef-configs.jsonc <<TECH_STACK
1411
+ ,
1412
+
1413
+ // Tech stack vocabulary. Stack-bound rules read this to route per-
1414
+ // stack discipline (schema-management.md ORM routing, api-conventions
1415
+ // frontend routing, sdlc-gates lang routing, etc.).
1416
+ // Auto-detected at /onboard-repo Phase 2.3.6; operator confirms or
1417
+ // overrides per field.
1418
+ // Pairs with: standards/tech-stack-config.md (schema + semantics),
1419
+ // standards/state-spine/schemas/tech-stack.schema.json
1420
+ // Schema reference: bassclef#1127 tech_stack discovery
1421
+ "tech_stack": {
1422
+ "lang": "<value>",
1423
+ "frontend": "<value>",
1424
+ "backend": "<value>",
1425
+ "orm": "<value>",
1426
+ "pkg_manager": "<value>",
1427
+ "deploy": "<value>",
1428
+ "declared_at": "<ISO date>",
1429
+ "declared_via": "onboard-repo"
1430
+ }
1431
+ TECH_STACK
1432
+ ```
1433
+
1434
+ (In practice the writer holds the file open as JSON-with-comments, mutates the parsed object, re-emits with comments preserved per the comment-aware JSONC editor pattern; the heredoc above is illustrative.)
1435
+
1436
+ **Validate after append:**
1437
+
1438
+ ```bash
1439
+ perl -pe 's,^\s*//.*$,,; s,/\*.*?\*/,,g' .claude/bassclef-configs.jsonc \
1440
+ | ajv validate -s ~/src/sunj-labs/bassclef/standards/state-spine/schemas/bassclef-configs.schema.json \
1441
+ -d - --spec=draft2020 -c ajv-formats
1442
+ ```
1443
+
1444
+ Expect `valid`. If the file fails to validate, revert the append, surface the schema error, and re-prompt.
1445
+
1446
+ **Acceptance:**
1447
+
1448
+ - After Phase 2.3.6 completes, `.claude/bassclef-configs.jsonc` contains a `tech_stack:` block.
1449
+ - Block validates against `tech-stack.schema.json`.
1450
+ - `declared_via` is one of `onboard-repo` (path a or b) or `deferred` (path c); `migrated` and `demo-prompt` reserved for other callers.
1451
+ - Operator can re-run `/onboard-repo` and the existing block is detected; prompt asks whether to keep, re-detect, or edit per-field.
1452
+
1453
+ **Composes with downstream Phase 2.x steps:**
1454
+
1455
+ - Phase 2.3.7 (testing-tier path_matchers seed) reads `tech_stack.lang` to pick the per-stack default tier matchers.
1456
+ - Phase 2.5 (security scanning) reads `tech_stack.lang` to seed the scanner adapter.
1457
+ - Future Phase 2.x for `hook_chain` reads stack signals to pick per-stack hook defaults.
1458
+
1459
+ ##### 2.3.7 Seed testing.path_matchers per declared tech_stack (bassclef#1143 T10)
1460
+
1461
+ Phase 2.3.5 wrote the `testing` block with `global_floor: 1` + empty `path_matchers`. Phase 2.3.6 just discovered + wrote `tech_stack`. This step reads `tech_stack.lang` and seeds `testing.path_matchers` with sensible per-stack defaults so the Tier 0 / Tier 1 enforcer (Phase 2.2.8) fires on the right paths from day one.
1462
+
1463
+ **Per-stack default seeds** (operator-eyes design choices):
1464
+
1465
+ For `tech_stack.lang: typescript` + `tech_stack.frontend: nextjs` (bassclef default):
1466
+
1467
+ ```jsonc
1468
+ "testing": {
1469
+ "global_floor": 1,
1470
+ "path_matchers": [
1471
+ { "match": "lib/domain/**/*.ts", "tier": 0 },
1472
+ { "match": "lib/infra/**/*.ts", "tier": 0 },
1473
+ { "match": "app/api/**/*.ts", "tier": 1 },
1474
+ { "match": "app/actions/**/*.ts", "tier": 1 },
1475
+ { "match": "app/_components/**/*.tsx", "tier": 2 },
1476
+ { "match": "app/**/*.tsx", "tier": 2 },
1477
+ { "match": "docs/**/*.md", "tier": 3 }
1478
+ ]
1479
+ }
1480
+ ```
1481
+
1482
+ For `tech_stack.lang: python` + `tech_stack.frontend: fastapi`:
1483
+
1484
+ ```jsonc
1485
+ "testing": {
1486
+ "global_floor": 1,
1487
+ "path_matchers": [
1488
+ { "match": "domain/**/*.py", "tier": 0 },
1489
+ { "match": "infrastructure/**/*.py", "tier": 0 },
1490
+ { "match": "api/**/*.py", "tier": 1 },
1491
+ { "match": "templates/**/*", "tier": 2 },
1492
+ { "match": "docs/**/*.md", "tier": 3 }
1493
+ ]
1494
+ }
1495
+ ```
1496
+
1497
+ Other stacks (ruby-rails, go-gin, java-spring) ship default seeds as per-stack siblings under `standards/testing-tier-seeds/<stack>.md` — deferred to a follow-on ticket when an adopter on that stack files for them. The pattern is the same: Tier 0 on the essential pure-domain layer; Tier 1 on the API + actions layer; Tier 2 on UI + template surfaces; Tier 3 on docs.
1498
+
1499
+ **On `tech_stack.lang: none` or `deferred`** — skip the seed; leave `path_matchers: []` empty. The `global_floor: 1` keeps the WARN posture; adopter can hand-edit later.
1500
+
1501
+ **Operator prompt** (surface this exact text after Phase 2.3.6 completes):
1502
+
1503
+ ```
1504
+ Tech stack: <detected lang + frontend>
1505
+
1506
+ Seed testing.path_matchers with the per-stack default for <stack>?
1507
+
1508
+ [a] Yes, accept default seed (recommended)
1509
+ [b] Show me the seed first
1510
+ [c] Skip — keep path_matchers empty (manual seed later)
1511
+
1512
+ Pick a/b/c:
1513
+ ```
1514
+
1515
+ **On accept (a)** — write the matching per-stack seed into `testing.path_matchers` in `bassclef-configs.jsonc`. Use the comment-aware JSONC editor pattern so the existing comments + `global_floor` survive.
1516
+
1517
+ **On show first (b)** — print the seed inline; ask for accept/edit/skip.
1518
+
1519
+ **On skip (c)** — leave empty; print "Adopter chose to skip — `testing.path_matchers` stays empty. Set with `/onboard-repo` re-run or hand-edit later."
1520
+
1521
+ **Validate after write:**
1522
+
1523
+ ```bash
1524
+ perl -pe 's,^\s*//.*$,,; s,/\*.*?\*/,,g' .claude/bassclef-configs.jsonc \
1525
+ | ajv validate -s ~/src/sunj-labs/bassclef/standards/state-spine/schemas/bassclef-configs.schema.json \
1526
+ -d - --spec=draft2020 -c ajv-formats
1527
+ ```
1528
+
1529
+ Expect a single `valid` line. The `testing.path_matchers` items must validate against the bassclef-configs schema.
1530
+
1531
+ **Stack siblings deferred** — only typescript-nextjs + python-fastapi ship in this step. Adopters on ruby-rails / go-gin / java-spring / rust-axum etc. get `global_floor: 1` + empty matchers (the safe default WARN posture) and can hand-seed per their layout. A follow-on ticket adds per-stack seed siblings under `standards/testing-tier-seeds/<stack>.md` as adopters request them.
1532
+
1533
+ Source: bassclef#1143 (this seed step — T10, iteration-2 BLOCKER). Sister rule: `.claude/rules/testing-tier-config.md`. Pairs with Phase 2.2.8 (the enforcer that reads this seed).
1534
+
1535
+ ##### 2.3.8 Surface the wiki_sync toggle (bassclef#1265 — Phase 1 stub)
1536
+
1537
+ The `wiki_sync` block in `.claude/bassclef-configs.jsonc` controls whether the adopter's repo gets the bassclef-docs-sync workflow vendored from `presence/install/bassclef-docs-sync.template.yml`. This step surfaces the toggle so adopters see the option exists.
1538
+
1539
+ **Phase 1 (this step — bet 2026-06-20b) — minimal stub:**
1540
+
1541
+ Read the adopter's `wiki_sync.enabled` value from the bassclef-configs file just written. Print one of the following:
1542
+
1543
+ - **`enabled: false`** (default — universal): print
1544
+ > Wiki-sync is OFF. The wiki-as-we-go discipline rule still loads at session-start, but no workflow fires. To enable later: edit `.claude/bassclef-configs.jsonc` `wiki_sync.enabled` to `true` and re-run `/onboard-repo` for the vendoring step (full vendoring lands in Phase 2 of bet 2026-06-20b — see `docs/iteration-bets/2026-06-20b-cold-adopter-mechanism-inheritance.md`).
1545
+ - **`enabled: true`**: print
1546
+ > Wiki-sync is ON. Full vendoring of `presence/install/bassclef-docs-sync.template.yml` → `.github/workflows/bassclef-docs-sync.yml` + `presence/install/docs-sync-allowlist.template.md` → `standards/docs-sync-allowlist.md` is **deferred to Phase 2 of bet 2026-06-20b**. For now, copy both templates manually if you need the wiki to update from main pushes today.
1547
+
1548
+ **Phase 2 (next session) — full vendoring (deferred):**
1549
+
1550
+ - Auto-copy both templates into the adopter's repo
1551
+ - Open a follow-up PR for GHA secrets if `target_wiki_repo` requires non-default auth
1552
+ - Walk the adopter through the three workflow-edit surfaces (the `on.push.paths` glob, the Filter step's case-glob, the Sync step's source-path → wiki-page mapping)
1553
+ - Smoke-test by pushing a docs change and verifying the wiki updates
1554
+
1555
+ The Phase 1 stub keeps this step minimal so the goal stays bounded. Adopters who want wiki-sync today can copy the templates by hand from bassclef's `presence/install/` (workflows + cold-adopter assets).
1556
+
1557
+ **Composes with:**
1558
+
1559
+ - `.claude/skills/docs-sync/SKILL.md` § Adopter mode (read at runtime; surfaces BLOCK if templates not vendored)
1560
+ - `.claude/rules/session-artifacts.md` § Wiki-as-we-go discipline (methodology rule that triggers the closeout question)
1561
+
1562
+ Source: bassclef#1265 (this surfacing step — wiki-sync mechanism adopter inheritance). Sister bet: `docs/iteration-bets/2026-06-20b-cold-adopter-mechanism-inheritance.md` WU-2.
1563
+
1564
+ ##### 2.3.9 Write the onboard-repo state marker (bassclef-upstream#664)
1565
+
1566
+ Write `.claude/state/onboard-repo.state.json` after Phase 2.3 has
1567
+ populated the config file(s). The marker captures which mode was used,
1568
+ the ISO timestamp, and which files were written. The configs UI reads
1569
+ this marker to render the first-run overlay (per canvas 2026-07-08
1570
+ Appendix C). Future audits trace how a repo got wired.
1571
+
1572
+ Re-runs append a history entry — the marker keeps prior runs, not
1573
+ clobber them.
1574
+
1575
+ Invoke the helper script that ships alongside this SKILL:
1576
+
1577
+ ```bash
1578
+ # ONBOARD_MODE is set earlier in Procedure —
1579
+ # default / --with-deploy-host / --with-secrets / --full / --greenfield-from-intent
1580
+ bash .claude/skills/onboard-repo/write-state-marker.sh "${ONBOARD_MODE:-default}"
1581
+ ```
1582
+
1583
+ The script writes / updates `.claude/state/onboard-repo.state.json`
1584
+ with shape `{mode, timestamp_iso, paths_written[], history[], skill_version}`.
1585
+ Tier 0 tests at `.claude/skills/onboard-repo/tests/write-state-marker.test.sh`
1586
+ pin all behavior including re-run history append.
1587
+
1588
+ **Composes with:**
1589
+
1590
+ - Configs UI first-run overlay (`bassclef/configs/`, bet 2026-07-08a WU-7)
1591
+ - Future audits reading `.claude/state/onboard-repo.state.json`
1592
+
1593
+ Source: bassclef-upstream#664 (state marker for onboard-repo). Part of goal
1594
+ `docs/iteration-bets/2026-07-08a-configs-ui-iteration-1.md` WU-4.
1595
+
1596
+ ##### 2.3.10 Wire the cron installer for auto-save (bassclef-upstream#941)
1597
+
1598
+ Goal 27b (PR #937) shipped `presence/install/schedule-auto-save.cron.sh` at
1599
+ `tier: lite`. The installer writes a cron entry that fires
1600
+ `save-state.sh --auto --quiet` on idle. Sam's magic demo per canvas
1601
+ `docs/canvases/2026-07-19-bassclef-lite.md` L67-71 promises session-state
1602
+ continuity as part of onboarding. This step wires the installer in.
1603
+
1604
+ Behavior per OS:
1605
+
1606
+ - **macOS + Linux + WSL** — run the installer directly. It appends one
1607
+ crontab entry and is idempotent (re-running is safe per PR #937 Test 4).
1608
+ - **Native Windows (no WSL)** — cron is not available. Point the adopter at
1609
+ `presence/install/schedule-auto-save.taskscheduler.md` for the manual
1610
+ Task Scheduler setup.
1611
+
1612
+ ```bash
1613
+ OS_KIND="$(uname -s)"
1614
+ case "$OS_KIND" in
1615
+ Darwin|Linux)
1616
+ if [ -x "$BASSCLEF_DIR/presence/install/schedule-auto-save.cron.sh" ]; then
1617
+ bash "$BASSCLEF_DIR/presence/install/schedule-auto-save.cron.sh" \
1618
+ || echo "WARNING: cron installer failed; run manually to enable auto-save" >&2
1619
+ else
1620
+ echo "WARNING: schedule-auto-save.cron.sh not found; skip auto-save wire" >&2
1621
+ fi
1622
+ ;;
1623
+ MINGW*|CYGWIN*|MSYS*)
1624
+ echo "Native Windows detected. Cron is not available."
1625
+ echo "See presence/install/schedule-auto-save.taskscheduler.md for the"
1626
+ echo "manual Task Scheduler setup that wires auto-save on Windows."
1627
+ ;;
1628
+ *)
1629
+ echo "Unknown OS '$OS_KIND'; skip auto-save wire"
1630
+ ;;
1631
+ esac
1632
+ ```
1633
+
1634
+ The installer prints its own progress. Failures do not stop onboarding —
1635
+ auto-save is a nice-to-have on first install, and adopters can re-run
1636
+ the installer any time via `bash $BASSCLEF_DIR/presence/install/schedule-auto-save.cron.sh`.
1637
+
1638
+ **Composes with:**
1639
+
1640
+ - `presence/install/schedule-auto-save.cron.sh` (PR #937 — the cron installer)
1641
+ - `presence/install/schedule-auto-save.taskscheduler.md` (PR #937 — Windows guide)
1642
+ - ADR-046 cross-OS scheduling (accepted; Option C picked)
1643
+
1644
+ Source: bassclef-upstream#941 (wire cron into onboard for Sam's magic demo).
1645
+
1646
+ ##### 2.4 Point at bassclef's source (URL + auth)
1647
+
1648
+ The thin-pointer dispatcher installed in 2.1 reads `.bassclef-source.json`
1649
+ for the upstream tarball URL + auth metadata when the curl fallback
1650
+ fires (Cloud Env / mobile / fresh sandboxes / CI). Schema codified at
1651
+ `standards/bassclef-source-config.md`; validation hook at
1652
+ `.claude/hooks/bassclef-source-config-validate.sh`.
1653
+
1654
+ Before writing the file, prompt the adopter explicitly (per bassclef#939):
1655
+
1656
+ ```
1657
+ Bassclef can auto-pull the latest skills, rules, and agents into this
1658
+ repo at every Claude Code session start. Keeps you current with bassclef's
1659
+ evolution; you never manually update.
1660
+
1661
+ Off by default. Turn on now? [y/N]
1662
+ ```
1663
+
1664
+ Persist the adopter's answer to the `auto_sync` field. Default is `false` when in doubt.
1665
+
1666
+ ```bash
1667
+ cat > .bassclef-source.json <<JSON
1668
+ {
1669
+ "source_url": "https://api.github.com/repos/sunj-labs/bassclef/tarball/main",
1670
+ "auth_method": "Bearer",
1671
+ "token_env": "BASSCLEF_TOKEN",
1672
+ "auto_sync": false,
1673
+ "comment": "Provider: GitHub repo tarball API. Auth: fine-grained PAT 'bassclef-substrate-read' (Contents:read on sunj-labs/bassclef), stored as BASSCLEF_TOKEN in the consumer's Cloud Code environment. Schema: standards/bassclef-source-config.md. auto_sync controls whether SessionStart fetches bassclef's latest substrate; off by default per bassclef#939."
1674
+ }
1675
+ JSON
1676
+ ```
1677
+
1678
+ **Operator action — out of band, one-time per consumer:**
1679
+
1680
+ 1. Create fine-grained GitHub PAT scoped to `sunj-labs/bassclef`
1681
+ - Permission: Contents: read
1682
+ - Expiry: 90 days
1683
+ - Name: `bassclef-read-{REPO_SHORT}-{YYYYMMDD}`
1684
+ 2. Paste into the consumer's Cloud Code environment as `BASSCLEF_TOKEN`.
1685
+ 3. Track the PAT lifecycle in operator's `substrate.secrets.md` per
1686
+ `standards/secrets-lifecycle.md`.
1687
+
1688
+ Provider alternatives (see `standards/bassclef-source-config.md` for
1689
+ GitHub public, GitHub private release, ADO Repos, GitLab examples).
1690
+
1691
+ After writing the file, run the hook once locally to validate via
1692
+ sibling fast-path (no PAT needed for desktop):
1693
+
1694
+ ```bash
1695
+ echo "{\"cwd\":\"$PWD\"}" | bash .claude/hooks/bassclef-sync.sh
1696
+ ```
1697
+
1698
+ Expect: `### BASSCLEF SUBSTRATE — SYNCED` banner; skills/rules/hooks/
1699
+ luminaries symlinked into `.claude/`; no errors surfaced.
1700
+
1701
+ ##### 2.5 Configure security scanning (per bassclef goal 2026-06-17a Phase Y)
1702
+
1703
+ Auto-detect the adopter's stack from build files, confirm with the operator, and write the `security_scanning` block to `.claude/bassclef-configs.jsonc`. Adopter copies the matching reference workflow next.
1704
+
1705
+ Auto-detection signals (per `standards/security-scanner-adapter.md` § Stack auto-detection):
1706
+
1707
+ ```bash
1708
+ # Detect stack by build-file signal.
1709
+ STACK_GUESS="custom"
1710
+ if [ -f "package.json" ]; then
1711
+ if grep -q '"typescript"\|"@types/' package.json; then
1712
+ STACK_GUESS="node-typescript"
1713
+ else
1714
+ STACK_GUESS="node-typescript" # TS opt-out can happen later
1715
+ fi
1716
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
1717
+ STACK_GUESS="python"
1718
+ elif [ -f "Gemfile" ] || ls *.gemspec >/dev/null 2>&1; then
1719
+ STACK_GUESS="ruby"
1720
+ elif [ -f "pom.xml" ] || [ -f "build.gradle" ] || ls **/*.java >/dev/null 2>&1; then
1721
+ STACK_GUESS="java"
1722
+ elif [ -f "go.mod" ] || ls **/*.go >/dev/null 2>&1; then
1723
+ STACK_GUESS="go"
1724
+ fi
1725
+ ```
1726
+
1727
+ Prompt the operator (sample):
1728
+
1729
+ ```
1730
+ Bassclef security scanner adapter detected stack: <STACK_GUESS>
1731
+
1732
+ Default scanners for this stack (per standards/security-scanner-adapter.md):
1733
+ - node-typescript: eslint-plugin-security + audit-ci + gitleaks
1734
+ - python: bandit + safety + gitleaks
1735
+ - ruby: brakeman + bundler-audit + gitleaks
1736
+ - java: spotbugs-security + dependency-check + gitleaks
1737
+ - go: gosec + govulncheck + gitleaks
1738
+ - custom: adopter-supplied list
1739
+
1740
+ Activate scanners with stack=<STACK_GUESS>? [Y/n/custom/skip]
1741
+ ```
1742
+
1743
+ Persist the answer to the bassclef-configs block. Phase 2.3.5 (bassclef#1135 WU-4) wrote the seed file; this step adds the `security_scanning` block to it via a Python merge to keep the JSONC valid.
1744
+
1745
+ ```bash
1746
+ # Add security_scanning block to .claude/bassclef-configs.jsonc (bassclef#1135).
1747
+ # Phase 2.3.5 already wrote the seed; we add a block via JSONC-aware Python merge.
1748
+ python3 - "$STACK_GUESS" <<'PY'
1749
+ import json, re, sys, pathlib
1750
+ path = pathlib.Path('.claude/bassclef-configs.jsonc')
1751
+ raw = path.read_text()
1752
+ # Strip line + block comments before parsing
1753
+ stripped = re.sub(r'/\*.*?\*/', '', re.sub(r'//.*$', '', raw, flags=re.M), flags=re.S)
1754
+ data = json.loads(stripped)
1755
+ stack = sys.argv[1]
1756
+ data['security_scanning'] = {
1757
+ 'stack': None if stack in ('null', 'skip', '') else stack,
1758
+ 'additional_scanners': [],
1759
+ 'block_on_high_severity': True,
1760
+ }
1761
+ path.write_text(json.dumps(data, indent=2) + '\n')
1762
+ PY
1763
+ ```
1764
+
1765
+ Note: the Python merge re-serializes the file without comments. Operators
1766
+ keep the schema-doc reference (`standards/bassclef-configs-schema.md`)
1767
+ as the bassclef comment-bearing source; the on-disk file is the values.
1768
+ A follow-on (bassclef#1135 V2) may add a comment-preserving JSONC writer.
1769
+
1770
+ INSTEAD of asking the operator to know the scanner names: the standard supplies the per-stack defaults; the operator confirms the stack choice only.
1771
+
1772
+ If the operator answers `skip`, write `"stack": null` — scanners stay off until the next session re-runs the question.
1773
+
1774
+ **Operator follow-up — copy the reference workflow:**
1775
+
1776
+ After the bassclef-configs block is written, point the operator at the matching reference workflow:
1777
+
1778
+ ```
1779
+ Next step: copy the per-stack reference workflow into your repo's CI.
1780
+
1781
+ cp ~/src/sunj-labs/bassclef/config/security-scanners/<STACK_GUESS>.yml \
1782
+ .github/workflows/security-scan.yml
1783
+
1784
+ The workflow auto-skips when the bassclef-configs stack value is null,
1785
+ so copying is harmless if you're not ready to activate yet.
1786
+ ```
1787
+
1788
+ For stacks beyond node-typescript + python (which ship reference workflows), the operator authors their own workflow guided by `standards/security-scanner-adapter.md` § Stack reference matrix.
1789
+
1790
+ ##### 2.6 Set up .gitignore for bassclef's runtime files
1791
+
1792
+ If `.gitignore` doesn't exist, create with bassclef runtime markers:
1793
+
1794
+ ```bash
1795
+ [ -f .gitignore ] || cat > .gitignore <<'EOF'
1796
+ # Bassclef runtime artifacts (per-machine, not content)
1797
+ .claude/LAST_SAVE
1798
+ .claude/SESSION_LOCK
1799
+ .claude/CHECKPOINT.md
1800
+ .claude/settings.local.json
1801
+ state/markers/verify/
1802
+ EOF
1803
+ ```
1804
+
1805
+ The sync hook will add its managed symlink block on next run; this
1806
+ seeds the file with everything else.
1807
+
1808
+ #### Phase 3 — Create starter docs (CLAUDE.md, whereami)
1809
+
1810
+ **Working directory: target repo.**
1811
+
1812
+ ##### 3.1 Create the starter CLAUDE.md
1813
+
1814
+ Minimal starter. Operator owns this file going forward.
1815
+
1816
+ The first non-title line reads `@.claude/bassclef-orientation.md` (per
1817
+ ADR-032 + Anthropic's `@filename` convention). This import loads bassclef's
1818
+ orientation into every session. The orientation names the gates, the
1819
+ luminaries, the tier ladder, and the rules the agent should not break.
1820
+
1821
+ The orientation file lands in the adopter repo as a symlink into bassclef.
1822
+ The sync template Section 3d-bis writes the symlink (per bassclef-upstream#925).
1823
+ Without this symlink, the import resolves only through `additionalDirectories`.
1824
+ If that path breaks — rename, delete, corporate laptop with no sibling — the
1825
+ import silently no-ops. The symlink closes that gap.
1826
+
1827
+ Bassclef updates the file via release. The adopter's CLAUDE.md stays
1828
+ adopter-owned outside the import line.
1829
+
1830
+ ```markdown
1831
+ # [repo-name]
1832
+
1833
+ @.claude/bassclef-orientation.md
1834
+
1835
+ ## What this repo is
1836
+
1837
+ [One-paragraph description]
1838
+
1839
+ ## Substrate
1840
+
1841
+ Inherits bassclef substrate via `--add-dir ../bassclef` (desktop) and
1842
+ S3 tarball sync (mobile). The `@.claude/bassclef-orientation.md` import
1843
+ above auto-loads the substrate discipline (gates, luminaries, tier ladder,
1844
+ what-NOT-to-do). For the upstream role declaration, see
1845
+ `~/src/sunj-labs/bassclef/CLAUDE.md`.
1846
+
1847
+ ## Stack
1848
+
1849
+ [Framework, language, key dependencies — or "documentation only"]
1850
+
1851
+ ## Current state
1852
+
1853
+ [Phase, active iteration, any context a fresh session needs]
1854
+ ```
1855
+
1856
+ ##### 3.2 Create the starter docs/whereami.md
1857
+
1858
+ ```bash
1859
+ mkdir -p docs
1860
+ cp ~/src/sunj-labs/bassclef/templates/whereami-template.md \
1861
+ docs/whereami.md
1862
+ ```
1863
+
1864
+ Operator fills in phase + iteration section. If repo is fresh,
1865
+ `phase: Inception` with `iteration_bet: none — new repo` is fine.
1866
+
1867
+ #### Phase 3.5 — Optional: provision the deploy host + bootstrap secrets (bassclef#522)
1868
+
1869
+ **Working directory: target repo.**
1870
+
1871
+ After Phase 3 (docs stubs) lands the substrate config files, three opt-in
1872
+ flags chain follow-on skills inline so every new sunj-labs repo bootstraps
1873
+ in one dispatch instead of four manual steps. Without these flags, the
1874
+ operator runs the follow-on skills separately afterward (older pattern,
1875
+ still supported).
1876
+
1877
+ ##### Flag reference
1878
+
1879
+ | Invocation | Behavior |
1880
+ |---|---|
1881
+ | `/onboard-repo` (no flag) | Phases 1–3 only. Interactive prompt at end: "Run /provision-deploy-host now? [Y/n/skip]" + same for /secrets-bootstrap. Skip-state persists per below. |
1882
+ | `/onboard-repo --with-deploy-host` | Phases 1–3, then dispatches `/provision-deploy-host` inline (operator-confirms before AWS calls). Skips the interactive prompt for deploy-host. |
1883
+ | `/onboard-repo --with-secrets` | Phases 1–3, then dispatches `/secrets-bootstrap` inline (audits + bootstraps env per substrate.secrets.md). Skips the interactive prompt for secrets. |
1884
+ | `/onboard-repo --full` | All of the above — substrate scaffolded + Amplify host provisioned + secrets audited in one dispatch. Recommended for new sunj-labs repos. |
1885
+ | `/onboard-repo --greenfield-from-intent "<intent>"` | Cold-adopter demo path. Skips Path A entirely and runs Path B — empty directory in, full artifact chain on disk plus localhost gallery URL out. No GitHub repo gets created. No vendor accounts needed. Uses the LLM your Claude Code session provides. For the extended path (deploy mocks to a configured subdomain plus chain to `/build` plus `/launch-preview`), finish Path A first to wire vendor bindings, then dispatch `/launch --voice-input` without `--local`. See `docs/iteration-bets/2026-05-12-phase-v-hey-alexa-mvd.md` and bassclef#923 / PR #924 for the local-mode building block. |
1886
+
1887
+ ##### Tier gate (bassclef#740 Task 1.2)
1888
+
1889
+ Three of the flags above dispatch skills that ship at `tier: standard`.
1890
+ Lite adopters running the bassclef-lite tarball do not carry
1891
+ `/provision-deploy-host` or `/secrets-bootstrap`. Before dispatching
1892
+ `--with-deploy-host`, `--with-secrets`, or `--full`, resolve the
1893
+ adopter tier through `lib/tier-check.sh`:
1894
+
1895
+ ```bash
1896
+ source lib/tier-check.sh
1897
+ tier=$(resolve_adopter_tier)
1898
+ ```
1899
+
1900
+ The check reads `.claude/bassclef-configs.jsonc` `adopter.tier` field
1901
+ first. Falls back to skill probe — presence of `/provision-deploy-host`
1902
+ skill returns `standard`. Safe default is `standard`.
1903
+
1904
+ Behavior per tier:
1905
+
1906
+ | Invocation | tier=lite | tier=standard or pro |
1907
+ |---|---|---|
1908
+ | `/onboard-repo` (no flag) | Phases 1–3 run. Skip interactive prompts for /provision-deploy-host and /secrets-bootstrap. | Phases 1–3 + interactive prompts. |
1909
+ | `/onboard-repo --with-deploy-host` (lite adopter) | Print graceful upgrade message: "--with-deploy-host requires bassclef standard. Reason: /provision-deploy-host is a standard-tier skill. Upgrade path: install bassclef standard, or run Phases 1–3 without the flag and provision the deploy host manually later." Continue Phases 1–3 as if no flag was passed. | Phases 1–3 + `/provision-deploy-host` dispatched inline. |
1910
+ | `/onboard-repo --with-secrets` (lite adopter) | Print graceful upgrade message: "--with-secrets requires bassclef standard. Reason: /secrets-bootstrap is a standard-tier skill. Upgrade path: install bassclef standard, or run Phases 1–3 without the flag and manage secrets manually via substrate.secrets.md." Continue Phases 1–3. | Phases 1–3 + `/secrets-bootstrap` dispatched inline. |
1911
+ | `/onboard-repo --full` (lite adopter) | Print graceful upgrade message: "--full requires bassclef standard. Reason: /provision-deploy-host and /secrets-bootstrap are both standard-tier skills. Upgrade path: install bassclef standard." Continue Phases 1–3. | Phases 1–3 + both skills dispatched inline. |
1912
+ | `/onboard-repo --greenfield-from-intent` | Runs Path B via `/launch --local`. Lite-safe by design. | Same. |
1913
+
1914
+ Per canvas 2026-07-19-bassclef-lite Q6 Option E — one SKILL.md file,
1915
+ same name in both tiers, body carries runtime tier awareness with
1916
+ graceful degrade.
1917
+
1918
+ ##### Interactive prompt (when no flag)
1919
+
1920
+ After Phase 3 completes, agent surfaces follow-on prompts. Prompts are
1921
+ gated on artifact existence (bassclef#961) and the deploy-host prompt
1922
+ uses honest platform framing (bassclef#962):
1923
+
1924
+ ```bash
1925
+ # Check for substrate.secrets.md (bassclef#961) — silently skip the
1926
+ # secrets prompt if the manifest doesn't exist. No point prompting an
1927
+ # adopter to bootstrap secrets when they haven't declared any.
1928
+ HAS_SECRETS_MANIFEST=0
1929
+ if [ -f substrate.secrets.md ]; then
1930
+ HAS_SECRETS_MANIFEST=1
1931
+ fi
1932
+ ```
1933
+
1934
+ Then surface (deploy-host always; secrets only when manifest present):
1935
+
1936
+ ```
1937
+ Substrate scaffolded. One or two follow-on steps remain before /launch
1938
+ runs cleanly end-to-end:
1939
+
1940
+ 1. /provision-deploy-host — provisions a preview deploy host. Tested
1941
+ today with AWS Amplify; other platforms (Vercel, Netlify, Cloudflare
1942
+ Pages, GCP, Azure) ship via adapter PRs. If you want a platform that
1943
+ doesn't ship yet, file a ticket — or build the adapter (it's a single
1944
+ shell script following `scripts/adapters/deploy/amplify.sh` as the
1945
+ reference). The provision takes ~1-2h, mostly waiting. Required
1946
+ before /launch can bind a subdomain on the chosen host. Skip if this
1947
+ repo opts out of preview deployment (hosting_platform: none) or if
1948
+ you want to provision later.
1949
+
1950
+ 2. /secrets-bootstrap — audits + bootstraps env credentials per
1951
+ substrate.secrets.md. Required before /launch, /deploy-prod, or
1952
+ /journal push. Skip if creds are already known-good.
1953
+
1954
+ Run /provision-deploy-host now? [Y/n/skip]
1955
+ Run /secrets-bootstrap now? [Y/n/skip]
1956
+ ```
1957
+
1958
+ When `HAS_SECRETS_MANIFEST=0` (no substrate.secrets.md on disk), the
1959
+ agent OMITS step 2 entirely + the second prompt — no "(likely no-op)"
1960
+ hedge, no prompt asking the adopter to bootstrap secrets they haven't
1961
+ declared. The numbered list collapses to "One follow-on step remains".
1962
+
1963
+ Each `Y` triggers the inline dispatch (operator-confirms any destructive
1964
+ substep — AWS calls, env writes). Each `n` defers to a later session.
1965
+ Each `skip` persists in `substrate.config.md` so re-running /onboard-repo
1966
+ on this repo doesn't re-prompt for that step.
1967
+
1968
+ The deploy-host prompt wording matters: cold adopters reading it should
1969
+ understand bassclef's posture on multi-platform support without
1970
+ operator coaching. "Tested with AWS Amplify; other platforms via
1971
+ adapter PRs" reads as honest (we have one tested path + a clear
1972
+ contribution route) instead of overpromising. Filing a ticket for an
1973
+ unsupported platform is invited, not gated.
1974
+
1975
+ ##### Skip-state persistence
1976
+
1977
+ When operator chooses `skip` (not `n`) for a follow-on step, the agent
1978
+ writes one of these fields to `substrate.config.md`:
1979
+
1980
+ ```yaml
1981
+ # === Onboarding follow-on skip state (bassclef#522) ===
1982
+ #
1983
+ # Set by /onboard-repo when the operator explicitly skips a follow-on
1984
+ # step. Re-runs of /onboard-repo do NOT re-prompt for skipped steps;
1985
+ # operator can dispatch the underlying skill directly when ready, or
1986
+ # unset the field to re-enable the prompt.
1987
+ onboarding_deploy_host_skipped: true # set when operator answers 'skip'
1988
+ onboarding_secrets_skipped: true # set when operator answers 'skip'
1989
+ ```
1990
+
1991
+ `n` (decline-this-time) does NOT persist — re-runs re-prompt because the
1992
+ operator didn't say skip-permanently.
1993
+
1994
+ `Y` sets the per-platform provisioning indicator (e.g., `amplify_app_id`) on success. Chain pre-flight Check 6 (bassclef#523) reads that indicator when /launch dispatches. Without it, /launch BLOCKs with three-path remediation. The two flags pair well. `--with-deploy-host` pre-empts the BLOCK. The default flow lets the BLOCK surface the missing setup.
1995
+
1996
+ ##### Composes with
1997
+
1998
+ | Skill | Role |
1999
+ |---|---|
2000
+ | `/provision-deploy-host` | Inline dispatch when `--with-deploy-host` or interactive `Y`. Populates `amplify_app_id` (or platform-equivalent indicator). |
2001
+ | `/secrets-bootstrap` | Inline dispatch when `--with-secrets` or interactive `Y`. Audits creds against `substrate.secrets.md`. |
2002
+ | `/bind-subdomain <subdomain>` | Separate dispatch — not chained from /onboard-repo (subdomain choice is repo-specific; no good default). Run after deploy host provisioned. |
2003
+ | `/launch` | First substantive dispatch after onboarding completes. Chain pre-flight Check 6 verifies the provisioning indicator before variant generation runs. |
2004
+
2005
+ #### Phase 3.6 — Closing handoff (bassclef#964)
2006
+
2007
+ After Phase 3 (and Phase 3.5 if any flag fired), Path A surfaces a
2008
+ closing handoff. Two templates — adopter-mode and operator-mode —
2009
+ mirror the same split documented at `.claude/skills/launch/SKILL.md`
2010
+ Phase 14b.
2011
+
2012
+ ##### Adopter-mode auto-chain (default when context signals cold-adopter)
2013
+
2014
+ Cold-adopter signals: `/onboard-repo` was invoked from a fresh
2015
+ directory (no prior chronicle / no whereami / no iteration-goal), OR
2016
+ the `--greenfield-from-intent "<paragraph>"` flag was used. In either
2017
+ shape, the agent has a paragraph in hand (either from the flag or
2018
+ asked at Phase 1 as part of the "what is this repo for?" prompt).
2019
+
2020
+ The adopter-mode handoff template re-uses that paragraph and offers
2021
+ the build:
2022
+
2023
+ ```
2024
+ Your repo is wired. Two things you can do next — pick one:
2025
+
2026
+ 1. Build something now. I'll take the paragraph you gave me earlier
2027
+ ("<INTENT_PARAGRAPH>") and run /launch --local. About 4 minutes
2028
+ later you'll have four clickable mock directions to pick from on
2029
+ your phone or laptop. After you pick a direction, I'll write the
2030
+ formal artifacts + the app code for that direction only (~15 min
2031
+ total). You'll have a working local version to play with.
2032
+
2033
+ 2. Stop here and come back later. I'll save your spot. When you come
2034
+ back, just open Claude Code in this repo and say "/launch --local"
2035
+ to pick up where we left off.
2036
+
2037
+ Build now? [Y/n]
2038
+ ```
2039
+
2040
+ `Y` triggers `/launch --local "<INTENT_PARAGRAPH>"` inline in the
2041
+ same session — no context switch, no second dispatch. `n` writes
2042
+ the paragraph to `docs/whereami.md` under "Pending intent" so the
2043
+ next session's `/sprint` surfaces it.
2044
+
2045
+ Substitute `<INTENT_PARAGRAPH>` with the actual paragraph (from
2046
+ `--greenfield-from-intent` flag value OR from the Phase 1 "what is
2047
+ this repo for?" prompt — operator confirms it's still accurate before
2048
+ the auto-chain fires).
2049
+
2050
+ The adopter-mode handoff **skips entirely**:
2051
+ - "Run /sprint to orient" — adopter has no work in flight to orient
2052
+ to
2053
+ - "Run /whereami" — same
2054
+ - Bare command names (`/launch`) without the plain-English explanation
2055
+ of what /launch produces
2056
+
2057
+ ##### Operator-mode handoff (non-cold-adopter context)
2058
+
2059
+ This mode fires when cold-adopter signals are absent. Signals absent means: the repo has prior chronicles, whereami is populated, OR the operator passed `--no-auto-chain`. The agent uses this wording:
2060
+
2061
+ ```
2062
+ Your bassclef is ready.
2063
+
2064
+ What landed this session:
2065
+ - Skills, rules, and hooks synced from bassclef
2066
+ - GitHub labels + branch protection set up
2067
+ - Session-start wiring wired
2068
+
2069
+ Best single next move — try /sprint in a fresh session. It reads
2070
+ your current state and proposes the next work.
2071
+
2072
+ Other useful next moves:
2073
+ - /whereami — current state in one glance
2074
+ - /launch <tier> "<paragraph>" — start a new feature; see /launch
2075
+ SKILL.md for sizes + flags
2076
+ - /launch --local "<paragraph>" — same as above but writes artifacts
2077
+ to disk + serves the gallery on localhost (no vendor accounts
2078
+ needed)
2079
+
2080
+ Run any of those in a fresh session.
2081
+ ```
2082
+
2083
+ Operator-mode keeps a hard break between onboard and build. Operators often onboard a repo as one task. They pick up build work later in a fresh session.
2084
+
2085
+ ##### Composes with
2086
+
2087
+ | Skill | How it composes |
2088
+ |---|---|
2089
+ | `/launch --local` | Adopter-mode auto-chain dispatch. Inline in same session. |
2090
+ | `/sprint` | Operator-mode orientation skill. Surfaces "Pending intent" if `n` was chosen for the build-now prompt. |
2091
+ | `/whereami` | Same. |
2092
+
2093
+ #### Phase 4 — Bassclef-side matrix PRs
2094
+
2095
+ **Phase 4 retired under bassclef goal 2026-05-03-consumer-bassclef-sync-rollout WU-7.**
2096
+
2097
+ Under thin-pointer architecture (Phase 2.1 + 2.4 above), the `bassclef-template-sync.yml` workflow is deleted (no more auto-PRs). The `bassclef-s3-publish.yml` URL-refresh matrix is unused by thin-pointer (no `.bassclef-url`); a follow-on issue tracks its retirement separately.
2098
+
2099
+ **No matrix-PR step is needed for new consumers.** The thin-pointer dispatcher reads from `.bassclef-source.json` directly; bassclef's matrix list is no longer the source of truth for which consumers receive substrate updates — every consumer fetches HEAD on every session-start.
2100
+
2101
+ Skip this phase entirely. Phase 5 (Verify) below validates that the consumer is correctly wired without any bassclef-side matrix change.
2102
+
2103
+ #### Historical note
2104
+
2105
+ Pre-2026-05-03, this phase added the new consumer to BOTH `bassclef-s3-publish.yml` and `bassclef-template-sync.yml` matrices. The 2026-04-18 lesson (#166 caught a one-of-two miss after #158) drove the bassclef-workflow-parity rule. With both workflows retired/unused under thin-pointer, the parity rule no longer applies to this surface.
2106
+
2107
+ ## Verify (acceptance)
2108
+
2109
+ Acceptance differs per path.
2110
+
2111
+ ### Path A — verify everything's wired correctly (after Phases 1–3 + 3.5 if flagged)
2112
+
2113
+ - [ ] `gh label list --repo "$REPO"` shows all 11 standard labels
2114
+ - [ ] `gh api repos/$REPO/branches/$DEFAULT_BRANCH/protection` returns 200
2115
+ - [ ] (If applicable) `gh api repos/$REPO/environments/prod` shows required_reviewers
2116
+ - [ ] `.claude/hooks/bassclef-sync.sh` is executable
2117
+ - [ ] `.claude/settings.json` validates as JSON
2118
+ - [ ] `substrate.config.md` has no `TODO:` markers (or operator approved deferring them)
2119
+ - [ ] `.bassclef-source.json` exists, validates against bassclef schema (`standards/bassclef-source/schemas/bassclef-source.schema.json`), references a valid `BASSCLEF_TOKEN` env var, and has an explicit `auto_sync` value (true OR false) set by the adopter at Phase 2.4 — silence is not deferral here
2120
+ - [ ] Running the sync hook produces no errors; banner shows `### BASSCLEF SUBSTRATE — SYNCED`; symlinks resolve
2121
+ - [ ] `CLAUDE.md` exists with at least a one-paragraph description
2122
+ - [ ] `docs/whereami.md` exists with phase + iteration filled in
2123
+ - [ ] **(Retired)** ~~Bassclef PR opened with both workflow matrices updated~~ — Phase 4 retired under bassclef goal 2026-05-03 WU-7
2124
+ - [ ] Test session in target repo: `claude` → SessionStart hook fires → no BLOCKED items from missing substrate
2125
+
2126
+ ### Path B acceptance — after Phases B.1–B.5
2127
+
2128
+ - [ ] Generated task list had at most 5 tasks (Phase B.1–B.5); no Path A phases queued
2129
+ - [ ] No `gh` calls fired during the dispatch
2130
+ - [ ] No `gh repo create`, no `gh api .../environments/prod`, no branch-protection writes
2131
+ - [ ] `.git/` exists with at least one commit
2132
+ - [ ] `.claude/hooks/bassclef-sync.sh` is executable
2133
+ - [ ] `.claude/settings.json` validates as JSON
2134
+ - [ ] `substrate.config.md` exists with `hosting_platform: none`
2135
+ - [ ] `CLAUDE.md` exists
2136
+ - [ ] `/launch --local --voice-input` was the final dispatch (not extended `/launch` and not `/build` and not `/launch-preview`)
2137
+ - [ ] `/launch` emitted a `localhost:80xx` URL pointing at the generated gallery
2138
+ - [ ] Operator opened the URL and saw the variants gallery
2139
+
2140
+ ## What NOT to do
2141
+
2142
+ - **Do not** restore Phase 4 — the bassclef-template-sync.yml workflow is retired. Adding new consumers to a deleted matrix produces nothing.
2143
+ - **Do not** vendor `bassclef-sync.template.sh` directly — that was the pre-2026-05-03 pattern. The thin-pointer dispatcher reads `.bassclef-source.json` and fetches bassclef's CURRENT logic at fire-time. Vendoring reproduces the drift problem WU-7 retires.
2144
+ - **Do not** auto-merge the bassclef-side PR. Bassclef is the trust
2145
+ root; every matrix change needs operator review.
2146
+ - **Do not** hardcode the presigned URL anywhere — read from
2147
+ `.bassclef-url` at runtime.
2148
+ - **Do not** copy `.claude/settings.local.json` from another repo — it's
2149
+ machine-local per-operator permission grants.
2150
+ - **Do not** commit secrets. The sync URL expires in ~7 days; don't
2151
+ treat it as a secret, but don't commit long-lived tokens either.
2152
+ - **Do not** mix Path A and Path B in one dispatch. If the operator picked
2153
+ Path B at the prereqs-missing menu, skip every Path A phase — don't
2154
+ generate a task list that walks Phase 1.1 / 1.2 / 1.3 / 2.4 / 3.5 /
2155
+ Phase 4. The whole point of Path B is the ≤5-step task list ending
2156
+ in `/launch --local --voice-input`. Any Path A task that leaks into a
2157
+ Path B run is the bassclef#934 regression.
2158
+ - **Do not** chain `/build` or `/launch-preview` after Path B's final
2159
+ dispatch. Path B's deliverable is the localhost URL `/launch --local`
2160
+ emits; the extended chain belongs to operators with vendor bindings.
2161
+
2162
+ ## Relationship to other skills
2163
+
2164
+ - **`/autonomous start`** — runs AFTER `/onboard-repo` to set up iteration
2165
+ scaffolding. `/onboard-repo` does the one-time wiring; `/autonomous
2166
+ start` does the per-iteration setup.
2167
+ - **`/substrate-check`** — validates substrate wiring AFTER onboarding.
2168
+ Run it as a post-flight check.
2169
+ - **`/promote`** — once onboarded, the new repo can file
2170
+ `bassclef-evolution` issues. The labels bootstrapped in Phase 1.1
2171
+ make them scannable.
2172
+
2173
+ ## Source
2174
+
2175
+ Ritual observed 4x on 2026-04-17: four consumer repos (refresh). Each took ~15 minutes by hand.
2176
+ Codified here so the fifth, sixth, and nth take ~3 minutes.
2177
+
2178
+ Closes bassclef #164.
2179
+
2180
+ Path B added 2026-06-13. Cold-adopter smoke test on `new-recipe-test`
2181
+ 2026-06-13 01:30am showed PR #931's edit to the flag-row description
2182
+ was cosmetic — `--greenfield-from-intent` still queued 12 standard
2183
+ onboarding tasks because the Procedure was monolithic. Refactor splits
2184
+ Procedure into Path A (standard, unchanged) and Path B (≤5 steps, no
2185
+ GitHub-remote operations, ends in `/launch --local --voice-input`).
2186
+ Closes bassclef#934.
2187
+
2188
+ ## Output discipline
2189
+
2190
+ Dispatch `/kiss words --rewrite` on your skill output before you return it. See `standards/skill-output-discipline.md` for the contract.